pax_global_header 0000666 0000000 0000000 00000000064 14655770405 0014527 g ustar 00root root 0000000 0000000 52 comment=2afc006bc71c9054e71b34f0c7a137669ab349de
golang-github-minio-pkg-3.0.10/ 0000775 0000000 0000000 00000000000 14655770405 0016227 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/.github/ 0000775 0000000 0000000 00000000000 14655770405 0017567 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/.github/workflows/ 0000775 0000000 0000000 00000000000 14655770405 0021624 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/.github/workflows/go.yml 0000664 0000000 0000000 00000001345 14655770405 0022757 0 ustar 00root root 0000000 0000000 name: Go
on:
push:
branches: [main]
pull_request:
branches: [main]
# This ensures that previous jobs for the PR are canceled when the PR is
# updated.
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
build:
name: Build Go ${{ matrix.go-version }}
runs-on: ubuntu-latest
strategy:
matrix:
go-version: [1.22.x]
steps:
- name: Set up Go ${{ matrix.go-version }}
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v4
- name: Test
run: make test
golang-github-minio-pkg-3.0.10/.github/workflows/ldap.yaml 0000664 0000000 0000000 00000002466 14655770405 0023440 0 ustar 00root root 0000000 0000000 name: LDAP Config Validator
on:
push:
branches: [main]
pull_request:
branches: [main]
# This ensures that previous jobs for the PR are canceled when the PR is
# updated.
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
ldap-config-validator:
name: Test LDAP configuration validator (Go ${{ matrix.go-version }})
runs-on: ubuntu-latest
services:
openldap:
image: quay.io/minio/openldap
ports:
- "389:389"
- "636:636"
env:
LDAP_ORGANIZATION: "MinIO Inc"
LDAP_DOMAIN: "min.io"
LDAP_ADMIN_PASSWORD: "admin"
strategy:
matrix:
go-version: [1.22.x]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
check-latest: true
- uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-${{ matrix.go-version }}-go-
- name: Test with LDAP server
env:
LDAP_TEST_SERVER: "localhost:389"
run: make test-ldap
golang-github-minio-pkg-3.0.10/.github/workflows/vulncheck.yml 0000664 0000000 0000000 00000001203 14655770405 0024325 0 ustar 00root root 0000000 0000000 name: VulnCheck
on:
pull_request:
branches:
- master
- main
push:
branches:
- master
- main
jobs:
vulncheck:
name: Analysis
runs-on: ubuntu-latest
strategy:
matrix:
go-version: [ 1.22.5 ]
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go-version }}
check-latest: true
- name: Get govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest
shell: bash
- name: Run govulncheck
run: govulncheck ./...
shell: bash
golang-github-minio-pkg-3.0.10/.gitignore 0000664 0000000 0000000 00000000345 14655770405 0020221 0 ustar 00root root 0000000 0000000 **/*.swp
cover.out
*~
minio
!*/
site/
**/*.test
**/*.sublime-workspace
/.idea/
/Minio.iml
**/access.log
vendor/**/*.js
vendor/**/*.json
.DS_Store
*.syso
coverage.txt
.vscode/
*.tar.bz2
parts/
prime/
stage/
.sia_temp/
config.json
golang-github-minio-pkg-3.0.10/.golangci.yml 0000664 0000000 0000000 00000001061 14655770405 0020611 0 ustar 00root root 0000000 0000000 linters-settings:
golint:
min-confidence: 0
misspell:
locale: US
linters:
disable-all: true
enable:
- typecheck
- goimports
- misspell
- govet
- revive
- ineffassign
- gosimple
- gomodguard
- gofmt
- unused
- unconvert
issues:
exclude-use-default: false
exclude:
- should have a package comment
- error strings should not be capitalized or end with punctuation or a newline
service:
golangci-lint-version: 1.20.0 # use the fixed version to not introduce new linters unexpectedly
golang-github-minio-pkg-3.0.10/LICENSE 0000664 0000000 0000000 00000103333 14655770405 0017237 0 ustar 00root root 0000000 0000000 GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
.
golang-github-minio-pkg-3.0.10/Makefile 0000664 0000000 0000000 00000001432 14655770405 0017667 0 ustar 00root root 0000000 0000000 GOPATH := $(shell go env GOPATH)
GOARCH := $(shell go env GOARCH)
GOOS := $(shell go env GOOS)
all: test
getdeps:
@mkdir -p ${GOPATH}/bin
@echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin
lint: getdeps
@echo "Running $@ check"
@${GOPATH}/bin/golangci-lint cache clean
@${GOPATH}/bin/golangci-lint run --build-tags kqueue --timeout=10m --config ./.golangci.yml
test: lint
@echo "Running unit tests"
@go test -race -tags kqueue ./...
test-ldap: lint
@echo "Running unit tests for LDAP with LDAP server at '"${LDAP_TEST_SERVER}"'"
@go test -v -race ./ldap
clean:
@echo "Cleaning up all the generated files"
@find . -name '*.test' | xargs rm -fv
@find . -name '*~' | xargs rm -fv
golang-github-minio-pkg-3.0.10/README.md 0000664 0000000 0000000 00000000342 14655770405 0017505 0 ustar 00root root 0000000 0000000 # pkg
Collection of common packages used in MinIO projects.
# License
Use of this package `pkg` is governed by the GNU AGPLv3 license that can be found in the [LICENSE](https://github.com/minio/pkg/blob/master/LICENSE) file.
golang-github-minio-pkg-3.0.10/certs/ 0000775 0000000 0000000 00000000000 14655770405 0017347 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/certs/ca-certs.go 0000664 0000000 0000000 00000005261 14655770405 0021403 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package certs
import (
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
)
// GetRootCAs loads all X.509 certificates at the given path and adds them
// to the list of system root CAs, if available. The returned CA pool
// is a conjunction of the system root CAs and the certificate(s) at
// the given path.
//
// If path is a regular file, LoadCAs simply adds it to the CA pool
// if the file contains a valid X.509 certificate
//
// If the path points to a directory, LoadCAs iterates over all top-level
// files within the directory and adds them to the CA pool if they contain
// a valid X.509 certificate.
func GetRootCAs(path string) (*x509.CertPool, error) {
rootCAs, _ := loadSystemRoots()
if rootCAs == nil {
// In some systems system cert pool is not supported
// or no certificates are present on the
// system - so we create a new cert pool.
rootCAs = x509.NewCertPool()
}
// Open the file path and check whether its a regular file
// or a directory.
f, err := os.Open(path)
if errors.Is(err, os.ErrNotExist) {
return rootCAs, nil
}
if errors.Is(err, os.ErrPermission) {
return rootCAs, nil
}
if err != nil {
return rootCAs, err
}
defer f.Close()
stat, err := f.Stat()
if err != nil {
return rootCAs, err
}
// In case of a file add it to the root CAs.
if !stat.IsDir() {
bytes, err := ioutil.ReadAll(f)
if err != nil {
return rootCAs, err
}
if !rootCAs.AppendCertsFromPEM(bytes) {
return rootCAs, fmt.Errorf("cert: %q does not contain a valid X.509 PEM-encoded certificate", path)
}
return rootCAs, nil
}
// Otherwise iterate over the files in the directory
// and add each on to the root CAs.
files, err := f.Readdirnames(0)
if err != nil {
return rootCAs, err
}
for _, file := range files {
bytes, err := ioutil.ReadFile(filepath.Join(path, file))
if err == nil { // ignore files which are not readable.
rootCAs.AppendCertsFromPEM(bytes)
}
}
return rootCAs, nil
}
golang-github-minio-pkg-3.0.10/certs/ca-certs_test.go 0000664 0000000 0000000 00000004422 14655770405 0022440 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package certs
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func TestGetRootCAs(t *testing.T) {
emptydir, err := ioutil.TempDir("", "test-get-root-cas")
if err != nil {
t.Fatalf("Unable create temp directory. %v", emptydir)
}
defer os.RemoveAll(emptydir)
dir1, err := ioutil.TempDir("", "test-get-root-cas")
if err != nil {
t.Fatalf("Unable create temp directory. %v", dir1)
}
defer os.RemoveAll(dir1)
if err = os.Mkdir(filepath.Join(dir1, "empty-dir"), 0o755); err != nil {
t.Fatalf("Unable create empty dir. %v", err)
}
dir2, err := ioutil.TempDir("", "test-get-root-cas")
if err != nil {
t.Fatalf("Unable create temp directory. %v", dir2)
}
defer os.RemoveAll(dir2)
if err = ioutil.WriteFile(filepath.Join(dir2, "empty-file"), []byte{}, 0o644); err != nil {
t.Fatalf("Unable create test file. %v", err)
}
testCases := []struct {
certCAsDir string
expectedErr error
}{
// ignores non-existent directories.
{"nonexistent-dir", nil},
// Ignores directories.
{dir1, nil},
// Ignore empty directory.
{emptydir, nil},
// Loads the cert properly.
{dir2, nil},
}
for _, testCase := range testCases {
_, err := GetRootCAs(testCase.certCAsDir)
if testCase.expectedErr == nil {
if err != nil {
t.Fatalf("error: expected = , got = %v", err)
}
} else if err == nil {
t.Fatalf("error: expected = %v, got = ", testCase.expectedErr)
} else if testCase.expectedErr.Error() != err.Error() {
t.Fatalf("error: expected = %v, got = %v", testCase.expectedErr, err)
}
}
}
golang-github-minio-pkg-3.0.10/certs/cert_pool_nix.go 0000664 0000000 0000000 00000004473 14655770405 0022552 0 ustar 00root root 0000000 0000000 //go:build !windows
// +build !windows
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package certs
import (
"crypto/x509"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
// Possible directories with certificate files, this is an extended
// list from https://golang.org/src/crypto/x509/root_unix.go?#L18
// for k8s platform
var certDirectories = []string{
"/var/run/secrets/kubernetes.io/serviceaccount",
}
// readUniqueDirectoryEntries is like ioutil.ReadDir but omits
// symlinks that point within the directory.
func readUniqueDirectoryEntries(dir string) ([]os.FileInfo, error) {
fis, err := ioutil.ReadDir(dir)
if err != nil {
return nil, err
}
uniq := fis[:0]
for _, fi := range fis {
if !isSameDirSymlink(fi, dir) {
uniq = append(uniq, fi)
}
}
return uniq, nil
}
// isSameDirSymlink reports whether fi in dir is a symlink with a
// target not containing a slash.
func isSameDirSymlink(fi os.FileInfo, dir string) bool {
if fi.Mode()&os.ModeSymlink == 0 {
return false
}
target, err := os.Readlink(filepath.Join(dir, fi.Name()))
return err == nil && !strings.Contains(target, "/")
}
func loadSystemRoots() (*x509.CertPool, error) {
caPool, err := x509.SystemCertPool()
if err != nil {
return caPool, err
}
for _, directory := range certDirectories {
fis, err := readUniqueDirectoryEntries(directory)
if err != nil {
if os.IsNotExist(err) || os.IsPermission(err) {
return caPool, nil
}
return caPool, err
}
for _, fi := range fis {
data, err := ioutil.ReadFile(directory + "/" + fi.Name())
if err == nil {
caPool.AppendCertsFromPEM(data)
}
}
}
return caPool, nil
}
golang-github-minio-pkg-3.0.10/certs/cert_pool_windows.go 0000664 0000000 0000000 00000003251 14655770405 0023437 0 ustar 00root root 0000000 0000000 //go:build windows
// +build windows
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package certs
import (
"crypto/x509"
"syscall"
"unsafe"
)
func loadSystemRoots() (*x509.CertPool, error) {
const CRYPTENOTFOUND = 0x80092004
store, err := syscall.CertOpenSystemStore(0, syscall.StringToUTF16Ptr("ROOT"))
if err != nil {
return nil, err
}
defer syscall.CertCloseStore(store, 0)
roots := x509.NewCertPool()
var cert *syscall.CertContext
for {
cert, err = syscall.CertEnumCertificatesInStore(store, cert)
if err != nil {
if errno, ok := err.(syscall.Errno); ok {
if errno == CRYPTENOTFOUND {
break
}
}
return nil, err
}
if cert == nil {
break
}
// Copy the buf, since ParseCertificate does not create its own copy.
buf := (*[1 << 20]byte)(unsafe.Pointer(cert.EncodedCert))[:]
buf2 := make([]byte, cert.Length)
copy(buf2, buf)
if c, err := x509.ParseCertificate(buf2); err == nil {
roots.AddCert(c)
}
}
return roots, nil
}
golang-github-minio-pkg-3.0.10/certs/certs.go 0000664 0000000 0000000 00000012456 14655770405 0021026 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2022 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package certs
import (
"context"
"crypto/tls"
"crypto/x509"
"os"
"os/signal"
"path/filepath"
"sync"
"time"
"github.com/rjeczalik/notify"
)
// LoadX509KeyPairFunc is a function that parses a private key and
// certificate file and returns a TLS certificate on success.
type LoadX509KeyPairFunc func(certFile, keyFile string) (tls.Certificate, error)
// GetCertificateFunc is a callback that allows a TLS stack deliver different
// certificates based on the client trying to establish a TLS connection.
//
// For example, a GetCertificateFunc can return different TLS certificates depending
// upon the TLS SNI sent by the client.
type GetCertificateFunc func(hello *tls.ClientHelloInfo) (*tls.Certificate, error)
// Certificate is a chain of one or more reloadable certificates.
type Certificate struct {
certFile string
keyFile string
loadX509KeyPair LoadX509KeyPairFunc
lock sync.RWMutex
certificate tls.Certificate
listenerLock sync.Mutex
listeners []chan<- tls.Certificate
}
// NewCertificate returns a new Certificate from the given certficate and private key file.
// On a reload event, the certificate is reloaded using the loadX509KeyPair function.
func NewCertificate(certFile, keyFile string, loadX509KeyPair LoadX509KeyPairFunc) (*Certificate, error) {
certFile, err := filepath.Abs(certFile)
if err != nil {
return nil, err
}
keyFile, err = filepath.Abs(keyFile)
if err != nil {
return nil, err
}
c := &Certificate{
certFile: certFile,
keyFile: keyFile,
loadX509KeyPair: loadX509KeyPair,
}
if err := c.Reload(); err != nil {
return nil, err
}
return c, nil
}
// Get returns the current TLS certificate.
func (c *Certificate) Get() tls.Certificate {
c.lock.RLock()
defer c.lock.RUnlock()
return c.certificate
}
// Notify notifies the given events channel whenever the
// certificate has been reloaded successfully. The new
// certificate is sent to the channel receiver.
func (c *Certificate) Notify(events chan<- tls.Certificate) {
c.listenerLock.Lock()
c.listeners = append(c.listeners, events)
c.listenerLock.Unlock()
}
// Stop stops notifying the given events channel whenever the
// certificate has been reloaded successfully.
func (c *Certificate) Stop(events chan<- tls.Certificate) {
c.listenerLock.Lock()
defer c.listenerLock.Unlock()
listeners := make([]chan<- tls.Certificate, 0, len(c.listeners))
for _, listener := range c.listeners {
if listener != events {
listeners = append(listeners, listener)
}
}
c.listeners = listeners
}
// Reload reloads the certificate and sends notifications to
// all listeners that subscribed via Notify.
func (c *Certificate) Reload() error {
certificate, err := c.loadX509KeyPair(c.certFile, c.keyFile)
if err != nil {
return err
}
if certificate.Leaf == nil {
certificate.Leaf, err = x509.ParseCertificate(certificate.Certificate[0])
if err != nil {
return err
}
}
c.lock.Lock()
c.certificate = certificate
c.lock.Unlock()
c.listenerLock.Lock()
for _, listener := range c.listeners {
select {
case listener <- certificate:
default:
}
}
c.listenerLock.Unlock()
return nil
}
// Watch starts watching the certificate and private key file for any changes and reloads
// the Certificate whenever a change is detected.
//
// Additionally, Watch listens on the given list of OS signals and reloads the Certificate
// whenever it encounters one of the signals. Further, Watch reloads the certificate periodically
// if interval > 0.
func (c *Certificate) Watch(ctx context.Context, interval time.Duration, signals ...os.Signal) {
certFileSymLink, _ := isSymlink(c.certFile)
keyFileSymLink, _ := isSymlink(c.keyFile)
if !certFileSymLink && !keyFileSymLink && !isk8s {
go func() {
events := make(chan notify.EventInfo, 1)
if err := notify.Watch(filepath.Dir(c.certFile), events, eventWrite...); err != nil {
return
}
if err := notify.Watch(filepath.Dir(c.keyFile), events, eventWrite...); err != nil {
notify.Stop(events)
return
}
defer notify.Stop(events)
for {
select {
case <-events:
c.Reload()
case <-ctx.Done():
return
}
}
}()
}
if interval > 0 {
go func() {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
c.Reload()
case <-ctx.Done():
return
}
}
}()
}
if len(signals) > 0 {
go func() {
events := make(chan os.Signal, 1)
signal.Notify(events, signals...)
defer signal.Stop(events)
for {
select {
case <-events:
c.Reload()
case <-ctx.Done():
return
}
}
}()
}
}
golang-github-minio-pkg-3.0.10/certs/certs_test.go 0000664 0000000 0000000 00000005730 14655770405 0022062 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package certs_test
import (
"context"
"crypto/tls"
"io"
"os"
"reflect"
"testing"
"time"
"github.com/minio/pkg/v3/certs"
)
func updateCerts(crt, key string) {
// ignore error handling
crtSource, _ := os.Open(crt)
defer crtSource.Close()
crtDest, _ := os.Create("public.crt")
defer crtDest.Close()
io.Copy(crtDest, crtSource)
keySource, _ := os.Open(key)
defer keySource.Close()
keyDest, _ := os.Create("private.key")
defer keyDest.Close()
io.Copy(keyDest, keySource)
}
func TestNewManager(t *testing.T) {
ctx, cancelFn := context.WithCancel(context.Background())
defer cancelFn()
c, err := certs.NewManager(ctx, "public.crt", "private.key", tls.LoadX509KeyPair)
if err != nil {
t.Fatal(err)
}
hello := &tls.ClientHelloInfo{}
gcert, err := c.GetCertificate(hello)
if err != nil {
t.Fatal(err)
}
expectedCert, err := tls.LoadX509KeyPair("public.crt", "private.key")
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(gcert.Certificate, expectedCert.Certificate) {
t.Error("certificate doesn't match expected certificate")
}
_, err = certs.NewManager(ctx, "public.crt", "new-private.key", tls.LoadX509KeyPair)
if err == nil {
t.Fatal("Expected to fail but got success")
}
}
func TestValidPairAfterWrite(t *testing.T) {
ctx, cancelFn := context.WithCancel(context.Background())
defer cancelFn()
expectedCert, err := tls.LoadX509KeyPair("new-public.crt", "new-private.key")
if err != nil {
t.Fatal(err)
}
c, err := certs.NewManager(ctx, "public.crt", "private.key", tls.LoadX509KeyPair)
if err != nil {
t.Fatal(err)
}
updateCerts("new-public.crt", "new-private.key")
defer updateCerts("original-public.crt", "original-private.key")
// Wait for the write event..
time.Sleep(200 * time.Millisecond)
hello := &tls.ClientHelloInfo{}
gcert, err := c.GetCertificate(hello)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(gcert.Certificate, expectedCert.Certificate) {
t.Error("certificate doesn't match expected certificate")
}
rInfo := &tls.CertificateRequestInfo{}
gcert, err = c.GetClientCertificate(rInfo)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(gcert.Certificate, expectedCert.Certificate) {
t.Error("client certificate doesn't match expected certificate")
}
}
golang-github-minio-pkg-3.0.10/certs/event.go 0000664 0000000 0000000 00000001774 14655770405 0021030 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package certs
import (
"github.com/rjeczalik/notify"
)
// isWriteEvent checks if the event returned is a write event
func isWriteEvent(event notify.Event) bool {
for _, ev := range eventWrite {
if event&ev != 0 {
return true
}
}
return false
}
golang-github-minio-pkg-3.0.10/certs/event_linux.go 0000664 0000000 0000000 00000001710 14655770405 0022235 0 ustar 00root root 0000000 0000000 //go:build linux
// +build linux
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package certs
import "github.com/rjeczalik/notify"
// eventWrite contains the notify events that will cause a write
var eventWrite = []notify.Event{notify.InCloseWrite}
golang-github-minio-pkg-3.0.10/certs/event_others.go 0000664 0000000 0000000 00000001722 14655770405 0022405 0 ustar 00root root 0000000 0000000 //go:build !linux
// +build !linux
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package certs
import "github.com/rjeczalik/notify"
// eventWrite contains the notify events that will cause a write
var eventWrite = []notify.Event{notify.Create, notify.Write}
golang-github-minio-pkg-3.0.10/certs/manager.go 0000664 0000000 0000000 00000030462 14655770405 0021315 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2022 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package certs
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"os"
"os/signal"
"path/filepath"
"sync"
"time"
"github.com/minio/pkg/v3/env"
"github.com/rjeczalik/notify"
)
// Manager is a TLS certificate manager that can handle multiple certificates.
// When a client tries to establish a TLS connection, Manager will try to
// pick a certificate that can be validated by the client.
//
// For instance, if the client specifies a TLS SNI then Manager will try to
// find the corresponding certificate. If there is no such certificate it
// will fallback to the certificate named public.crt.
//
// Manager will automatically reload certificates if the corresponding file changes.
type Manager struct {
lock sync.RWMutex
certificates map[pair]*tls.Certificate // Mapping: certificate file name => TLS certificates
defaultCert pair
duration time.Duration
loadX509KeyPair LoadX509KeyPairFunc
done <-chan struct{}
reloadCerts []chan struct{}
}
var isk8s = env.Get("KUBERNETES_SERVICE_HOST", "") != ""
// pair represents a certificate and private key file tuple.
type pair struct {
KeyFile string
CertFile string
}
// NewManager returns a new Manager that handles one certificate specified via
// the certFile and keyFile. It will use the loadX509KeyPair function to (re)load
// certificates.
//
// The certificate loaded from certFile is considered the default certificate.
// If a client does not send the TLS SNI extension then Manager will return
// this certificate.
func NewManager(ctx context.Context, certFile, keyFile string, loadX509KeyPair LoadX509KeyPairFunc) (manager *Manager, err error) {
certFile, err = filepath.Abs(certFile)
if err != nil {
return nil, err
}
keyFile, err = filepath.Abs(keyFile)
if err != nil {
return nil, err
}
manager = &Manager{
certificates: map[pair]*tls.Certificate{},
defaultCert: pair{
KeyFile: keyFile,
CertFile: certFile,
},
loadX509KeyPair: loadX509KeyPair,
done: ctx.Done(),
duration: 1 * time.Minute,
}
if err := manager.AddCertificate(certFile, keyFile); err != nil {
return nil, err
}
return manager, nil
}
// UpdateReloadDuration set custom symlink reload duration
func (m *Manager) UpdateReloadDuration(t time.Duration) {
m.lock.Lock()
m.duration = t
m.lock.Unlock()
}
// AddCertificate adds the TLS certificate in certFile resp. keyFile
// to the Manager.
//
// If there is already a certificate with the same base name it will be
// replaced by the newly added one.
func (m *Manager) AddCertificate(certFile, keyFile string) (err error) {
certFile, err = filepath.Abs(certFile)
if err != nil {
return err
}
keyFile, err = filepath.Abs(keyFile)
if err != nil {
return err
}
certFileIsLink, err := isSymlink(certFile)
if err != nil {
return err
}
keyFileIsLink, err := isSymlink(keyFile)
if err != nil {
return err
}
if certFileIsLink && !keyFileIsLink {
return fmt.Errorf("certs: '%s' is a symlink but '%s' is a regular file", certFile, keyFile)
}
if keyFileIsLink && !certFileIsLink {
return fmt.Errorf("certs: '%s' is a symlink but '%s' is a regular file", keyFile, certFile)
}
certificate, err := m.loadX509KeyPair(certFile, keyFile)
if err != nil {
return err
}
// We set the certificate leaf to the actual certificate such that
// we don't have to do the parsing (multiple times) when matching the
// certificate to the client hello. This a performance optimisation.
if certificate.Leaf == nil {
certificate.Leaf, err = x509.ParseCertificate(certificate.Certificate[0])
if err != nil {
return err
}
}
p := pair{
CertFile: certFile,
KeyFile: keyFile,
}
m.lock.Lock()
defer m.lock.Unlock()
// We don't allow IP SANs in certificates - except for the "default" certificate
// which is, by convention, the first certificate added to the manager. The problem
// with allowing IP SANs in more than one certificate is that the manager usually can't
// match the client SNI to a SAN since the SNI is meant to communicate the destination
// host name and clients will not set the SNI to an IP address.
// Allowing multiple certificates with IP SANs lead to errors that confuses users - like:
// "It works for `https://instance.minio.local` but not for `https://10.0.2.1`"
if len(m.certificates) > 0 && len(certificate.Leaf.IPAddresses) > 0 {
return errors.New("cert: certificate must not contain any IP SANs: only the default certificate may contain IP SANs")
}
m.certificates[p] = &certificate
if certFileIsLink && keyFileIsLink || isk8s {
go m.watchSymlinks(p, m.reloader())
} else {
// Windows doesn't allow for watching file changes but instead allows
// for directory changes only, while we can still watch for changes
// on files on other platforms. Watch parent directory on all platforms
// for simplicity.
events := make(chan notify.EventInfo, 1)
if err = notify.Watch(filepath.Dir(certFile), events, eventWrite...); err != nil {
return err
}
if err = notify.Watch(filepath.Dir(keyFile), events, eventWrite...); err != nil {
return err
}
go m.watchFileEvents(p, events, m.reloader())
}
return nil
}
// reloader creates and registers a reloader.
// m must be locked when called.
func (m *Manager) reloader() <-chan struct{} {
ch := make(chan struct{}, 1)
m.reloadCerts = append(m.reloadCerts, ch)
return ch
}
// ReloadOnSignal specifies one or more signals that will trigger certificates reloading.
// If called multiple times with the same signal certificates
func (m *Manager) ReloadOnSignal(sig ...os.Signal) {
if len(sig) == 0 {
return
}
ch := make(chan os.Signal, 1)
signal.Notify(ch, sig...)
go func() {
for {
select {
case <-m.done:
signal.Stop(ch)
return
case <-ch:
m.ReloadCerts()
}
}
}()
}
// ReloadCerts will forcefully reload all certs.
func (m *Manager) ReloadCerts() {
m.lock.RLock()
defer m.lock.RUnlock()
for _, ch := range m.reloadCerts {
// Non-blocking
select {
case ch <- struct{}{}:
default:
}
}
}
// watchSymlinks starts an endless loop reloading the
// certFile and keyFile periodically.
func (m *Manager) watchSymlinks(watch pair, reload <-chan struct{}) {
t := time.NewTimer(m.duration)
defer t.Stop()
for {
select {
case <-m.done:
return // Once stopped exits this routine.
case <-t.C:
case <-reload:
}
t.Reset(m.duration) // Reset timer for new duration
certificate, err := m.loadX509KeyPair(watch.CertFile, watch.KeyFile)
if err != nil {
continue
}
if certificate.Leaf == nil { // This is a performance optimisation
certificate.Leaf, err = x509.ParseCertificate(certificate.Certificate[0])
if err != nil {
continue
}
}
m.lock.Lock()
m.certificates[watch] = &certificate
m.lock.Unlock()
}
}
// watchFileEvents starts an endless loop waiting for file systems events.
// Once an event occurs it reloads the private key and certificate that
// has changed, if any.
func (m *Manager) watchFileEvents(watch pair, events chan notify.EventInfo, reload <-chan struct{}) {
for {
select {
case <-m.done:
return
case event := <-events:
if !isWriteEvent(event.Event()) {
continue
}
p := event.Path()
if watch.KeyFile != p && watch.CertFile != p {
continue
}
case <-reload:
}
// Do reload
certificate, err := m.loadX509KeyPair(watch.CertFile, watch.KeyFile)
if err != nil {
continue
}
if certificate.Leaf == nil { // This is performance optimisation
certificate.Leaf, err = x509.ParseCertificate(certificate.Certificate[0])
if err != nil {
continue
}
}
m.lock.Lock()
m.certificates[watch] = &certificate
m.lock.Unlock()
}
}
// GetCertificate returns a TLS certificate based on the client hello.
//
// It tries to find a certificate that would be accepted by the client
// according to the client hello. However, if no certificate can be
// found GetCertificate returns the certificate loaded from the
// Public file.
func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
m.lock.RLock()
defer m.lock.RUnlock()
// If the client does not send a SNI we return the "default"
// certificate. A client may not send a SNI - e.g. when trying
// to connect to an IP directly (https://:).
//
// In this case we don't know which the certificate the client
// asks for. It may be a public-facing certificate issued by a
// public CA or an internal certificate containing internal domain
// names.
// Now, we should not serve "the first" certificate that would be
// accepted by the client based on the Client Hello. Otherwise, we
// may expose an internal certificate to the client that contains
// internal domain names. That way we would disclose internal
// infrastructure details.
//
// Therefore, we serve the "default" certificate - which by convention
// is the first certificate added to the Manager. It's the calling code's
// responsibility to ensure that the "public-facing" certificate is used
// when creating a Manager instance.
if hello.ServerName == "" {
certificate := m.certificates[m.defaultCert]
return certificate, nil
}
// Optimization: If there is just one certificate, always serve that one.
if len(m.certificates) == 1 {
for _, certificate := range m.certificates {
return certificate, nil
}
}
// Iterate over all certificates and return the first one that would
// be accepted by the peer (TLS client) based on the client hello.
// In particular, the client usually specifies the requested host/domain
// via SNI.
//
// Note: The certificate.Leaf should be non-nil and contain the actual
// client certificate of MinIO that should be presented to the peer (TLS client).
// Otherwise, the leaf certificate has to be parsed again - which is kind of
// expensive and may cause a performance issue. For more information, check the
// docs of tls.ClientHelloInfo.SupportsCertificate.
for _, certificate := range m.certificates {
if err := hello.SupportsCertificate(certificate); err == nil {
return certificate, nil
}
}
return nil, errors.New("certs: no server certificate is supported by peer")
}
// GetClientCertificate returns a TLS certificate for mTLS based on the
// certificate request.
//
// It tries to find a certificate that would be accepted by the server
// according to the certificate request. However, if no certificate can be
// found GetClientCertificate returns the certificate loaded from the
// Public file.
func (m *Manager) GetClientCertificate(reqInfo *tls.CertificateRequestInfo) (*tls.Certificate, error) {
m.lock.RLock()
defer m.lock.RUnlock()
// Optimization: If there is just one certificate, always serve that one.
if len(m.certificates) == 1 {
for _, certificate := range m.certificates {
return certificate, nil
}
}
// Iterate over all certificates and return the first one that would
// be accepted by the peer (TLS server) based on reqInfo.
//
// Note: The certificate.Leaf should be non-nil and contain the actual
// client certificate of MinIO that should be presented to the peer (TLS server).
// Otherwise, the leaf certificate has to be parsed again - which is kind of
// expensive and may cause a performance issue. For more information, check the
// docs of tls.CertificateRequestInfo.SupportsCertificate.
for _, certificate := range m.certificates {
if err := reqInfo.SupportsCertificate(certificate); err == nil {
return certificate, nil
}
}
return nil, errors.New("certs: no client certificate is supported by peer")
}
// isSymlink returns true if the given file
// is a symbolic link.
func isSymlink(file string) (bool, error) {
st, err := os.Lstat(file)
if err != nil {
return false, err
}
return st.Mode()&os.ModeSymlink == os.ModeSymlink, nil
}
golang-github-minio-pkg-3.0.10/certs/new-private.key 0000664 0000000 0000000 00000003250 14655770405 0022322 0 ustar 00root root 0000000 0000000 -----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD4tnz56oMfKgK3
scZAiFkr5lqAPFfMnrtrZXzyz+scHfOGLV3qFrqrmxyWOYI2qPbnR+NJakNDiGyx
+rI0OZj8hRqmGdP14wE9lfpFdlUVca2lRQW8aKTmF53aDuPkmHL6W7PlAef3NHoT
uuSpM+XdYlnZ97gsR+KPr9vNnYIxQWsrK3+6qfiVpOWbgM77COg5QYAeZoduSmSF
hNv+ptbNXSoyHRnLiBC4VhwFVsBNx0CNXhtE2Qd3QIsSR3cD1p7llp46uDYeQ9Vy
KfGDWWfmMF7YsGoMWoqRa53ObqN43D6gTiUorEN8cfwAPTw3yoVt+zVjFkCMvfAq
lxhXDanNAgMBAAECggEBAIGAI5rNbPCxIzEas6uuUx/0lXLn+J9mlxfYhDK56CV/
wuk+fgQBSblIzp252/8yAz1xxPrZBaUIR/B0JI3k36+8bp/GGwOQ63hxuxqn/q1n
v46qXc44foQAEAUWc7r3Vgbd8NFxKKMjA916Fs2zZCDdsQM5ZQBJfcJrQvvQ45VY
//UtXdNeIBQOb5Wg4o9fHJolKzCHWRaD2ExoIHZ5Fa6JpBmk9JBHcUbrHrlbOeep
/SkbSa0ma9j3k3jqV970XRoQUCJf+K1Li49jmaYPPGXBUAp6AfU+yiAJ1aups38m
BClLAV9g6vgE3xK2xozGPI1+j9lkruYbvGbPNkXexdECgYEA/47XnKITSnxtV+NK
nDbWNOgpeaRbxAdjp1P0b4VI0S0SuRvKUOCp1UlPg5BjGL0JLPQpGlPzEfLlGWAa
68vhyj0V6HL2+PAJNib1eu6yyRBsSbPdrAD5nydHpbxRcdShhVwb2MHMyBeYH5Al
kL+ed5wCF32kXOOGzhoGzJEKNEcCgYEA+SSdcdbuVpQFkAecIoABwdx/qeOAeS19
FsvVSTmWlhal8m2Mn8RWZ0IKXT9AoZJ0KQBIKHViPtyV7UQey05uRgLRHZapHpe8
dhm6SsGYtU3BhLdHJBP0kI79qm2kzqsHp6ghSzaxT9CkRfMniN+TD+w8p7lrOaxv
vV46UHoGX0sCgYB4LlCvVHkF+mXhgv4/YHpz/woiLm0JTwBKXG0DVQbdd/jqHGuU
hVLY/tTp5ij0JVH/VgNOYlRZCIU83blLUmIonXmECyyh/SAX21JuMXram2KRdoi0
rvC1K9/BzUHv6jLbaGmgEeOf5Zign0VLQRHg5fkF2wxEsqtemVbBNSQ7WQKBgBFk
Y/VRervig2zlixnBc93zpZnXft12tnfD7PS6p298z0LYMOvqSdnVe2G9C6b70U4X
bfIdF6mpvnGcwsWQiRQsGCsHnHC9SPO5og6b6ywk7HB2VuoG1pjM0pp2Iv4mZFdo
3kIg5EndF8qmSck9SkffRvCyefDBv98pV8rMaet3AoGBALjlN2hLoNE5Cs5vTYH8
W0AN4lEOaTlBRKG8a1h7Fm2vPgzGGkiwU6bVzsh0oTfytc8v8MW9lNQZpE3dBKne
ms3FrNsnBbTczX+xJmndRnVRocdyON6u476VxAuz/dHSFFnZGXX+2lJse9xnWHUz
OpSHUPq3TrUzhgZClE2ZKpNm
-----END PRIVATE KEY-----
golang-github-minio-pkg-3.0.10/certs/new-public.crt 0000664 0000000 0000000 00000002321 14655770405 0022124 0 ustar 00root root 0000000 0000000 -----BEGIN CERTIFICATE-----
MIIDYDCCAkigAwIBAgIJALIHkFXjtZ2yMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTgwNTIwMDg1MzI3WhcNMTkwNTIwMDg1MzI3WjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEA+LZ8+eqDHyoCt7HGQIhZK+ZagDxXzJ67a2V88s/rHB3zhi1d6ha6q5sc
ljmCNqj250fjSWpDQ4hssfqyNDmY/IUaphnT9eMBPZX6RXZVFXGtpUUFvGik5hed
2g7j5Jhy+luz5QHn9zR6E7rkqTPl3WJZ2fe4LEfij6/bzZ2CMUFrKyt/uqn4laTl
m4DO+wjoOUGAHmaHbkpkhYTb/qbWzV0qMh0Zy4gQuFYcBVbATcdAjV4bRNkHd0CL
Ekd3A9ae5ZaeOrg2HkPVcinxg1ln5jBe2LBqDFqKkWudzm6jeNw+oE4lKKxDfHH8
AD08N8qFbfs1YxZAjL3wKpcYVw2pzQIDAQABo1MwUTAdBgNVHQ4EFgQU2Yywgv8p
WfyZxYVx+MnH+VQ5TTUwHwYDVR0jBBgwFoAU2Yywgv8pWfyZxYVx+MnH+VQ5TTUw
DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEA2maF7DQ7CMpCho9B
9gjGxvt8HqY1pCyuQwcSPb4PTyoKUZ/ZuIDhVOaBX+ox1RzlfGtYs2BUM63/QUDs
dP0GO7/IL/XEqJi1flrFvM7LNSs89qAbPJ440m6jJDzsuL2VeyUX/M72IEsBK2uS
ajtS1+HFQjPMvt7wR6fDPCP7wHPOrkTN4hcHlgzVJShKUnFaHtb2lOnWaoM/Sk91
IsiyAhKRuCM9et7/bnOj7G8448QDVtQNniT8V/HpqQ7ltSuIGvs3QYTLDTege/74
Q8Ph1oH7shyRE/PqPfyIuLq3p0N9Sah3oRMHLohYjJL0zAGt0jxSsnhrBSNUUD/v
bAd5VQ==
-----END CERTIFICATE-----
golang-github-minio-pkg-3.0.10/certs/original-private.key 0000664 0000000 0000000 00000003250 14655770405 0023335 0 ustar 00root root 0000000 0000000 -----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDPszxaYwn+mIz6
IGuUlmvWwUs/yWTH4MC17qey2N5MqcxlfIWHUugcBsbGhi/e1druFW0s7YGMxp+G
+Q1IezxX+VmVaJCN8AgSowbYgpRdpRQ+mhGeQby0JcvO16fyPnUJBz3GGel2bcK8
fcQyT0TVapCiD9oURVmdvDSsRXz+EoPlOve8AWciHHgm1ItO5qdPRP5YtcJfLiwK
noYnpda2d9SzmYk+Q2JFArooF7/A1DYz9bXCMo3qp0gQlMpSMDR+MCbxHBzBBr+f
QG8QdDrzWQ2slhniBhFDk0LuPCBLlSeIzkp+DoAGDXf3hWYhechlabZ7nfngg5er
Ez776WCFAgMBAAECggEBAJcHRyCWmcLm3MRY5MF0K9BKV9R3NnBdTuQ8OPdE2Ui3
w6gcRuBi+eK/TrU3CAIqUXsEW5Hq1mQuXfwAh5cn/XYfG/QXx91eKBCdOTIgqY/6
pODsmVkRhg0c2rl6eWYd4m6BNHsjhm8WWx9C+HJ4z528UpV1n2dUElkvbMHD+aKp
Ndwd0W+0PCn/BjMn/sdyy01f8sfaK2Zoy7HBw/fGeBDNLFFj3Iz7BqXYeS+OyfLN
B4xD5I5fFqt1iJeyqVPzGkOAYSqisijbM1GtZJCeVp37/+IDylCKTO3l8Xd8x73U
qTYcYT3heSHyUC2xCM6Va2YkSrOHeqbq91QgHh9LVrUCgYEA9t/wE2S8TE2l1IG9
68SXdhyaXTnB2qSL7ggY0uazPzBNLQpNMOxicZ6/4QGEi3hSuCqGxxGo9UEoTsVd
pk8oIeDULdPVi4NQxSmkxUyArs/dzOMygUPyosOiEc8z6jWFFKDcQ7mnZnay8dZ4
e4j+/hZDONtDrJ+zH2xu98ZrJPcCgYEA12CbSRbCkTiRj/dq8Qvgp6+ceTVcAbnk
MWpAhZQaXHrG3XP0L7QTIHG/7a09Mln92zjuAFXDp/Vc5NdxeXcnj9j6oUAxq+0I
dq+vibzjROemmvnmQvXGY9tc0ns6u7GjM0+Sicmas+IH4vuum/aRasABfVe2XBwe
4fVs0n7yU2MCgYA7KevFGg0uVCV7yiQTzqdlvPEZim/00B5gyzv3vyYR7KdyNdfN
87ib9imR6OU0738Td82ZA5h0PktEpXQOGUZK6DCxUuUIbE39Ej/UsMLeIh7LrV87
L2eErlG25utQI8di7DIdYO7HVYcJAhcZs/k4N2mgxJtxUUyCKWBmrPycfQKBgAo7
0uUUKcaQs4ntra0qbVBKbdrsiCSk2ozmiY5PTTlbtBtNqSqjGc2O2hnHA4Ni90b1
W4m0iYlvhSxyeDfXS4/wNWh4DmQm7SIGkwaubPYXM7llamWAHB8eiziNFmtYs3J6
s3HMnIczlEBayR8sBhjWaruz8TxLMcR2zubplUYVAoGBAItxeC9IT8BGJoZB++qM
f2LXCqJ383x0sDHhwPMFPtwUTzAwc5BJgQe9zFktW5CBxsER+MnUZjlrarT1HQfH
1Y1mJQXtwuBKG4pPPZphH0yoVlYcWkBTMw/KmlVlwRclEzRQwV3TPD+i6ieKeZhz
9eZwhS3H+Zb/693WbBDyH8L+
-----END PRIVATE KEY-----
golang-github-minio-pkg-3.0.10/certs/original-public.crt 0000664 0000000 0000000 00000002462 14655770405 0023145 0 ustar 00root root 0000000 0000000 -----BEGIN CERTIFICATE-----
MIIDqjCCApKgAwIBAgIJAOcv4FsrflS4MA0GCSqGSIb3DQEBCwUAMGoxCzAJBgNV
BAYTAlVTMQswCQYDVQQIDAJDQTEVMBMGA1UEBwwMUmVkd29vZCBDaXR5MQ4wDAYD
VQQKDAVNaW5pbzEUMBIGA1UECwwLRW5naW5lZXJpbmcxETAPBgNVBAMMCG1pbmlv
LmlvMB4XDTE4MDUyMDA4NDc0MFoXDTE5MDUyMDA4NDc0MFowajELMAkGA1UEBhMC
VVMxCzAJBgNVBAgMAkNBMRUwEwYDVQQHDAxSZWR3b29kIENpdHkxDjAMBgNVBAoM
BU1pbmlvMRQwEgYDVQQLDAtFbmdpbmVlcmluZzERMA8GA1UEAwwIbWluaW8uaW8w
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPszxaYwn+mIz6IGuUlmvW
wUs/yWTH4MC17qey2N5MqcxlfIWHUugcBsbGhi/e1druFW0s7YGMxp+G+Q1IezxX
+VmVaJCN8AgSowbYgpRdpRQ+mhGeQby0JcvO16fyPnUJBz3GGel2bcK8fcQyT0TV
apCiD9oURVmdvDSsRXz+EoPlOve8AWciHHgm1ItO5qdPRP5YtcJfLiwKnoYnpda2
d9SzmYk+Q2JFArooF7/A1DYz9bXCMo3qp0gQlMpSMDR+MCbxHBzBBr+fQG8QdDrz
WQ2slhniBhFDk0LuPCBLlSeIzkp+DoAGDXf3hWYhechlabZ7nfngg5erEz776WCF
AgMBAAGjUzBRMB0GA1UdDgQWBBRzC09a+3AlbFDg6BsvELolmO8jYjAfBgNVHSME
GDAWgBRzC09a+3AlbFDg6BsvELolmO8jYjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG
SIb3DQEBCwUAA4IBAQBl0cx7qbidKjhoZ1Iv4pCD8xHZgtuWEDApPoGuMtVS66jJ
+oj0ncD5xCtv9XqXtshE65FIsEWnDOIwa+kyjMnxHbFwxveWBT4W0twtqwbVs7NE
I0So6cEmSx4+rB0XorY6mIbD3O9YAStelNhB1jVfQfIMSByYkcGq2Fh+B1LHlOrz
06LJdwYMiILzK0c5fvjZvsDq/9EK+Xo66hphKjs5cl1t9WK7wKOCoZDt2lOTZqEq
UWYGPWlTAxSWQxO4WnvSKqFdsRi8fOO3KlDq1eNqeDSGGCI0DTGgJxidHIpfOPEF
s/zojgc5npE32/1n8og6gLcv7LIKelBfMhUrFTp7
-----END CERTIFICATE-----
golang-github-minio-pkg-3.0.10/certs/private.key 0000664 0000000 0000000 00000003250 14655770405 0021533 0 ustar 00root root 0000000 0000000 -----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDPszxaYwn+mIz6
IGuUlmvWwUs/yWTH4MC17qey2N5MqcxlfIWHUugcBsbGhi/e1druFW0s7YGMxp+G
+Q1IezxX+VmVaJCN8AgSowbYgpRdpRQ+mhGeQby0JcvO16fyPnUJBz3GGel2bcK8
fcQyT0TVapCiD9oURVmdvDSsRXz+EoPlOve8AWciHHgm1ItO5qdPRP5YtcJfLiwK
noYnpda2d9SzmYk+Q2JFArooF7/A1DYz9bXCMo3qp0gQlMpSMDR+MCbxHBzBBr+f
QG8QdDrzWQ2slhniBhFDk0LuPCBLlSeIzkp+DoAGDXf3hWYhechlabZ7nfngg5er
Ez776WCFAgMBAAECggEBAJcHRyCWmcLm3MRY5MF0K9BKV9R3NnBdTuQ8OPdE2Ui3
w6gcRuBi+eK/TrU3CAIqUXsEW5Hq1mQuXfwAh5cn/XYfG/QXx91eKBCdOTIgqY/6
pODsmVkRhg0c2rl6eWYd4m6BNHsjhm8WWx9C+HJ4z528UpV1n2dUElkvbMHD+aKp
Ndwd0W+0PCn/BjMn/sdyy01f8sfaK2Zoy7HBw/fGeBDNLFFj3Iz7BqXYeS+OyfLN
B4xD5I5fFqt1iJeyqVPzGkOAYSqisijbM1GtZJCeVp37/+IDylCKTO3l8Xd8x73U
qTYcYT3heSHyUC2xCM6Va2YkSrOHeqbq91QgHh9LVrUCgYEA9t/wE2S8TE2l1IG9
68SXdhyaXTnB2qSL7ggY0uazPzBNLQpNMOxicZ6/4QGEi3hSuCqGxxGo9UEoTsVd
pk8oIeDULdPVi4NQxSmkxUyArs/dzOMygUPyosOiEc8z6jWFFKDcQ7mnZnay8dZ4
e4j+/hZDONtDrJ+zH2xu98ZrJPcCgYEA12CbSRbCkTiRj/dq8Qvgp6+ceTVcAbnk
MWpAhZQaXHrG3XP0L7QTIHG/7a09Mln92zjuAFXDp/Vc5NdxeXcnj9j6oUAxq+0I
dq+vibzjROemmvnmQvXGY9tc0ns6u7GjM0+Sicmas+IH4vuum/aRasABfVe2XBwe
4fVs0n7yU2MCgYA7KevFGg0uVCV7yiQTzqdlvPEZim/00B5gyzv3vyYR7KdyNdfN
87ib9imR6OU0738Td82ZA5h0PktEpXQOGUZK6DCxUuUIbE39Ej/UsMLeIh7LrV87
L2eErlG25utQI8di7DIdYO7HVYcJAhcZs/k4N2mgxJtxUUyCKWBmrPycfQKBgAo7
0uUUKcaQs4ntra0qbVBKbdrsiCSk2ozmiY5PTTlbtBtNqSqjGc2O2hnHA4Ni90b1
W4m0iYlvhSxyeDfXS4/wNWh4DmQm7SIGkwaubPYXM7llamWAHB8eiziNFmtYs3J6
s3HMnIczlEBayR8sBhjWaruz8TxLMcR2zubplUYVAoGBAItxeC9IT8BGJoZB++qM
f2LXCqJ383x0sDHhwPMFPtwUTzAwc5BJgQe9zFktW5CBxsER+MnUZjlrarT1HQfH
1Y1mJQXtwuBKG4pPPZphH0yoVlYcWkBTMw/KmlVlwRclEzRQwV3TPD+i6ieKeZhz
9eZwhS3H+Zb/693WbBDyH8L+
-----END PRIVATE KEY-----
golang-github-minio-pkg-3.0.10/certs/public.crt 0000664 0000000 0000000 00000002462 14655770405 0021343 0 ustar 00root root 0000000 0000000 -----BEGIN CERTIFICATE-----
MIIDqjCCApKgAwIBAgIJAOcv4FsrflS4MA0GCSqGSIb3DQEBCwUAMGoxCzAJBgNV
BAYTAlVTMQswCQYDVQQIDAJDQTEVMBMGA1UEBwwMUmVkd29vZCBDaXR5MQ4wDAYD
VQQKDAVNaW5pbzEUMBIGA1UECwwLRW5naW5lZXJpbmcxETAPBgNVBAMMCG1pbmlv
LmlvMB4XDTE4MDUyMDA4NDc0MFoXDTE5MDUyMDA4NDc0MFowajELMAkGA1UEBhMC
VVMxCzAJBgNVBAgMAkNBMRUwEwYDVQQHDAxSZWR3b29kIENpdHkxDjAMBgNVBAoM
BU1pbmlvMRQwEgYDVQQLDAtFbmdpbmVlcmluZzERMA8GA1UEAwwIbWluaW8uaW8w
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPszxaYwn+mIz6IGuUlmvW
wUs/yWTH4MC17qey2N5MqcxlfIWHUugcBsbGhi/e1druFW0s7YGMxp+G+Q1IezxX
+VmVaJCN8AgSowbYgpRdpRQ+mhGeQby0JcvO16fyPnUJBz3GGel2bcK8fcQyT0TV
apCiD9oURVmdvDSsRXz+EoPlOve8AWciHHgm1ItO5qdPRP5YtcJfLiwKnoYnpda2
d9SzmYk+Q2JFArooF7/A1DYz9bXCMo3qp0gQlMpSMDR+MCbxHBzBBr+fQG8QdDrz
WQ2slhniBhFDk0LuPCBLlSeIzkp+DoAGDXf3hWYhechlabZ7nfngg5erEz776WCF
AgMBAAGjUzBRMB0GA1UdDgQWBBRzC09a+3AlbFDg6BsvELolmO8jYjAfBgNVHSME
GDAWgBRzC09a+3AlbFDg6BsvELolmO8jYjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG
SIb3DQEBCwUAA4IBAQBl0cx7qbidKjhoZ1Iv4pCD8xHZgtuWEDApPoGuMtVS66jJ
+oj0ncD5xCtv9XqXtshE65FIsEWnDOIwa+kyjMnxHbFwxveWBT4W0twtqwbVs7NE
I0So6cEmSx4+rB0XorY6mIbD3O9YAStelNhB1jVfQfIMSByYkcGq2Fh+B1LHlOrz
06LJdwYMiILzK0c5fvjZvsDq/9EK+Xo66hphKjs5cl1t9WK7wKOCoZDt2lOTZqEq
UWYGPWlTAxSWQxO4WnvSKqFdsRi8fOO3KlDq1eNqeDSGGCI0DTGgJxidHIpfOPEF
s/zojgc5npE32/1n8og6gLcv7LIKelBfMhUrFTp7
-----END CERTIFICATE-----
golang-github-minio-pkg-3.0.10/certs/server.crt 0000664 0000000 0000000 00000002462 14655770405 0021373 0 ustar 00root root 0000000 0000000 -----BEGIN CERTIFICATE-----
MIIDqjCCApKgAwIBAgIJAOcv4FsrflS4MA0GCSqGSIb3DQEBCwUAMGoxCzAJBgNV
BAYTAlVTMQswCQYDVQQIDAJDQTEVMBMGA1UEBwwMUmVkd29vZCBDaXR5MQ4wDAYD
VQQKDAVNaW5pbzEUMBIGA1UECwwLRW5naW5lZXJpbmcxETAPBgNVBAMMCG1pbmlv
LmlvMB4XDTE4MDUyMDA4NDc0MFoXDTE5MDUyMDA4NDc0MFowajELMAkGA1UEBhMC
VVMxCzAJBgNVBAgMAkNBMRUwEwYDVQQHDAxSZWR3b29kIENpdHkxDjAMBgNVBAoM
BU1pbmlvMRQwEgYDVQQLDAtFbmdpbmVlcmluZzERMA8GA1UEAwwIbWluaW8uaW8w
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPszxaYwn+mIz6IGuUlmvW
wUs/yWTH4MC17qey2N5MqcxlfIWHUugcBsbGhi/e1druFW0s7YGMxp+G+Q1IezxX
+VmVaJCN8AgSowbYgpRdpRQ+mhGeQby0JcvO16fyPnUJBz3GGel2bcK8fcQyT0TV
apCiD9oURVmdvDSsRXz+EoPlOve8AWciHHgm1ItO5qdPRP5YtcJfLiwKnoYnpda2
d9SzmYk+Q2JFArooF7/A1DYz9bXCMo3qp0gQlMpSMDR+MCbxHBzBBr+fQG8QdDrz
WQ2slhniBhFDk0LuPCBLlSeIzkp+DoAGDXf3hWYhechlabZ7nfngg5erEz776WCF
AgMBAAGjUzBRMB0GA1UdDgQWBBRzC09a+3AlbFDg6BsvELolmO8jYjAfBgNVHSME
GDAWgBRzC09a+3AlbFDg6BsvELolmO8jYjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG
SIb3DQEBCwUAA4IBAQBl0cx7qbidKjhoZ1Iv4pCD8xHZgtuWEDApPoGuMtVS66jJ
+oj0ncD5xCtv9XqXtshE65FIsEWnDOIwa+kyjMnxHbFwxveWBT4W0twtqwbVs7NE
I0So6cEmSx4+rB0XorY6mIbD3O9YAStelNhB1jVfQfIMSByYkcGq2Fh+B1LHlOrz
06LJdwYMiILzK0c5fvjZvsDq/9EK+Xo66hphKjs5cl1t9WK7wKOCoZDt2lOTZqEq
UWYGPWlTAxSWQxO4WnvSKqFdsRi8fOO3KlDq1eNqeDSGGCI0DTGgJxidHIpfOPEF
s/zojgc5npE32/1n8og6gLcv7LIKelBfMhUrFTp7
-----END CERTIFICATE-----
golang-github-minio-pkg-3.0.10/certs/server.key 0000664 0000000 0000000 00000003250 14655770405 0021367 0 ustar 00root root 0000000 0000000 -----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDPszxaYwn+mIz6
IGuUlmvWwUs/yWTH4MC17qey2N5MqcxlfIWHUugcBsbGhi/e1druFW0s7YGMxp+G
+Q1IezxX+VmVaJCN8AgSowbYgpRdpRQ+mhGeQby0JcvO16fyPnUJBz3GGel2bcK8
fcQyT0TVapCiD9oURVmdvDSsRXz+EoPlOve8AWciHHgm1ItO5qdPRP5YtcJfLiwK
noYnpda2d9SzmYk+Q2JFArooF7/A1DYz9bXCMo3qp0gQlMpSMDR+MCbxHBzBBr+f
QG8QdDrzWQ2slhniBhFDk0LuPCBLlSeIzkp+DoAGDXf3hWYhechlabZ7nfngg5er
Ez776WCFAgMBAAECggEBAJcHRyCWmcLm3MRY5MF0K9BKV9R3NnBdTuQ8OPdE2Ui3
w6gcRuBi+eK/TrU3CAIqUXsEW5Hq1mQuXfwAh5cn/XYfG/QXx91eKBCdOTIgqY/6
pODsmVkRhg0c2rl6eWYd4m6BNHsjhm8WWx9C+HJ4z528UpV1n2dUElkvbMHD+aKp
Ndwd0W+0PCn/BjMn/sdyy01f8sfaK2Zoy7HBw/fGeBDNLFFj3Iz7BqXYeS+OyfLN
B4xD5I5fFqt1iJeyqVPzGkOAYSqisijbM1GtZJCeVp37/+IDylCKTO3l8Xd8x73U
qTYcYT3heSHyUC2xCM6Va2YkSrOHeqbq91QgHh9LVrUCgYEA9t/wE2S8TE2l1IG9
68SXdhyaXTnB2qSL7ggY0uazPzBNLQpNMOxicZ6/4QGEi3hSuCqGxxGo9UEoTsVd
pk8oIeDULdPVi4NQxSmkxUyArs/dzOMygUPyosOiEc8z6jWFFKDcQ7mnZnay8dZ4
e4j+/hZDONtDrJ+zH2xu98ZrJPcCgYEA12CbSRbCkTiRj/dq8Qvgp6+ceTVcAbnk
MWpAhZQaXHrG3XP0L7QTIHG/7a09Mln92zjuAFXDp/Vc5NdxeXcnj9j6oUAxq+0I
dq+vibzjROemmvnmQvXGY9tc0ns6u7GjM0+Sicmas+IH4vuum/aRasABfVe2XBwe
4fVs0n7yU2MCgYA7KevFGg0uVCV7yiQTzqdlvPEZim/00B5gyzv3vyYR7KdyNdfN
87ib9imR6OU0738Td82ZA5h0PktEpXQOGUZK6DCxUuUIbE39Ej/UsMLeIh7LrV87
L2eErlG25utQI8di7DIdYO7HVYcJAhcZs/k4N2mgxJtxUUyCKWBmrPycfQKBgAo7
0uUUKcaQs4ntra0qbVBKbdrsiCSk2ozmiY5PTTlbtBtNqSqjGc2O2hnHA4Ni90b1
W4m0iYlvhSxyeDfXS4/wNWh4DmQm7SIGkwaubPYXM7llamWAHB8eiziNFmtYs3J6
s3HMnIczlEBayR8sBhjWaruz8TxLMcR2zubplUYVAoGBAItxeC9IT8BGJoZB++qM
f2LXCqJ383x0sDHhwPMFPtwUTzAwc5BJgQe9zFktW5CBxsER+MnUZjlrarT1HQfH
1Y1mJQXtwuBKG4pPPZphH0yoVlYcWkBTMw/KmlVlwRclEzRQwV3TPD+i6ieKeZhz
9eZwhS3H+Zb/693WbBDyH8L+
-----END PRIVATE KEY-----
golang-github-minio-pkg-3.0.10/console/ 0000775 0000000 0000000 00000000000 14655770405 0017671 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/console/console.go 0000664 0000000 0000000 00000033135 14655770405 0021667 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
// Package console implements console printing helpers
package console
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"sync"
"github.com/fatih/color"
"github.com/mattn/go-colorable"
"github.com/mattn/go-isatty"
)
var (
// Used by the caller to print multiple lines atomically. Exposed by Lock/Unlock methods.
publicMutex sync.Mutex
// Used internally by console.
privateMutex sync.Mutex
stderrColoredOutput = colorable.NewColorableStderr()
// Print prints a message.
Print = func(data ...interface{}) {
consolePrint("Print", getThemeColor("Print"), data...)
}
// PrintC prints a message with color.
PrintC = func(data ...interface{}) {
consolePrint("PrintC", getThemeColor("PrintC"), data...)
}
// Printf prints a formatted message.
Printf = func(format string, data ...interface{}) {
consolePrintf("Print", getThemeColor("Print"), format, data...)
}
// Println prints a message with a newline.
Println = func(data ...interface{}) {
consolePrintln("Print", getThemeColor("Print"), data...)
}
// Fatal print a error message and exit.
Fatal = func(data ...interface{}) {
consolePrint("Fatal", getThemeColor("Fatal"), data...)
os.Exit(1)
}
// Fatalf print a error message with a format specified and exit.
Fatalf = func(format string, data ...interface{}) {
consolePrintf("Fatal", getThemeColor("Fatal"), format, data...)
os.Exit(1)
}
// Fatalln print a error message with a new line and exit.
Fatalln = func(data ...interface{}) {
consolePrintln("Fatal", getThemeColor("Fatal"), data...)
os.Exit(1)
}
// Error prints a error message.
Error = func(data ...interface{}) {
consolePrint("Error", getThemeColor("Error"), data...)
}
// Errorf print a error message with a format specified.
Errorf = func(format string, data ...interface{}) {
consolePrintf("Error", getThemeColor("Error"), format, data...)
}
// Errorln prints a error message with a new line.
Errorln = func(data ...interface{}) {
consolePrintln("Error", getThemeColor("Error"), data...)
}
// Info prints a informational message.
Info = func(data ...interface{}) {
consolePrint("Info", getThemeColor("Info"), data...)
}
// Infof prints a informational message in custom format.
Infof = func(format string, data ...interface{}) {
consolePrintf("Info", getThemeColor("Info"), format, data...)
}
// Infoln prints a informational message with a new line.
Infoln = func(data ...interface{}) {
consolePrintln("Info", getThemeColor("Info"), data...)
}
// Debug prints a debug message without a new line
// Debug prints a debug message.
Debug = func(data ...interface{}) {
consolePrint("Debug", getThemeColor("Debug"), data...)
}
// Debugf prints a debug message with a new line.
Debugf = func(format string, data ...interface{}) {
consolePrintf("Debug", getThemeColor("Debug"), format, data...)
}
// Debugln prints a debug message with a new line.
Debugln = func(data ...interface{}) {
consolePrintln("Debug", getThemeColor("Debug"), data...)
}
// Colorize prints message in a colorized form, dictated by the corresponding tag argument.
Colorize = func(tag string, data interface{}) string {
if isatty.IsTerminal(os.Stdout.Fd()) {
colorized := getThemeColor(tag)
if colorized != nil {
return colorized.SprintFunc()(data)
} // else: No theme found. Return as string.
}
return fmt.Sprint(data)
}
// Eraseline Print in new line and adjust to top so that we don't print over the ongoing progress bar.
Eraseline = func() {
consolePrintf("Print", getThemeColor("Print"), "%c[2K\n", 27)
consolePrintf("Print", getThemeColor("Print"), "%c[A", 27)
}
)
// wrap around standard fmt functions.
// consolePrint prints a message prefixed with message type and program name.
func consolePrint(tag string, c *color.Color, a ...interface{}) {
privateMutex.Lock()
defer privateMutex.Unlock()
switch tag {
case "Debug":
// if no arguments are given do not invoke debug printer.
if len(a) == 0 {
return
}
output := color.Output
color.Output = stderrColoredOutput
if isatty.IsTerminal(os.Stderr.Fd()) {
c.Print(ProgramName() + ": ")
c.Print(a...)
} else {
fmt.Fprint(color.Output, ProgramName()+": ")
fmt.Fprint(color.Output, a...)
}
color.Output = output
case "Fatal":
fallthrough
case "Error":
// if no arguments are given do not invoke fatal and error printer.
if len(a) == 0 {
return
}
output := color.Output
color.Output = stderrColoredOutput
if isatty.IsTerminal(os.Stderr.Fd()) {
c.Print(ProgramName() + ": ")
c.Print(a...)
} else {
fmt.Fprint(color.Output, ProgramName()+": ")
fmt.Fprint(color.Output, a...)
}
color.Output = output
case "Info":
// if no arguments are given do not invoke info printer.
if len(a) == 0 {
return
}
if isatty.IsTerminal(os.Stdout.Fd()) {
c.Print(ProgramName() + ": ")
c.Print(a...)
} else {
fmt.Fprint(color.Output, ProgramName()+": ")
fmt.Fprint(color.Output, a...)
}
default:
if isatty.IsTerminal(os.Stdout.Fd()) {
c.Print(a...)
} else {
fmt.Fprint(color.Output, a...)
}
}
}
// consolePrintf - same as print with a new line.
func consolePrintf(tag string, c *color.Color, format string, a ...interface{}) {
privateMutex.Lock()
defer privateMutex.Unlock()
switch tag {
case "Debug":
// if no arguments are given do not invoke debug printer.
if len(a) == 0 {
return
}
output := color.Output
color.Output = stderrColoredOutput
if isatty.IsTerminal(os.Stderr.Fd()) {
c.Print(ProgramName() + ": ")
c.Printf(format, a...)
} else {
fmt.Fprint(color.Output, ProgramName()+": ")
fmt.Fprintf(color.Output, format, a...)
}
color.Output = output
case "Fatal":
fallthrough
case "Error":
// if no arguments are given do not invoke fatal and error printer.
if len(a) == 0 {
return
}
output := color.Output
color.Output = stderrColoredOutput
if isatty.IsTerminal(os.Stderr.Fd()) {
c.Print(ProgramName() + ": ")
c.Printf(format, a...)
} else {
fmt.Fprint(color.Output, ProgramName()+": ")
fmt.Fprintf(color.Output, format, a...)
}
color.Output = output
case "Info":
// if no arguments are given do not invoke info printer.
if len(a) == 0 {
return
}
if isatty.IsTerminal(os.Stdout.Fd()) {
c.Print(ProgramName() + ": ")
c.Printf(format, a...)
} else {
fmt.Fprint(color.Output, ProgramName()+": ")
fmt.Fprintf(color.Output, format, a...)
}
default:
if isatty.IsTerminal(os.Stdout.Fd()) {
c.Printf(format, a...)
} else {
fmt.Fprintf(color.Output, format, a...)
}
}
}
// consolePrintln - same as print with a new line.
func consolePrintln(tag string, c *color.Color, a ...interface{}) {
privateMutex.Lock()
defer privateMutex.Unlock()
switch tag {
case "Debug":
// if no arguments are given do not invoke debug printer.
if len(a) == 0 {
return
}
output := color.Output
color.Output = stderrColoredOutput
if isatty.IsTerminal(os.Stderr.Fd()) {
c.Print(ProgramName() + ": ")
c.Println(a...)
} else {
fmt.Fprint(color.Output, ProgramName()+": ")
fmt.Fprintln(color.Output, a...)
}
color.Output = output
case "Fatal":
fallthrough
case "Error":
// if no arguments are given do not invoke fatal and error printer.
if len(a) == 0 {
return
}
output := color.Output
color.Output = stderrColoredOutput
if isatty.IsTerminal(os.Stderr.Fd()) {
c.Print(ProgramName() + ": ")
c.Println(a...)
} else {
fmt.Fprint(color.Output, ProgramName()+": ")
fmt.Fprintln(color.Output, a...)
}
color.Output = output
case "Info":
// if no arguments are given do not invoke info printer.
if len(a) == 0 {
return
}
if isatty.IsTerminal(os.Stdout.Fd()) {
c.Print(ProgramName() + ": ")
c.Println(a...)
} else {
fmt.Fprint(color.Output, ProgramName()+": ")
fmt.Fprintln(color.Output, a...)
}
default:
if isatty.IsTerminal(os.Stdout.Fd()) {
c.Println(a...)
} else {
fmt.Fprintln(color.Output, a...)
}
}
}
// Lock console.
func Lock() {
publicMutex.Lock()
}
// Unlock locked console.
func Unlock() {
publicMutex.Unlock()
}
// ProgramName - return the name of the executable program.
func ProgramName() string {
_, progName := filepath.Split(os.Args[0])
return progName
}
// Table - data to print in table format with fixed row widths.
type Table struct {
// per-row colors
RowColors []*color.Color
// per-column align-right flag (aligns left by default)
AlignRight []bool
// Left margin width for table
TableIndentWidth int
// Flag to print separator under heading. Row 0 is considered heading
HeaderRowSeparator bool
}
// NewTable - create a new Table instance. Takes per-row colors and
// per-column right-align flags and table indentation width (i.e. left
// margin width)
func NewTable(rowColors []*color.Color, alignRight []bool, indentWidth int) *Table {
return &Table{rowColors, alignRight, indentWidth, false}
}
// PopulateTable - writes to the custom output
func (t *Table) PopulateTable(out io.Writer, rows [][]string) error {
numRows := len(rows)
numCols := len(rows[0])
if numRows != len(t.RowColors) {
return fmt.Errorf("row count and row-colors mismatch")
}
// Compute max. column widths
maxColWidths := make([]int, numCols)
for _, row := range rows {
if len(row) != len(t.AlignRight) {
return fmt.Errorf("col count and align-right mismatch")
}
for i, v := range row {
if len([]rune(v)) > maxColWidths[i] {
maxColWidths[i] = len([]rune(v))
}
}
}
// Compute per-cell text with padding and alignment applied.
paddedText := make([][]string, numRows)
for r, row := range rows {
paddedText[r] = make([]string, numCols)
for c, cell := range row {
if t.AlignRight[c] {
fmtStr := fmt.Sprintf("%%%ds", maxColWidths[c])
paddedText[r][c] = fmt.Sprintf(fmtStr, cell)
} else {
extraWidth := maxColWidths[c] - len([]rune(cell))
fmtStr := fmt.Sprintf("%%s%%%ds", extraWidth)
paddedText[r][c] = fmt.Sprintf(fmtStr, cell, "")
}
}
}
// Draw table top border
segments := make([]string, numCols)
for i, c := range maxColWidths {
segments[i] = strings.Repeat("─", c+2)
}
indentText := strings.Repeat(" ", t.TableIndentWidth)
border := fmt.Sprintf("%s┌%s┐", indentText, strings.Join(segments, "┬"))
fmt.Fprintln(out, border)
// Print the table with colors
for r, row := range paddedText {
if t.HeaderRowSeparator && r == 1 {
// Draw table header-row border
border = fmt.Sprintf("%s├%s┤", indentText, strings.Join(segments, "┼"))
fmt.Println(border)
}
fmt.Fprint(out, indentText+"│ ")
for c, text := range row {
t.RowColors[r].Fprint(out, text)
if c != numCols-1 {
fmt.Fprint(out, " │ ")
}
}
fmt.Fprintln(out, " │")
}
// Draw table bottom border
border = fmt.Sprintf("%s└%s┘", indentText, strings.Join(segments, "┴"))
fmt.Fprintln(out, border)
return nil
}
// DisplayTable - prints the table
func (t *Table) DisplayTable(rows [][]string) error {
numRows := len(rows)
numCols := len(rows[0])
if numRows != len(t.RowColors) {
return fmt.Errorf("row count and row-colors mismatch")
}
// Compute max. column widths
maxColWidths := make([]int, numCols)
for _, row := range rows {
if len(row) != len(t.AlignRight) {
return fmt.Errorf("col count and align-right mismatch")
}
for i, v := range row {
if len([]rune(v)) > maxColWidths[i] {
maxColWidths[i] = len([]rune(v))
}
}
}
// Compute per-cell text with padding and alignment applied.
paddedText := make([][]string, numRows)
for r, row := range rows {
paddedText[r] = make([]string, numCols)
for c, cell := range row {
if t.AlignRight[c] {
fmtStr := fmt.Sprintf("%%%ds", maxColWidths[c])
paddedText[r][c] = fmt.Sprintf(fmtStr, cell)
} else {
extraWidth := maxColWidths[c] - len([]rune(cell))
fmtStr := fmt.Sprintf("%%s%%%ds", extraWidth)
paddedText[r][c] = fmt.Sprintf(fmtStr, cell, "")
}
}
}
// Draw table top border
segments := make([]string, numCols)
for i, c := range maxColWidths {
segments[i] = strings.Repeat("─", c+2)
}
indentText := strings.Repeat(" ", t.TableIndentWidth)
border := fmt.Sprintf("%s┌%s┐", indentText, strings.Join(segments, "┬"))
fmt.Println(border)
// Print the table with colors
for r, row := range paddedText {
if t.HeaderRowSeparator && r == 1 {
// Draw table header-row border
border = fmt.Sprintf("%s├%s┤", indentText, strings.Join(segments, "┼"))
fmt.Println(border)
}
fmt.Print(indentText + "│ ")
for c, text := range row {
t.RowColors[r].Print(text)
if c != numCols-1 {
fmt.Print(" │ ")
}
}
fmt.Println(" │")
}
// Draw table bottom border
border = fmt.Sprintf("%s└%s┘", indentText, strings.Join(segments, "┴"))
fmt.Println(border)
return nil
}
// RewindLines - uses terminal escape symbols to clear and rewind
// upwards on the console for `n` lines.
func RewindLines(n int) {
for i := 0; i < n; i++ {
fmt.Printf("\033[1A\033[K")
}
}
golang-github-minio-pkg-3.0.10/console/console_test.go 0000664 0000000 0000000 00000002071 14655770405 0022721 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package console
import (
"testing"
"github.com/fatih/color"
)
func TestSetColor(t *testing.T) {
SetColor("unknown", color.New(color.FgWhite))
cl := getThemeColor("unknown")
if cl == nil {
t.Fatal("missing theme")
}
}
func TestColorLock(_ *testing.T) {
Lock()
Print("") // Test for deadlocks.
Unlock()
}
golang-github-minio-pkg-3.0.10/console/themes.go 0000664 0000000 0000000 00000003634 14655770405 0021513 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package console
import (
"sync"
"github.com/fatih/color"
)
var (
// Theme contains default color mapping.
theme = map[string]*color.Color{
"Debug": color.New(color.FgWhite, color.Faint, color.Italic),
"Fatal": color.New(color.FgRed, color.Italic, color.Bold),
"Error": color.New(color.FgYellow, color.Italic),
"Info": color.New(color.FgGreen, color.Bold),
"Print": color.New(),
"PrintB": color.New(color.FgBlue, color.Bold),
"PrintC": color.New(color.FgGreen, color.Bold),
}
themeMu sync.Mutex
)
func getThemeColor(tag string) *color.Color {
themeMu.Lock()
defer themeMu.Unlock()
return theme[tag]
}
func setThemeColor(tag string, cl *color.Color) {
themeMu.Lock()
defer themeMu.Unlock()
theme[tag] = cl
}
// SetColorOff disables coloring for the entire session.
func SetColorOff() {
privateMutex.Lock()
defer privateMutex.Unlock()
color.NoColor = true
}
// SetColorOn enables coloring for the entire session.
func SetColorOn() {
privateMutex.Lock()
defer privateMutex.Unlock()
color.NoColor = false
}
// SetColor sets a color for a particular tag.
func SetColor(tag string, cl *color.Color) {
// add new theme
setThemeColor(tag, cl)
}
golang-github-minio-pkg-3.0.10/cors/ 0000775 0000000 0000000 00000000000 14655770405 0017175 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/cors/cors.go 0000664 0000000 0000000 00000014046 14655770405 0020477 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2024 MinIO, Inc.
//
// # This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package cors
import (
"encoding/xml"
"fmt"
"io"
"net/http"
"strings"
"github.com/minio/pkg/v3/wildcard"
)
const defaultXMLNS = "http://s3.amazonaws.com/doc/2006-03-01/"
var allowedCORSRuleMethods = map[string]bool{
http.MethodGet: true,
http.MethodPut: true,
http.MethodPost: true,
http.MethodDelete: true,
http.MethodHead: true,
}
// Config is the container for a CORS configuration for a bucket.
type Config struct {
XMLNS string `xml:"xmlns,attr,omitempty"`
XMLName xml.Name `xml:"CORSConfiguration"`
CORSRules []Rule `xml:"CORSRule"`
}
// Rule is a single rule in a CORS configuration.
type Rule struct {
AllowedHeader []string `xml:"AllowedHeader,omitempty"`
AllowedMethod []string `xml:"AllowedMethod,omitempty"`
AllowedOrigin []string `xml:"AllowedOrigin,omitempty"`
ExposeHeader []string `xml:"ExposeHeader,omitempty"`
ID string `xml:"ID,omitempty"`
MaxAgeSeconds int `xml:"MaxAgeSeconds,omitempty"`
}
// Validate checks the CORS configuration is valid. This has been implemented to return errors that can be transformed
// to match the S3 API externally, while being slightly more informative internally using wrapping.
// Validate copies S3 behavior, and validates one rule at a time, erroring on the first invalid one found.
func (c *Config) Validate() error {
if len(c.CORSRules) == 0 {
return fmt.Errorf("no CORS rules found, %w", ErrMalformedXML{})
}
if len(c.CORSRules) > 100 {
return fmt.Errorf("too many CORS rules, max 100 allowed, got: %d, %w", len(c.CORSRules), ErrTooManyRules{})
}
for _, rule := range c.CORSRules {
// Origin validation
if len(rule.AllowedOrigin) == 0 {
return fmt.Errorf("no AllowedOrigin found in CORS rule, id: %s, %w", rule.ID, ErrMalformedXML{})
}
for _, origin := range rule.AllowedOrigin {
if strings.Count(origin, "*") > 1 {
return fmt.Errorf("origin %s in CORS rule, id: %s, %w", origin, rule.ID, ErrAllowedOriginWildcards{Origin: origin})
}
}
// Methods validation
if len(rule.AllowedMethod) == 0 {
return fmt.Errorf("no AllowedMethod found in CORS rule, id: %s, %w", rule.ID, ErrMalformedXML{})
}
for _, method := range rule.AllowedMethod {
if !allowedCORSRuleMethods[method] {
return fmt.Errorf("method %s in CORS rule, id: %s, %w", method, rule.ID, ErrInvalidMethod{Method: method})
}
}
// Headers validation
for _, header := range rule.AllowedHeader {
if strings.Count(header, "*") > 1 {
return fmt.Errorf("header %s in CORS rule, id: %s, %w", header, rule.ID, ErrAllowedHeaderWildcards{Header: header})
}
}
}
return nil
}
// HasAllowedOrigin returns true if the given origin is allowed by the CORS rule
func (c *Rule) HasAllowedOrigin(origin string) bool {
// See "AllowedOrigin element" in https://docs.aws.amazon.com/AmazonS3/latest/userguide/ManageCorsUsing.html
for _, allowedOrigin := range c.AllowedOrigin {
if wildcard.Match(allowedOrigin, origin) {
// Only one wildcard character (*) is allowed by S3 spec, but Match does
// not enforce that, it's done by Validate() function.
// Origins are case sensitive
return true
}
}
return false
}
// HasAllowedMethod returns true if the given method is contained in the CORS rule.
func (c *Rule) HasAllowedMethod(method string) bool {
// See "AllowedMethod element" in https://docs.aws.amazon.com/AmazonS3/latest/userguide/ManageCorsUsing.html
for _, allowedMethod := range c.AllowedMethod {
if allowedMethod == method {
// Methods are always uppercase, enforced by Validate() function.
return true
}
}
return false
}
// FilterAllowedHeaders returns the headers that are allowed by the rule, and a boolean indicating if all headers are allowed.
func (c *Rule) FilterAllowedHeaders(headers []string) ([]string, bool) {
// See "AllowedHeader element" in https://docs.aws.amazon.com/AmazonS3/latest/userguide/ManageCorsUsing.html
// It's inefficient to store the CORS config verbatim and run ToLower here, but S3 essentially
// behaves this way, and will return the XML config verbatim when you GET it.
filtered := []string{}
for _, header := range headers {
header = strings.ToLower(header)
found := false
for _, allowedHeader := range c.AllowedHeader {
// Case insensitive comparison for headers
if wildcard.Match(strings.ToLower(allowedHeader), header) {
// Only one wildcard character (*) is allowed by S3 spec, but Match does
// not enforce that, it's done by rule.Validate() function.
filtered = append(filtered, header)
found = true
break
}
}
if !found {
return nil, false
}
}
return filtered, true
}
// ParseBucketCorsConfig parses a CORS configuration in XML from an io.Reader.
func ParseBucketCorsConfig(reader io.Reader) (*Config, error) {
var c Config
err := xml.NewDecoder(reader).Decode(&c)
if err != nil {
return nil, fmt.Errorf("decoding xml: %w", err)
}
if c.XMLNS == "" {
c.XMLNS = defaultXMLNS
}
for i, rule := range c.CORSRules {
for j, method := range rule.AllowedMethod {
c.CORSRules[i].AllowedMethod[j] = strings.ToUpper(method)
}
}
return &c, nil
}
// ToXML marshals the CORS configuration to XML.
func (c Config) ToXML() ([]byte, error) {
if c.XMLNS == "" {
c.XMLNS = defaultXMLNS
}
data, err := xml.Marshal(&c)
if err != nil {
return nil, fmt.Errorf("marshaling xml: %w", err)
}
return append([]byte(xml.Header), data...), nil
}
golang-github-minio-pkg-3.0.10/cors/cors_test.go 0000664 0000000 0000000 00000016505 14655770405 0021540 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2024 MinIO, Inc.
//
// # This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package cors
import (
"bytes"
"encoding/xml"
"os"
"reflect"
"strings"
"testing"
)
var defaultXMLName = xml.Name{Space: "http://s3.amazonaws.com/doc/2006-03-01/", Local: "CORSConfiguration"}
func TestCORSFilterHeaders(t *testing.T) {
tests := []struct {
name string
rule Rule
headers []string
wantOk bool
wantHeaders []string
}{
{
name: "plain single header",
rule: Rule{AllowedHeader: []string{"x-custom-header"}},
headers: []string{"x-custom-header"},
wantOk: true,
wantHeaders: []string{"x-custom-header"},
},
{
name: "single header case insensitive",
rule: Rule{AllowedHeader: []string{"x-CUSTOM-header"}},
headers: []string{"x-custom-HEADER"},
wantOk: true,
wantHeaders: []string{"x-custom-header"},
},
{
name: "plain multiple headers in order",
rule: Rule{AllowedHeader: []string{"x-custom-header-1", "x-custom-header-2"}},
headers: []string{"x-custom-header-1", "x-custom-header-2"},
wantOk: true,
wantHeaders: []string{"x-custom-header-1", "x-custom-header-2"},
},
{
name: "plain multiple headers out of order",
rule: Rule{AllowedHeader: []string{"x-custom-header-2", "x-custom-header-1"}},
headers: []string{"x-custom-header-1", "x-custom-header-2"},
wantOk: true,
wantHeaders: []string{"x-custom-header-1", "x-custom-header-2"},
},
{
name: "plain multiple headers with unknown header",
rule: Rule{AllowedHeader: []string{"x-custom-header-1", "x-custom-header-2"}},
headers: []string{"x-custom-header-1", "x-custom-header-2", "x-custom-header-3"},
wantOk: false,
wantHeaders: nil,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
config := &Config{
CORSRules: []Rule{test.rule},
}
for _, rule := range config.CORSRules {
headers, ok := rule.FilterAllowedHeaders(test.headers)
if ok != test.wantOk {
t.Errorf("got: %v, want: %v", ok, test.wantOk)
}
if !reflect.DeepEqual(headers, test.wantHeaders) {
t.Errorf("got: %v, want: %v", headers, test.wantHeaders)
}
}
})
}
}
func TestCORSInvalid(t *testing.T) {
tests := []struct {
name string
config *Config
wantErrContains string
}{
{
name: "no CORS rules",
config: &Config{
CORSRules: []Rule{},
},
wantErrContains: "no CORS rules found",
},
{
name: "too many CORS rules",
config: &Config{
CORSRules: make([]Rule, 101),
},
wantErrContains: "too many CORS rules",
},
{
name: "no AllowedOrigin",
config: &Config{
CORSRules: []Rule{
{
ID: "1",
AllowedOrigin: []string{},
AllowedMethod: []string{"GET"},
},
},
},
wantErrContains: "no AllowedOrigin found in CORS rule, id: 1",
},
{
name: "invalid origin multiple wildcards",
config: &Config{
CORSRules: []Rule{
{
AllowedOrigin: []string{"https", "http://*.example.*"},
AllowedMethod: []string{"GET"},
},
},
},
wantErrContains: "can not have more than one wildcard",
},
{
name: "no AllowedMethod",
config: &Config{
CORSRules: []Rule{
{
AllowedOrigin: []string{"*"},
AllowedMethod: []string{},
},
},
},
wantErrContains: "no AllowedMethod found in CORS rule",
},
{
name: "invalid method",
config: &Config{
CORSRules: []Rule{
{
AllowedOrigin: []string{"*"},
AllowedMethod: []string{"GET", "POST", "PATCH"},
},
},
},
wantErrContains: "Unsupported method is PATCH",
},
{
name: "invalid method lowercase",
config: &Config{
CORSRules: []Rule{
{
AllowedOrigin: []string{"*"},
AllowedMethod: []string{"get"},
},
},
},
wantErrContains: "Unsupported method is get",
},
{
name: "invalid header multiple wildcards",
config: &Config{
CORSRules: []Rule{
{
AllowedOrigin: []string{"*"},
AllowedMethod: []string{"GET"},
AllowedHeader: []string{"X-*-Header-*"},
},
},
},
wantErrContains: "not have more than one wildcard",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := test.config.Validate()
if err == nil {
t.Fatal("expected error")
}
if !strings.Contains(err.Error(), test.wantErrContains) {
t.Errorf("got: %v, want contains: %v", err, test.wantErrContains)
}
})
}
}
func TestCORSXMLValid(t *testing.T) {
tests := []struct {
name string
filename string
wantCORSConfig *Config
}{
{
name: "example1 cors config",
filename: "example1.xml",
wantCORSConfig: &Config{
XMLName: defaultXMLName,
XMLNS: defaultXMLNS,
CORSRules: []Rule{
{
AllowedOrigin: []string{"http://www.example1.com"},
AllowedMethod: []string{"PUT", "POST", "DELETE"},
AllowedHeader: []string{"*"},
},
{
AllowedOrigin: []string{"http://www.example2.com"},
AllowedMethod: []string{"PUT", "POST", "DELETE"},
AllowedHeader: []string{"*"},
},
{
AllowedOrigin: []string{"*"},
AllowedMethod: []string{"GET"},
},
},
},
},
{
name: "example2 cors config",
filename: "example2.xml",
wantCORSConfig: &Config{
XMLName: defaultXMLName,
XMLNS: defaultXMLNS,
CORSRules: []Rule{
{
AllowedOrigin: []string{"http://www.example.com"},
AllowedMethod: []string{"PUT", "POST", "DELETE"},
AllowedHeader: []string{"*"},
MaxAgeSeconds: 3000,
ExposeHeader: []string{"x-amz-server-side-encryption", "x-amz-request-id", "x-amz-id-2"},
},
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
fileContents, err := os.ReadFile("testdata/" + test.filename)
if err != nil {
t.Fatal(err)
}
c, err := ParseBucketCorsConfig(bytes.NewReader(fileContents))
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(c, test.wantCORSConfig) {
t.Errorf("got: %v, want: %v", c, test.wantCORSConfig)
}
err = c.Validate()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
})
}
}
func TestCORSXMLMarshal(t *testing.T) {
fileContents, err := os.ReadFile("testdata/example3.xml")
if err != nil {
t.Fatal(err)
}
c, err := ParseBucketCorsConfig(bytes.NewReader(fileContents))
if err != nil {
t.Fatal(err)
}
remarshalled, err := c.ToXML()
if err != nil {
t.Fatal(err)
}
trimmedFileContents := bytes.TrimSpace(fileContents)
if !bytes.Equal(trimmedFileContents, remarshalled) {
t.Errorf("got: %s, want: %s", string(remarshalled), string(trimmedFileContents))
}
}
golang-github-minio-pkg-3.0.10/cors/errors.go 0000664 0000000 0000000 00000005051 14655770405 0021041 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2024 MinIO, Inc.
//
// # This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package cors
import "fmt"
// ErrTooManyRules is returned when the number of CORS rules exceeds the allowed limit.
type ErrTooManyRules struct{}
func (e ErrTooManyRules) Error() string {
return "The number of CORS rules should not exceed allowed limit of 100 rules."
}
// ErrMalformedXML is returned when the XML provided is not well-formed
type ErrMalformedXML struct{}
func (e ErrMalformedXML) Error() string {
return "The XML you provided was not well-formed or did not validate against our published schema"
}
// ErrAllowedOriginWildcards is returned when more than one wildcard is found in an AllowedOrigin.
type ErrAllowedOriginWildcards struct {
Origin string
}
func (e ErrAllowedOriginWildcards) Error() string {
// S3 quotes the origin, e.g. "http://*.*.example.com", in the error message, but these quotes are currently
// escaped by Go xml encoder. We could fix this with a `,innerxml` tag on the struct, but that has
// other implications. Easier to not add quotes in our error for now, revisit if this is an issue.
return fmt.Sprintf(`AllowedOrigin %s can not have more than one wildcard.`, e.Origin)
}
// ErrInvalidMethod is returned when an unsupported HTTP method is found in a CORS config.
type ErrInvalidMethod struct {
Method string
}
func (e ErrInvalidMethod) Error() string {
return fmt.Sprintf("Found unsupported HTTP method in CORS config. Unsupported method is %s", e.Method)
}
// ErrAllowedHeaderWildcards is returned when more than one wildcard is found in an AllowedHeader.
type ErrAllowedHeaderWildcards struct {
Header string
}
func (e ErrAllowedHeaderWildcards) Error() string {
// S3 quotes the header, e.g. "*-amz-*", in the error message, similar situation to ErrAllowedOriginWildcards above.
return fmt.Sprintf(`AllowedHeader %s can not have more than one wildcard.`, e.Header)
}
golang-github-minio-pkg-3.0.10/cors/testdata/ 0000775 0000000 0000000 00000000000 14655770405 0021006 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/cors/testdata/example1.xml 0000664 0000000 0000000 00000001363 14655770405 0023247 0 ustar 00root root 0000000 0000000
http://www.example1.com
PUT
POST
DELETE
*
http://www.example2.com
PUT
POST
DELETE
*
*
GET
golang-github-minio-pkg-3.0.10/cors/testdata/example2.xml 0000664 0000000 0000000 00000001137 14655770405 0023247 0 ustar 00root root 0000000 0000000
http://www.example.com
PUT
POST
DELETE
*
3000
x-amz-server-side-encryption
x-amz-request-id
x-amz-id-2
golang-github-minio-pkg-3.0.10/cors/testdata/example3.xml 0000664 0000000 0000000 00000001467 14655770405 0023256 0 ustar 00root root 0000000 0000000
*PUTPOSTDELETEhttp://www.example1.com*PUTPOSTDELETEhttp://www.example2.*GET*x-amz-id-26000POSThttps://www.example3.com
golang-github-minio-pkg-3.0.10/ellipses/ 0000775 0000000 0000000 00000000000 14655770405 0020047 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/ellipses/ellipses.go 0000664 0000000 0000000 00000015302 14655770405 0022217 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package ellipses
import (
"errors"
"fmt"
"regexp"
"strconv"
"strings"
)
var (
// Regex to extract ellipses syntax inputs.
regexpEllipses = regexp.MustCompile(`(.*)({[0-9a-z]*\.\.\.[0-9a-z]*})(.*)`)
// Ellipses constants
openBraces = "{"
closeBraces = "}"
ellipses = "..."
)
var errFormat = errors.New("format error")
// Parses an ellipses range pattern of following style
// `{1...64}`
// `{33...64}`
func parseEllipsesRange(pattern string) (seq []string, err error) {
if !strings.Contains(pattern, openBraces) {
return nil, errors.New("Invalid argument")
}
if !strings.Contains(pattern, closeBraces) {
return nil, errors.New("Invalid argument")
}
pattern = strings.TrimPrefix(pattern, openBraces)
pattern = strings.TrimSuffix(pattern, closeBraces)
ellipsesRange := strings.Split(pattern, ellipses)
if len(ellipsesRange) != 2 {
return nil, errors.New("Invalid argument")
}
var hexadecimal bool
var start, end uint64
if start, err = strconv.ParseUint(ellipsesRange[0], 10, 64); err != nil {
// Look for hexadecimal conversions if any.
start, err = strconv.ParseUint(ellipsesRange[0], 16, 64)
if err != nil {
return nil, err
}
hexadecimal = true
}
if end, err = strconv.ParseUint(ellipsesRange[1], 10, 64); err != nil {
// Look for hexadecimal conversions if any.
end, err = strconv.ParseUint(ellipsesRange[1], 16, 64)
if err != nil {
return nil, err
}
hexadecimal = true
}
if start > end {
return nil, fmt.Errorf("Incorrect range start %d cannot be bigger than end %d", start, end)
}
for i := start; i <= end; i++ {
if strings.HasPrefix(ellipsesRange[0], "0") && len(ellipsesRange[0]) > 1 || strings.HasPrefix(ellipsesRange[1], "0") {
if hexadecimal {
seq = append(seq, fmt.Sprintf(fmt.Sprintf("%%0%dx", len(ellipsesRange[1])), i))
} else {
seq = append(seq, fmt.Sprintf(fmt.Sprintf("%%0%dd", len(ellipsesRange[1])), i))
}
} else {
if hexadecimal {
seq = append(seq, fmt.Sprintf("%x", i))
} else {
seq = append(seq, fmt.Sprintf("%d", i))
}
}
}
return seq, nil
}
// Pattern - ellipses pattern, describes the range and also the
// associated prefix and suffixes.
type Pattern struct {
Prefix string
Suffix string
Seq []string
}
// argExpander - recursively expands labels into its respective forms.
func argExpander(labels [][]string) (out [][]string) {
if len(labels) == 1 {
for _, v := range labels[0] {
out = append(out, []string{v})
}
return out
}
for _, lbl := range labels[0] {
rs := argExpander(labels[1:])
for _, rlbls := range rs {
r := append(rlbls, []string{lbl}...)
out = append(out, r)
}
}
return out
}
// ArgPattern contains a list of patterns provided in the input.
type ArgPattern []Pattern
// Expand - expands all the ellipses patterns in
// the given argument.
func (a ArgPattern) Expand() [][]string {
labels := make([][]string, len(a))
for i := range labels {
labels[i] = a[i].Expand()
}
return argExpander(labels)
}
// Expand - expands a ellipses pattern.
func (p Pattern) Expand() []string {
var labels []string
for i := range p.Seq {
switch {
case p.Prefix != "" && p.Suffix == "":
labels = append(labels, fmt.Sprintf("%s%s", p.Prefix, p.Seq[i]))
case p.Suffix != "" && p.Prefix == "":
labels = append(labels, fmt.Sprintf("%s%s", p.Seq[i], p.Suffix))
case p.Suffix == "" && p.Prefix == "":
labels = append(labels, p.Seq[i])
default:
labels = append(labels, fmt.Sprintf("%s%s%s", p.Prefix, p.Seq[i], p.Suffix))
}
}
return labels
}
// HasEllipses - returns true if input arg has ellipses type pattern.
func HasEllipses(args ...string) bool {
ok := true
for _, arg := range args {
ok = ok && (strings.Count(arg, ellipses) > 0 || (strings.Count(arg, openBraces) > 0 && strings.Count(arg, closeBraces) > 0))
}
return ok
}
// ErrInvalidEllipsesFormatFn error returned when invalid ellipses format is detected.
var ErrInvalidEllipsesFormatFn = func(arg string) error {
return fmt.Errorf("Invalid ellipsis format in (%s), Ellipsis range must be provided in format {N...M} where N and M are positive integers, M must be greater than N, with an allowed minimum range of 4", arg)
}
// FindEllipsesPatterns - finds all ellipses patterns, recursively and parses the ranges numerically.
func FindEllipsesPatterns(arg string) (ArgPattern, error) {
v, err := findPatterns(arg, regexpEllipses, parseEllipsesRange)
if err == errFormat {
err = ErrInvalidEllipsesFormatFn(arg)
}
return v, err
}
// findPatterns - finds all patterns, recursively and parses the ranges numerically.
func findPatterns(arg string, re *regexp.Regexp, patternParser func(string) ([]string, error)) (ArgPattern, error) {
var patterns []Pattern
parts := re.FindStringSubmatch(arg)
if len(parts) == 0 {
// We throw an error if arg doesn't have any recognizable ellipses pattern.
return nil, errFormat
}
parts = parts[1:]
patternFound := re.MatchString(parts[0])
for patternFound {
seq, err := patternParser(parts[1])
if err != nil {
return patterns, err
}
patterns = append(patterns, Pattern{
Prefix: "",
Suffix: parts[2],
Seq: seq,
})
parts = re.FindStringSubmatch(parts[0])
if len(parts) > 0 {
parts = parts[1:]
patternFound = re.MatchString(parts[0])
continue
}
break
}
if len(parts) > 0 {
seq, err := patternParser(parts[1])
if err != nil {
return patterns, err
}
patterns = append(patterns, Pattern{
Prefix: parts[0],
Suffix: parts[2],
Seq: seq,
})
}
// Check if any of the prefix or suffixes now have flower braces
// left over, in such a case we generally think that there is
// perhaps a typo in users input and error out accordingly.
for _, pattern := range patterns {
if strings.Count(pattern.Prefix, openBraces) > 0 || strings.Count(pattern.Prefix, closeBraces) > 0 {
return nil, ErrInvalidEllipsesFormatFn(arg)
}
if strings.Count(pattern.Suffix, openBraces) > 0 || strings.Count(pattern.Suffix, closeBraces) > 0 {
return nil, ErrInvalidEllipsesFormatFn(arg)
}
}
return patterns, nil
}
golang-github-minio-pkg-3.0.10/ellipses/ellipses_test.go 0000664 0000000 0000000 00000212616 14655770405 0023265 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package ellipses
import (
"fmt"
"reflect"
"strings"
"testing"
)
// Test tests args with ellipses.
func TestHasEllipses(t *testing.T) {
testCases := []struct {
args []string
expectedOk bool
}{
// Tests for all args without ellipses.
{
[]string{"64"},
false,
},
// Found flower braces, still attempt to parse and throw an error.
{
[]string{"{1..64}"},
true,
},
{
[]string{"{1..2..}"},
true,
},
// Test for valid input.
{
[]string{"1...64"},
true,
},
{
[]string{"{1...2O}"},
true,
},
{
[]string{"..."},
true,
},
{
[]string{"{-1...1}"},
true,
},
{
[]string{"{0...-1}"},
true,
},
{
[]string{"{1....4}"},
true,
},
{
[]string{"{1...64}"},
true,
},
{
[]string{"{...}"},
true,
},
{
[]string{"{1...64}", "{65...128}"},
true,
},
{
[]string{"http://minio{2...3}/export/set{1...64}"},
true,
},
{
[]string{
"http://minio{2...3}/export/set{1...64}",
"http://minio{2...3}/export/set{65...128}",
},
true,
},
{
[]string{
"mydisk-{a...z}{1...20}",
},
true,
},
{
[]string{
"mydisk-{1...4}{1..2.}",
},
true,
},
}
for i, testCase := range testCases {
t.Run(fmt.Sprintf("Test%d", i+1), func(t *testing.T) {
gotOk := HasEllipses(testCase.args...)
if gotOk != testCase.expectedOk {
t.Errorf("Expected %t, got %t", testCase.expectedOk, gotOk)
}
})
}
}
// Test tests find ellipses patterns.
func TestFindEllipsesPatterns(t *testing.T) {
testCases := []struct {
pattern string
success bool
want [][]string
}{
// Tests for all invalid inputs
0: {
pattern: "{1..64}",
},
1: {
pattern: "1...64",
},
2: {
pattern: "...",
},
3: {
pattern: "{1...",
},
4: {
pattern: "...64}",
},
5: {
pattern: "{...}",
},
6: {
pattern: "{-1...1}",
},
7: {
pattern: "{0...-1}",
},
8: {
pattern: "{1...2O}",
},
9: {
pattern: "{64...1}",
},
10: {
pattern: "{1....4}",
},
11: {
pattern: "mydisk-{a...z}{1...20}",
},
12: {
pattern: "mydisk-{1...4}{1..2.}",
},
13: {
pattern: "{1..2.}-mydisk-{1...4}",
},
14: {
pattern: "{{1...4}}",
},
16: {
pattern: "{4...02}",
},
17: {
pattern: "{f...z}",
},
// Test for valid input.
18: {
pattern: "{1...64}",
success: true,
want: [][]string{{"1"}, {"2"}, {"3"}, {"4"}, {"5"}, {"6"}, {"7"}, {"8"}, {"9"}, {"10"}, {"11"}, {"12"}, {"13"}, {"14"}, {"15"}, {"16"}, {"17"}, {"18"}, {"19"}, {"20"}, {"21"}, {"22"}, {"23"}, {"24"}, {"25"}, {"26"}, {"27"}, {"28"}, {"29"}, {"30"}, {"31"}, {"32"}, {"33"}, {"34"}, {"35"}, {"36"}, {"37"}, {"38"}, {"39"}, {"40"}, {"41"}, {"42"}, {"43"}, {"44"}, {"45"}, {"46"}, {"47"}, {"48"}, {"49"}, {"50"}, {"51"}, {"52"}, {"53"}, {"54"}, {"55"}, {"56"}, {"57"}, {"58"}, {"59"}, {"60"}, {"61"}, {"62"}, {"63"}, {"64"}},
},
19: {
pattern: "{1...64} {65...128}",
success: true,
want: [][]string{{"1 ", "65"}, {"2 ", "65"}, {"3 ", "65"}, {"4 ", "65"}, {"5 ", "65"}, {"6 ", "65"}, {"7 ", "65"}, {"8 ", "65"}, {"9 ", "65"}, {"10 ", "65"}, {"11 ", "65"}, {"12 ", "65"}, {"13 ", "65"}, {"14 ", "65"}, {"15 ", "65"}, {"16 ", "65"}, {"17 ", "65"}, {"18 ", "65"}, {"19 ", "65"}, {"20 ", "65"}, {"21 ", "65"}, {"22 ", "65"}, {"23 ", "65"}, {"24 ", "65"}, {"25 ", "65"}, {"26 ", "65"}, {"27 ", "65"}, {"28 ", "65"}, {"29 ", "65"}, {"30 ", "65"}, {"31 ", "65"}, {"32 ", "65"}, {"33 ", "65"}, {"34 ", "65"}, {"35 ", "65"}, {"36 ", "65"}, {"37 ", "65"}, {"38 ", "65"}, {"39 ", "65"}, {"40 ", "65"}, {"41 ", "65"}, {"42 ", "65"}, {"43 ", "65"}, {"44 ", "65"}, {"45 ", "65"}, {"46 ", "65"}, {"47 ", "65"}, {"48 ", "65"}, {"49 ", "65"}, {"50 ", "65"}, {"51 ", "65"}, {"52 ", "65"}, {"53 ", "65"}, {"54 ", "65"}, {"55 ", "65"}, {"56 ", "65"}, {"57 ", "65"}, {"58 ", "65"}, {"59 ", "65"}, {"60 ", "65"}, {"61 ", "65"}, {"62 ", "65"}, {"63 ", "65"}, {"64 ", "65"}, {"1 ", "66"}, {"2 ", "66"}, {"3 ", "66"}, {"4 ", "66"}, {"5 ", "66"}, {"6 ", "66"}, {"7 ", "66"}, {"8 ", "66"}, {"9 ", "66"}, {"10 ", "66"}, {"11 ", "66"}, {"12 ", "66"}, {"13 ", "66"}, {"14 ", "66"}, {"15 ", "66"}, {"16 ", "66"}, {"17 ", "66"}, {"18 ", "66"}, {"19 ", "66"}, {"20 ", "66"}, {"21 ", "66"}, {"22 ", "66"}, {"23 ", "66"}, {"24 ", "66"}, {"25 ", "66"}, {"26 ", "66"}, {"27 ", "66"}, {"28 ", "66"}, {"29 ", "66"}, {"30 ", "66"}, {"31 ", "66"}, {"32 ", "66"}, {"33 ", "66"}, {"34 ", "66"}, {"35 ", "66"}, {"36 ", "66"}, {"37 ", "66"}, {"38 ", "66"}, {"39 ", "66"}, {"40 ", "66"}, {"41 ", "66"}, {"42 ", "66"}, {"43 ", "66"}, {"44 ", "66"}, {"45 ", "66"}, {"46 ", "66"}, {"47 ", "66"}, {"48 ", "66"}, {"49 ", "66"}, {"50 ", "66"}, {"51 ", "66"}, {"52 ", "66"}, {"53 ", "66"}, {"54 ", "66"}, {"55 ", "66"}, {"56 ", "66"}, {"57 ", "66"}, {"58 ", "66"}, {"59 ", "66"}, {"60 ", "66"}, {"61 ", "66"}, {"62 ", "66"}, {"63 ", "66"}, {"64 ", "66"}, {"1 ", "67"}, {"2 ", "67"}, {"3 ", "67"}, {"4 ", "67"}, {"5 ", "67"}, {"6 ", "67"}, {"7 ", "67"}, {"8 ", "67"}, {"9 ", "67"}, {"10 ", "67"}, {"11 ", "67"}, {"12 ", "67"}, {"13 ", "67"}, {"14 ", "67"}, {"15 ", "67"}, {"16 ", "67"}, {"17 ", "67"}, {"18 ", "67"}, {"19 ", "67"}, {"20 ", "67"}, {"21 ", "67"}, {"22 ", "67"}, {"23 ", "67"}, {"24 ", "67"}, {"25 ", "67"}, {"26 ", "67"}, {"27 ", "67"}, {"28 ", "67"}, {"29 ", "67"}, {"30 ", "67"}, {"31 ", "67"}, {"32 ", "67"}, {"33 ", "67"}, {"34 ", "67"}, {"35 ", "67"}, {"36 ", "67"}, {"37 ", "67"}, {"38 ", "67"}, {"39 ", "67"}, {"40 ", "67"}, {"41 ", "67"}, {"42 ", "67"}, {"43 ", "67"}, {"44 ", "67"}, {"45 ", "67"}, {"46 ", "67"}, {"47 ", "67"}, {"48 ", "67"}, {"49 ", "67"}, {"50 ", "67"}, {"51 ", "67"}, {"52 ", "67"}, {"53 ", "67"}, {"54 ", "67"}, {"55 ", "67"}, {"56 ", "67"}, {"57 ", "67"}, {"58 ", "67"}, {"59 ", "67"}, {"60 ", "67"}, {"61 ", "67"}, {"62 ", "67"}, {"63 ", "67"}, {"64 ", "67"}, {"1 ", "68"}, {"2 ", "68"}, {"3 ", "68"}, {"4 ", "68"}, {"5 ", "68"}, {"6 ", "68"}, {"7 ", "68"}, {"8 ", "68"}, {"9 ", "68"}, {"10 ", "68"}, {"11 ", "68"}, {"12 ", "68"}, {"13 ", "68"}, {"14 ", "68"}, {"15 ", "68"}, {"16 ", "68"}, {"17 ", "68"}, {"18 ", "68"}, {"19 ", "68"}, {"20 ", "68"}, {"21 ", "68"}, {"22 ", "68"}, {"23 ", "68"}, {"24 ", "68"}, {"25 ", "68"}, {"26 ", "68"}, {"27 ", "68"}, {"28 ", "68"}, {"29 ", "68"}, {"30 ", "68"}, {"31 ", "68"}, {"32 ", "68"}, {"33 ", "68"}, {"34 ", "68"}, {"35 ", "68"}, {"36 ", "68"}, {"37 ", "68"}, {"38 ", "68"}, {"39 ", "68"}, {"40 ", "68"}, {"41 ", "68"}, {"42 ", "68"}, {"43 ", "68"}, {"44 ", "68"}, {"45 ", "68"}, {"46 ", "68"}, {"47 ", "68"}, {"48 ", "68"}, {"49 ", "68"}, {"50 ", "68"}, {"51 ", "68"}, {"52 ", "68"}, {"53 ", "68"}, {"54 ", "68"}, {"55 ", "68"}, {"56 ", "68"}, {"57 ", "68"}, {"58 ", "68"}, {"59 ", "68"}, {"60 ", "68"}, {"61 ", "68"}, {"62 ", "68"}, {"63 ", "68"}, {"64 ", "68"}, {"1 ", "69"}, {"2 ", "69"}, {"3 ", "69"}, {"4 ", "69"}, {"5 ", "69"}, {"6 ", "69"}, {"7 ", "69"}, {"8 ", "69"}, {"9 ", "69"}, {"10 ", "69"}, {"11 ", "69"}, {"12 ", "69"}, {"13 ", "69"}, {"14 ", "69"}, {"15 ", "69"}, {"16 ", "69"}, {"17 ", "69"}, {"18 ", "69"}, {"19 ", "69"}, {"20 ", "69"}, {"21 ", "69"}, {"22 ", "69"}, {"23 ", "69"}, {"24 ", "69"}, {"25 ", "69"}, {"26 ", "69"}, {"27 ", "69"}, {"28 ", "69"}, {"29 ", "69"}, {"30 ", "69"}, {"31 ", "69"}, {"32 ", "69"}, {"33 ", "69"}, {"34 ", "69"}, {"35 ", "69"}, {"36 ", "69"}, {"37 ", "69"}, {"38 ", "69"}, {"39 ", "69"}, {"40 ", "69"}, {"41 ", "69"}, {"42 ", "69"}, {"43 ", "69"}, {"44 ", "69"}, {"45 ", "69"}, {"46 ", "69"}, {"47 ", "69"}, {"48 ", "69"}, {"49 ", "69"}, {"50 ", "69"}, {"51 ", "69"}, {"52 ", "69"}, {"53 ", "69"}, {"54 ", "69"}, {"55 ", "69"}, {"56 ", "69"}, {"57 ", "69"}, {"58 ", "69"}, {"59 ", "69"}, {"60 ", "69"}, {"61 ", "69"}, {"62 ", "69"}, {"63 ", "69"}, {"64 ", "69"}, {"1 ", "70"}, {"2 ", "70"}, {"3 ", "70"}, {"4 ", "70"}, {"5 ", "70"}, {"6 ", "70"}, {"7 ", "70"}, {"8 ", "70"}, {"9 ", "70"}, {"10 ", "70"}, {"11 ", "70"}, {"12 ", "70"}, {"13 ", "70"}, {"14 ", "70"}, {"15 ", "70"}, {"16 ", "70"}, {"17 ", "70"}, {"18 ", "70"}, {"19 ", "70"}, {"20 ", "70"}, {"21 ", "70"}, {"22 ", "70"}, {"23 ", "70"}, {"24 ", "70"}, {"25 ", "70"}, {"26 ", "70"}, {"27 ", "70"}, {"28 ", "70"}, {"29 ", "70"}, {"30 ", "70"}, {"31 ", "70"}, {"32 ", "70"}, {"33 ", "70"}, {"34 ", "70"}, {"35 ", "70"}, {"36 ", "70"}, {"37 ", "70"}, {"38 ", "70"}, {"39 ", "70"}, {"40 ", "70"}, {"41 ", "70"}, {"42 ", "70"}, {"43 ", "70"}, {"44 ", "70"}, {"45 ", "70"}, {"46 ", "70"}, {"47 ", "70"}, {"48 ", "70"}, {"49 ", "70"}, {"50 ", "70"}, {"51 ", "70"}, {"52 ", "70"}, {"53 ", "70"}, {"54 ", "70"}, {"55 ", "70"}, {"56 ", "70"}, {"57 ", "70"}, {"58 ", "70"}, {"59 ", "70"}, {"60 ", "70"}, {"61 ", "70"}, {"62 ", "70"}, {"63 ", "70"}, {"64 ", "70"}, {"1 ", "71"}, {"2 ", "71"}, {"3 ", "71"}, {"4 ", "71"}, {"5 ", "71"}, {"6 ", "71"}, {"7 ", "71"}, {"8 ", "71"}, {"9 ", "71"}, {"10 ", "71"}, {"11 ", "71"}, {"12 ", "71"}, {"13 ", "71"}, {"14 ", "71"}, {"15 ", "71"}, {"16 ", "71"}, {"17 ", "71"}, {"18 ", "71"}, {"19 ", "71"}, {"20 ", "71"}, {"21 ", "71"}, {"22 ", "71"}, {"23 ", "71"}, {"24 ", "71"}, {"25 ", "71"}, {"26 ", "71"}, {"27 ", "71"}, {"28 ", "71"}, {"29 ", "71"}, {"30 ", "71"}, {"31 ", "71"}, {"32 ", "71"}, {"33 ", "71"}, {"34 ", "71"}, {"35 ", "71"}, {"36 ", "71"}, {"37 ", "71"}, {"38 ", "71"}, {"39 ", "71"}, {"40 ", "71"}, {"41 ", "71"}, {"42 ", "71"}, {"43 ", "71"}, {"44 ", "71"}, {"45 ", "71"}, {"46 ", "71"}, {"47 ", "71"}, {"48 ", "71"}, {"49 ", "71"}, {"50 ", "71"}, {"51 ", "71"}, {"52 ", "71"}, {"53 ", "71"}, {"54 ", "71"}, {"55 ", "71"}, {"56 ", "71"}, {"57 ", "71"}, {"58 ", "71"}, {"59 ", "71"}, {"60 ", "71"}, {"61 ", "71"}, {"62 ", "71"}, {"63 ", "71"}, {"64 ", "71"}, {"1 ", "72"}, {"2 ", "72"}, {"3 ", "72"}, {"4 ", "72"}, {"5 ", "72"}, {"6 ", "72"}, {"7 ", "72"}, {"8 ", "72"}, {"9 ", "72"}, {"10 ", "72"}, {"11 ", "72"}, {"12 ", "72"}, {"13 ", "72"}, {"14 ", "72"}, {"15 ", "72"}, {"16 ", "72"}, {"17 ", "72"}, {"18 ", "72"}, {"19 ", "72"}, {"20 ", "72"}, {"21 ", "72"}, {"22 ", "72"}, {"23 ", "72"}, {"24 ", "72"}, {"25 ", "72"}, {"26 ", "72"}, {"27 ", "72"}, {"28 ", "72"}, {"29 ", "72"}, {"30 ", "72"}, {"31 ", "72"}, {"32 ", "72"}, {"33 ", "72"}, {"34 ", "72"}, {"35 ", "72"}, {"36 ", "72"}, {"37 ", "72"}, {"38 ", "72"}, {"39 ", "72"}, {"40 ", "72"}, {"41 ", "72"}, {"42 ", "72"}, {"43 ", "72"}, {"44 ", "72"}, {"45 ", "72"}, {"46 ", "72"}, {"47 ", "72"}, {"48 ", "72"}, {"49 ", "72"}, {"50 ", "72"}, {"51 ", "72"}, {"52 ", "72"}, {"53 ", "72"}, {"54 ", "72"}, {"55 ", "72"}, {"56 ", "72"}, {"57 ", "72"}, {"58 ", "72"}, {"59 ", "72"}, {"60 ", "72"}, {"61 ", "72"}, {"62 ", "72"}, {"63 ", "72"}, {"64 ", "72"}, {"1 ", "73"}, {"2 ", "73"}, {"3 ", "73"}, {"4 ", "73"}, {"5 ", "73"}, {"6 ", "73"}, {"7 ", "73"}, {"8 ", "73"}, {"9 ", "73"}, {"10 ", "73"}, {"11 ", "73"}, {"12 ", "73"}, {"13 ", "73"}, {"14 ", "73"}, {"15 ", "73"}, {"16 ", "73"}, {"17 ", "73"}, {"18 ", "73"}, {"19 ", "73"}, {"20 ", "73"}, {"21 ", "73"}, {"22 ", "73"}, {"23 ", "73"}, {"24 ", "73"}, {"25 ", "73"}, {"26 ", "73"}, {"27 ", "73"}, {"28 ", "73"}, {"29 ", "73"}, {"30 ", "73"}, {"31 ", "73"}, {"32 ", "73"}, {"33 ", "73"}, {"34 ", "73"}, {"35 ", "73"}, {"36 ", "73"}, {"37 ", "73"}, {"38 ", "73"}, {"39 ", "73"}, {"40 ", "73"}, {"41 ", "73"}, {"42 ", "73"}, {"43 ", "73"}, {"44 ", "73"}, {"45 ", "73"}, {"46 ", "73"}, {"47 ", "73"}, {"48 ", "73"}, {"49 ", "73"}, {"50 ", "73"}, {"51 ", "73"}, {"52 ", "73"}, {"53 ", "73"}, {"54 ", "73"}, {"55 ", "73"}, {"56 ", "73"}, {"57 ", "73"}, {"58 ", "73"}, {"59 ", "73"}, {"60 ", "73"}, {"61 ", "73"}, {"62 ", "73"}, {"63 ", "73"}, {"64 ", "73"}, {"1 ", "74"}, {"2 ", "74"}, {"3 ", "74"}, {"4 ", "74"}, {"5 ", "74"}, {"6 ", "74"}, {"7 ", "74"}, {"8 ", "74"}, {"9 ", "74"}, {"10 ", "74"}, {"11 ", "74"}, {"12 ", "74"}, {"13 ", "74"}, {"14 ", "74"}, {"15 ", "74"}, {"16 ", "74"}, {"17 ", "74"}, {"18 ", "74"}, {"19 ", "74"}, {"20 ", "74"}, {"21 ", "74"}, {"22 ", "74"}, {"23 ", "74"}, {"24 ", "74"}, {"25 ", "74"}, {"26 ", "74"}, {"27 ", "74"}, {"28 ", "74"}, {"29 ", "74"}, {"30 ", "74"}, {"31 ", "74"}, {"32 ", "74"}, {"33 ", "74"}, {"34 ", "74"}, {"35 ", "74"}, {"36 ", "74"}, {"37 ", "74"}, {"38 ", "74"}, {"39 ", "74"}, {"40 ", "74"}, {"41 ", "74"}, {"42 ", "74"}, {"43 ", "74"}, {"44 ", "74"}, {"45 ", "74"}, {"46 ", "74"}, {"47 ", "74"}, {"48 ", "74"}, {"49 ", "74"}, {"50 ", "74"}, {"51 ", "74"}, {"52 ", "74"}, {"53 ", "74"}, {"54 ", "74"}, {"55 ", "74"}, {"56 ", "74"}, {"57 ", "74"}, {"58 ", "74"}, {"59 ", "74"}, {"60 ", "74"}, {"61 ", "74"}, {"62 ", "74"}, {"63 ", "74"}, {"64 ", "74"}, {"1 ", "75"}, {"2 ", "75"}, {"3 ", "75"}, {"4 ", "75"}, {"5 ", "75"}, {"6 ", "75"}, {"7 ", "75"}, {"8 ", "75"}, {"9 ", "75"}, {"10 ", "75"}, {"11 ", "75"}, {"12 ", "75"}, {"13 ", "75"}, {"14 ", "75"}, {"15 ", "75"}, {"16 ", "75"}, {"17 ", "75"}, {"18 ", "75"}, {"19 ", "75"}, {"20 ", "75"}, {"21 ", "75"}, {"22 ", "75"}, {"23 ", "75"}, {"24 ", "75"}, {"25 ", "75"}, {"26 ", "75"}, {"27 ", "75"}, {"28 ", "75"}, {"29 ", "75"}, {"30 ", "75"}, {"31 ", "75"}, {"32 ", "75"}, {"33 ", "75"}, {"34 ", "75"}, {"35 ", "75"}, {"36 ", "75"}, {"37 ", "75"}, {"38 ", "75"}, {"39 ", "75"}, {"40 ", "75"}, {"41 ", "75"}, {"42 ", "75"}, {"43 ", "75"}, {"44 ", "75"}, {"45 ", "75"}, {"46 ", "75"}, {"47 ", "75"}, {"48 ", "75"}, {"49 ", "75"}, {"50 ", "75"}, {"51 ", "75"}, {"52 ", "75"}, {"53 ", "75"}, {"54 ", "75"}, {"55 ", "75"}, {"56 ", "75"}, {"57 ", "75"}, {"58 ", "75"}, {"59 ", "75"}, {"60 ", "75"}, {"61 ", "75"}, {"62 ", "75"}, {"63 ", "75"}, {"64 ", "75"}, {"1 ", "76"}, {"2 ", "76"}, {"3 ", "76"}, {"4 ", "76"}, {"5 ", "76"}, {"6 ", "76"}, {"7 ", "76"}, {"8 ", "76"}, {"9 ", "76"}, {"10 ", "76"}, {"11 ", "76"}, {"12 ", "76"}, {"13 ", "76"}, {"14 ", "76"}, {"15 ", "76"}, {"16 ", "76"}, {"17 ", "76"}, {"18 ", "76"}, {"19 ", "76"}, {"20 ", "76"}, {"21 ", "76"}, {"22 ", "76"}, {"23 ", "76"}, {"24 ", "76"}, {"25 ", "76"}, {"26 ", "76"}, {"27 ", "76"}, {"28 ", "76"}, {"29 ", "76"}, {"30 ", "76"}, {"31 ", "76"}, {"32 ", "76"}, {"33 ", "76"}, {"34 ", "76"}, {"35 ", "76"}, {"36 ", "76"}, {"37 ", "76"}, {"38 ", "76"}, {"39 ", "76"}, {"40 ", "76"}, {"41 ", "76"}, {"42 ", "76"}, {"43 ", "76"}, {"44 ", "76"}, {"45 ", "76"}, {"46 ", "76"}, {"47 ", "76"}, {"48 ", "76"}, {"49 ", "76"}, {"50 ", "76"}, {"51 ", "76"}, {"52 ", "76"}, {"53 ", "76"}, {"54 ", "76"}, {"55 ", "76"}, {"56 ", "76"}, {"57 ", "76"}, {"58 ", "76"}, {"59 ", "76"}, {"60 ", "76"}, {"61 ", "76"}, {"62 ", "76"}, {"63 ", "76"}, {"64 ", "76"}, {"1 ", "77"}, {"2 ", "77"}, {"3 ", "77"}, {"4 ", "77"}, {"5 ", "77"}, {"6 ", "77"}, {"7 ", "77"}, {"8 ", "77"}, {"9 ", "77"}, {"10 ", "77"}, {"11 ", "77"}, {"12 ", "77"}, {"13 ", "77"}, {"14 ", "77"}, {"15 ", "77"}, {"16 ", "77"}, {"17 ", "77"}, {"18 ", "77"}, {"19 ", "77"}, {"20 ", "77"}, {"21 ", "77"}, {"22 ", "77"}, {"23 ", "77"}, {"24 ", "77"}, {"25 ", "77"}, {"26 ", "77"}, {"27 ", "77"}, {"28 ", "77"}, {"29 ", "77"}, {"30 ", "77"}, {"31 ", "77"}, {"32 ", "77"}, {"33 ", "77"}, {"34 ", "77"}, {"35 ", "77"}, {"36 ", "77"}, {"37 ", "77"}, {"38 ", "77"}, {"39 ", "77"}, {"40 ", "77"}, {"41 ", "77"}, {"42 ", "77"}, {"43 ", "77"}, {"44 ", "77"}, {"45 ", "77"}, {"46 ", "77"}, {"47 ", "77"}, {"48 ", "77"}, {"49 ", "77"}, {"50 ", "77"}, {"51 ", "77"}, {"52 ", "77"}, {"53 ", "77"}, {"54 ", "77"}, {"55 ", "77"}, {"56 ", "77"}, {"57 ", "77"}, {"58 ", "77"}, {"59 ", "77"}, {"60 ", "77"}, {"61 ", "77"}, {"62 ", "77"}, {"63 ", "77"}, {"64 ", "77"}, {"1 ", "78"}, {"2 ", "78"}, {"3 ", "78"}, {"4 ", "78"}, {"5 ", "78"}, {"6 ", "78"}, {"7 ", "78"}, {"8 ", "78"}, {"9 ", "78"}, {"10 ", "78"}, {"11 ", "78"}, {"12 ", "78"}, {"13 ", "78"}, {"14 ", "78"}, {"15 ", "78"}, {"16 ", "78"}, {"17 ", "78"}, {"18 ", "78"}, {"19 ", "78"}, {"20 ", "78"}, {"21 ", "78"}, {"22 ", "78"}, {"23 ", "78"}, {"24 ", "78"}, {"25 ", "78"}, {"26 ", "78"}, {"27 ", "78"}, {"28 ", "78"}, {"29 ", "78"}, {"30 ", "78"}, {"31 ", "78"}, {"32 ", "78"}, {"33 ", "78"}, {"34 ", "78"}, {"35 ", "78"}, {"36 ", "78"}, {"37 ", "78"}, {"38 ", "78"}, {"39 ", "78"}, {"40 ", "78"}, {"41 ", "78"}, {"42 ", "78"}, {"43 ", "78"}, {"44 ", "78"}, {"45 ", "78"}, {"46 ", "78"}, {"47 ", "78"}, {"48 ", "78"}, {"49 ", "78"}, {"50 ", "78"}, {"51 ", "78"}, {"52 ", "78"}, {"53 ", "78"}, {"54 ", "78"}, {"55 ", "78"}, {"56 ", "78"}, {"57 ", "78"}, {"58 ", "78"}, {"59 ", "78"}, {"60 ", "78"}, {"61 ", "78"}, {"62 ", "78"}, {"63 ", "78"}, {"64 ", "78"}, {"1 ", "79"}, {"2 ", "79"}, {"3 ", "79"}, {"4 ", "79"}, {"5 ", "79"}, {"6 ", "79"}, {"7 ", "79"}, {"8 ", "79"}, {"9 ", "79"}, {"10 ", "79"}, {"11 ", "79"}, {"12 ", "79"}, {"13 ", "79"}, {"14 ", "79"}, {"15 ", "79"}, {"16 ", "79"}, {"17 ", "79"}, {"18 ", "79"}, {"19 ", "79"}, {"20 ", "79"}, {"21 ", "79"}, {"22 ", "79"}, {"23 ", "79"}, {"24 ", "79"}, {"25 ", "79"}, {"26 ", "79"}, {"27 ", "79"}, {"28 ", "79"}, {"29 ", "79"}, {"30 ", "79"}, {"31 ", "79"}, {"32 ", "79"}, {"33 ", "79"}, {"34 ", "79"}, {"35 ", "79"}, {"36 ", "79"}, {"37 ", "79"}, {"38 ", "79"}, {"39 ", "79"}, {"40 ", "79"}, {"41 ", "79"}, {"42 ", "79"}, {"43 ", "79"}, {"44 ", "79"}, {"45 ", "79"}, {"46 ", "79"}, {"47 ", "79"}, {"48 ", "79"}, {"49 ", "79"}, {"50 ", "79"}, {"51 ", "79"}, {"52 ", "79"}, {"53 ", "79"}, {"54 ", "79"}, {"55 ", "79"}, {"56 ", "79"}, {"57 ", "79"}, {"58 ", "79"}, {"59 ", "79"}, {"60 ", "79"}, {"61 ", "79"}, {"62 ", "79"}, {"63 ", "79"}, {"64 ", "79"}, {"1 ", "80"}, {"2 ", "80"}, {"3 ", "80"}, {"4 ", "80"}, {"5 ", "80"}, {"6 ", "80"}, {"7 ", "80"}, {"8 ", "80"}, {"9 ", "80"}, {"10 ", "80"}, {"11 ", "80"}, {"12 ", "80"}, {"13 ", "80"}, {"14 ", "80"}, {"15 ", "80"}, {"16 ", "80"}, {"17 ", "80"}, {"18 ", "80"}, {"19 ", "80"}, {"20 ", "80"}, {"21 ", "80"}, {"22 ", "80"}, {"23 ", "80"}, {"24 ", "80"}, {"25 ", "80"}, {"26 ", "80"}, {"27 ", "80"}, {"28 ", "80"}, {"29 ", "80"}, {"30 ", "80"}, {"31 ", "80"}, {"32 ", "80"}, {"33 ", "80"}, {"34 ", "80"}, {"35 ", "80"}, {"36 ", "80"}, {"37 ", "80"}, {"38 ", "80"}, {"39 ", "80"}, {"40 ", "80"}, {"41 ", "80"}, {"42 ", "80"}, {"43 ", "80"}, {"44 ", "80"}, {"45 ", "80"}, {"46 ", "80"}, {"47 ", "80"}, {"48 ", "80"}, {"49 ", "80"}, {"50 ", "80"}, {"51 ", "80"}, {"52 ", "80"}, {"53 ", "80"}, {"54 ", "80"}, {"55 ", "80"}, {"56 ", "80"}, {"57 ", "80"}, {"58 ", "80"}, {"59 ", "80"}, {"60 ", "80"}, {"61 ", "80"}, {"62 ", "80"}, {"63 ", "80"}, {"64 ", "80"}, {"1 ", "81"}, {"2 ", "81"}, {"3 ", "81"}, {"4 ", "81"}, {"5 ", "81"}, {"6 ", "81"}, {"7 ", "81"}, {"8 ", "81"}, {"9 ", "81"}, {"10 ", "81"}, {"11 ", "81"}, {"12 ", "81"}, {"13 ", "81"}, {"14 ", "81"}, {"15 ", "81"}, {"16 ", "81"}, {"17 ", "81"}, {"18 ", "81"}, {"19 ", "81"}, {"20 ", "81"}, {"21 ", "81"}, {"22 ", "81"}, {"23 ", "81"}, {"24 ", "81"}, {"25 ", "81"}, {"26 ", "81"}, {"27 ", "81"}, {"28 ", "81"}, {"29 ", "81"}, {"30 ", "81"}, {"31 ", "81"}, {"32 ", "81"}, {"33 ", "81"}, {"34 ", "81"}, {"35 ", "81"}, {"36 ", "81"}, {"37 ", "81"}, {"38 ", "81"}, {"39 ", "81"}, {"40 ", "81"}, {"41 ", "81"}, {"42 ", "81"}, {"43 ", "81"}, {"44 ", "81"}, {"45 ", "81"}, {"46 ", "81"}, {"47 ", "81"}, {"48 ", "81"}, {"49 ", "81"}, {"50 ", "81"}, {"51 ", "81"}, {"52 ", "81"}, {"53 ", "81"}, {"54 ", "81"}, {"55 ", "81"}, {"56 ", "81"}, {"57 ", "81"}, {"58 ", "81"}, {"59 ", "81"}, {"60 ", "81"}, {"61 ", "81"}, {"62 ", "81"}, {"63 ", "81"}, {"64 ", "81"}, {"1 ", "82"}, {"2 ", "82"}, {"3 ", "82"}, {"4 ", "82"}, {"5 ", "82"}, {"6 ", "82"}, {"7 ", "82"}, {"8 ", "82"}, {"9 ", "82"}, {"10 ", "82"}, {"11 ", "82"}, {"12 ", "82"}, {"13 ", "82"}, {"14 ", "82"}, {"15 ", "82"}, {"16 ", "82"}, {"17 ", "82"}, {"18 ", "82"}, {"19 ", "82"}, {"20 ", "82"}, {"21 ", "82"}, {"22 ", "82"}, {"23 ", "82"}, {"24 ", "82"}, {"25 ", "82"}, {"26 ", "82"}, {"27 ", "82"}, {"28 ", "82"}, {"29 ", "82"}, {"30 ", "82"}, {"31 ", "82"}, {"32 ", "82"}, {"33 ", "82"}, {"34 ", "82"}, {"35 ", "82"}, {"36 ", "82"}, {"37 ", "82"}, {"38 ", "82"}, {"39 ", "82"}, {"40 ", "82"}, {"41 ", "82"}, {"42 ", "82"}, {"43 ", "82"}, {"44 ", "82"}, {"45 ", "82"}, {"46 ", "82"}, {"47 ", "82"}, {"48 ", "82"}, {"49 ", "82"}, {"50 ", "82"}, {"51 ", "82"}, {"52 ", "82"}, {"53 ", "82"}, {"54 ", "82"}, {"55 ", "82"}, {"56 ", "82"}, {"57 ", "82"}, {"58 ", "82"}, {"59 ", "82"}, {"60 ", "82"}, {"61 ", "82"}, {"62 ", "82"}, {"63 ", "82"}, {"64 ", "82"}, {"1 ", "83"}, {"2 ", "83"}, {"3 ", "83"}, {"4 ", "83"}, {"5 ", "83"}, {"6 ", "83"}, {"7 ", "83"}, {"8 ", "83"}, {"9 ", "83"}, {"10 ", "83"}, {"11 ", "83"}, {"12 ", "83"}, {"13 ", "83"}, {"14 ", "83"}, {"15 ", "83"}, {"16 ", "83"}, {"17 ", "83"}, {"18 ", "83"}, {"19 ", "83"}, {"20 ", "83"}, {"21 ", "83"}, {"22 ", "83"}, {"23 ", "83"}, {"24 ", "83"}, {"25 ", "83"}, {"26 ", "83"}, {"27 ", "83"}, {"28 ", "83"}, {"29 ", "83"}, {"30 ", "83"}, {"31 ", "83"}, {"32 ", "83"}, {"33 ", "83"}, {"34 ", "83"}, {"35 ", "83"}, {"36 ", "83"}, {"37 ", "83"}, {"38 ", "83"}, {"39 ", "83"}, {"40 ", "83"}, {"41 ", "83"}, {"42 ", "83"}, {"43 ", "83"}, {"44 ", "83"}, {"45 ", "83"}, {"46 ", "83"}, {"47 ", "83"}, {"48 ", "83"}, {"49 ", "83"}, {"50 ", "83"}, {"51 ", "83"}, {"52 ", "83"}, {"53 ", "83"}, {"54 ", "83"}, {"55 ", "83"}, {"56 ", "83"}, {"57 ", "83"}, {"58 ", "83"}, {"59 ", "83"}, {"60 ", "83"}, {"61 ", "83"}, {"62 ", "83"}, {"63 ", "83"}, {"64 ", "83"}, {"1 ", "84"}, {"2 ", "84"}, {"3 ", "84"}, {"4 ", "84"}, {"5 ", "84"}, {"6 ", "84"}, {"7 ", "84"}, {"8 ", "84"}, {"9 ", "84"}, {"10 ", "84"}, {"11 ", "84"}, {"12 ", "84"}, {"13 ", "84"}, {"14 ", "84"}, {"15 ", "84"}, {"16 ", "84"}, {"17 ", "84"}, {"18 ", "84"}, {"19 ", "84"}, {"20 ", "84"}, {"21 ", "84"}, {"22 ", "84"}, {"23 ", "84"}, {"24 ", "84"}, {"25 ", "84"}, {"26 ", "84"}, {"27 ", "84"}, {"28 ", "84"}, {"29 ", "84"}, {"30 ", "84"}, {"31 ", "84"}, {"32 ", "84"}, {"33 ", "84"}, {"34 ", "84"}, {"35 ", "84"}, {"36 ", "84"}, {"37 ", "84"}, {"38 ", "84"}, {"39 ", "84"}, {"40 ", "84"}, {"41 ", "84"}, {"42 ", "84"}, {"43 ", "84"}, {"44 ", "84"}, {"45 ", "84"}, {"46 ", "84"}, {"47 ", "84"}, {"48 ", "84"}, {"49 ", "84"}, {"50 ", "84"}, {"51 ", "84"}, {"52 ", "84"}, {"53 ", "84"}, {"54 ", "84"}, {"55 ", "84"}, {"56 ", "84"}, {"57 ", "84"}, {"58 ", "84"}, {"59 ", "84"}, {"60 ", "84"}, {"61 ", "84"}, {"62 ", "84"}, {"63 ", "84"}, {"64 ", "84"}, {"1 ", "85"}, {"2 ", "85"}, {"3 ", "85"}, {"4 ", "85"}, {"5 ", "85"}, {"6 ", "85"}, {"7 ", "85"}, {"8 ", "85"}, {"9 ", "85"}, {"10 ", "85"}, {"11 ", "85"}, {"12 ", "85"}, {"13 ", "85"}, {"14 ", "85"}, {"15 ", "85"}, {"16 ", "85"}, {"17 ", "85"}, {"18 ", "85"}, {"19 ", "85"}, {"20 ", "85"}, {"21 ", "85"}, {"22 ", "85"}, {"23 ", "85"}, {"24 ", "85"}, {"25 ", "85"}, {"26 ", "85"}, {"27 ", "85"}, {"28 ", "85"}, {"29 ", "85"}, {"30 ", "85"}, {"31 ", "85"}, {"32 ", "85"}, {"33 ", "85"}, {"34 ", "85"}, {"35 ", "85"}, {"36 ", "85"}, {"37 ", "85"}, {"38 ", "85"}, {"39 ", "85"}, {"40 ", "85"}, {"41 ", "85"}, {"42 ", "85"}, {"43 ", "85"}, {"44 ", "85"}, {"45 ", "85"}, {"46 ", "85"}, {"47 ", "85"}, {"48 ", "85"}, {"49 ", "85"}, {"50 ", "85"}, {"51 ", "85"}, {"52 ", "85"}, {"53 ", "85"}, {"54 ", "85"}, {"55 ", "85"}, {"56 ", "85"}, {"57 ", "85"}, {"58 ", "85"}, {"59 ", "85"}, {"60 ", "85"}, {"61 ", "85"}, {"62 ", "85"}, {"63 ", "85"}, {"64 ", "85"}, {"1 ", "86"}, {"2 ", "86"}, {"3 ", "86"}, {"4 ", "86"}, {"5 ", "86"}, {"6 ", "86"}, {"7 ", "86"}, {"8 ", "86"}, {"9 ", "86"}, {"10 ", "86"}, {"11 ", "86"}, {"12 ", "86"}, {"13 ", "86"}, {"14 ", "86"}, {"15 ", "86"}, {"16 ", "86"}, {"17 ", "86"}, {"18 ", "86"}, {"19 ", "86"}, {"20 ", "86"}, {"21 ", "86"}, {"22 ", "86"}, {"23 ", "86"}, {"24 ", "86"}, {"25 ", "86"}, {"26 ", "86"}, {"27 ", "86"}, {"28 ", "86"}, {"29 ", "86"}, {"30 ", "86"}, {"31 ", "86"}, {"32 ", "86"}, {"33 ", "86"}, {"34 ", "86"}, {"35 ", "86"}, {"36 ", "86"}, {"37 ", "86"}, {"38 ", "86"}, {"39 ", "86"}, {"40 ", "86"}, {"41 ", "86"}, {"42 ", "86"}, {"43 ", "86"}, {"44 ", "86"}, {"45 ", "86"}, {"46 ", "86"}, {"47 ", "86"}, {"48 ", "86"}, {"49 ", "86"}, {"50 ", "86"}, {"51 ", "86"}, {"52 ", "86"}, {"53 ", "86"}, {"54 ", "86"}, {"55 ", "86"}, {"56 ", "86"}, {"57 ", "86"}, {"58 ", "86"}, {"59 ", "86"}, {"60 ", "86"}, {"61 ", "86"}, {"62 ", "86"}, {"63 ", "86"}, {"64 ", "86"}, {"1 ", "87"}, {"2 ", "87"}, {"3 ", "87"}, {"4 ", "87"}, {"5 ", "87"}, {"6 ", "87"}, {"7 ", "87"}, {"8 ", "87"}, {"9 ", "87"}, {"10 ", "87"}, {"11 ", "87"}, {"12 ", "87"}, {"13 ", "87"}, {"14 ", "87"}, {"15 ", "87"}, {"16 ", "87"}, {"17 ", "87"}, {"18 ", "87"}, {"19 ", "87"}, {"20 ", "87"}, {"21 ", "87"}, {"22 ", "87"}, {"23 ", "87"}, {"24 ", "87"}, {"25 ", "87"}, {"26 ", "87"}, {"27 ", "87"}, {"28 ", "87"}, {"29 ", "87"}, {"30 ", "87"}, {"31 ", "87"}, {"32 ", "87"}, {"33 ", "87"}, {"34 ", "87"}, {"35 ", "87"}, {"36 ", "87"}, {"37 ", "87"}, {"38 ", "87"}, {"39 ", "87"}, {"40 ", "87"}, {"41 ", "87"}, {"42 ", "87"}, {"43 ", "87"}, {"44 ", "87"}, {"45 ", "87"}, {"46 ", "87"}, {"47 ", "87"}, {"48 ", "87"}, {"49 ", "87"}, {"50 ", "87"}, {"51 ", "87"}, {"52 ", "87"}, {"53 ", "87"}, {"54 ", "87"}, {"55 ", "87"}, {"56 ", "87"}, {"57 ", "87"}, {"58 ", "87"}, {"59 ", "87"}, {"60 ", "87"}, {"61 ", "87"}, {"62 ", "87"}, {"63 ", "87"}, {"64 ", "87"}, {"1 ", "88"}, {"2 ", "88"}, {"3 ", "88"}, {"4 ", "88"}, {"5 ", "88"}, {"6 ", "88"}, {"7 ", "88"}, {"8 ", "88"}, {"9 ", "88"}, {"10 ", "88"}, {"11 ", "88"}, {"12 ", "88"}, {"13 ", "88"}, {"14 ", "88"}, {"15 ", "88"}, {"16 ", "88"}, {"17 ", "88"}, {"18 ", "88"}, {"19 ", "88"}, {"20 ", "88"}, {"21 ", "88"}, {"22 ", "88"}, {"23 ", "88"}, {"24 ", "88"}, {"25 ", "88"}, {"26 ", "88"}, {"27 ", "88"}, {"28 ", "88"}, {"29 ", "88"}, {"30 ", "88"}, {"31 ", "88"}, {"32 ", "88"}, {"33 ", "88"}, {"34 ", "88"}, {"35 ", "88"}, {"36 ", "88"}, {"37 ", "88"}, {"38 ", "88"}, {"39 ", "88"}, {"40 ", "88"}, {"41 ", "88"}, {"42 ", "88"}, {"43 ", "88"}, {"44 ", "88"}, {"45 ", "88"}, {"46 ", "88"}, {"47 ", "88"}, {"48 ", "88"}, {"49 ", "88"}, {"50 ", "88"}, {"51 ", "88"}, {"52 ", "88"}, {"53 ", "88"}, {"54 ", "88"}, {"55 ", "88"}, {"56 ", "88"}, {"57 ", "88"}, {"58 ", "88"}, {"59 ", "88"}, {"60 ", "88"}, {"61 ", "88"}, {"62 ", "88"}, {"63 ", "88"}, {"64 ", "88"}, {"1 ", "89"}, {"2 ", "89"}, {"3 ", "89"}, {"4 ", "89"}, {"5 ", "89"}, {"6 ", "89"}, {"7 ", "89"}, {"8 ", "89"}, {"9 ", "89"}, {"10 ", "89"}, {"11 ", "89"}, {"12 ", "89"}, {"13 ", "89"}, {"14 ", "89"}, {"15 ", "89"}, {"16 ", "89"}, {"17 ", "89"}, {"18 ", "89"}, {"19 ", "89"}, {"20 ", "89"}, {"21 ", "89"}, {"22 ", "89"}, {"23 ", "89"}, {"24 ", "89"}, {"25 ", "89"}, {"26 ", "89"}, {"27 ", "89"}, {"28 ", "89"}, {"29 ", "89"}, {"30 ", "89"}, {"31 ", "89"}, {"32 ", "89"}, {"33 ", "89"}, {"34 ", "89"}, {"35 ", "89"}, {"36 ", "89"}, {"37 ", "89"}, {"38 ", "89"}, {"39 ", "89"}, {"40 ", "89"}, {"41 ", "89"}, {"42 ", "89"}, {"43 ", "89"}, {"44 ", "89"}, {"45 ", "89"}, {"46 ", "89"}, {"47 ", "89"}, {"48 ", "89"}, {"49 ", "89"}, {"50 ", "89"}, {"51 ", "89"}, {"52 ", "89"}, {"53 ", "89"}, {"54 ", "89"}, {"55 ", "89"}, {"56 ", "89"}, {"57 ", "89"}, {"58 ", "89"}, {"59 ", "89"}, {"60 ", "89"}, {"61 ", "89"}, {"62 ", "89"}, {"63 ", "89"}, {"64 ", "89"}, {"1 ", "90"}, {"2 ", "90"}, {"3 ", "90"}, {"4 ", "90"}, {"5 ", "90"}, {"6 ", "90"}, {"7 ", "90"}, {"8 ", "90"}, {"9 ", "90"}, {"10 ", "90"}, {"11 ", "90"}, {"12 ", "90"}, {"13 ", "90"}, {"14 ", "90"}, {"15 ", "90"}, {"16 ", "90"}, {"17 ", "90"}, {"18 ", "90"}, {"19 ", "90"}, {"20 ", "90"}, {"21 ", "90"}, {"22 ", "90"}, {"23 ", "90"}, {"24 ", "90"}, {"25 ", "90"}, {"26 ", "90"}, {"27 ", "90"}, {"28 ", "90"}, {"29 ", "90"}, {"30 ", "90"}, {"31 ", "90"}, {"32 ", "90"}, {"33 ", "90"}, {"34 ", "90"}, {"35 ", "90"}, {"36 ", "90"}, {"37 ", "90"}, {"38 ", "90"}, {"39 ", "90"}, {"40 ", "90"}, {"41 ", "90"}, {"42 ", "90"}, {"43 ", "90"}, {"44 ", "90"}, {"45 ", "90"}, {"46 ", "90"}, {"47 ", "90"}, {"48 ", "90"}, {"49 ", "90"}, {"50 ", "90"}, {"51 ", "90"}, {"52 ", "90"}, {"53 ", "90"}, {"54 ", "90"}, {"55 ", "90"}, {"56 ", "90"}, {"57 ", "90"}, {"58 ", "90"}, {"59 ", "90"}, {"60 ", "90"}, {"61 ", "90"}, {"62 ", "90"}, {"63 ", "90"}, {"64 ", "90"}, {"1 ", "91"}, {"2 ", "91"}, {"3 ", "91"}, {"4 ", "91"}, {"5 ", "91"}, {"6 ", "91"}, {"7 ", "91"}, {"8 ", "91"}, {"9 ", "91"}, {"10 ", "91"}, {"11 ", "91"}, {"12 ", "91"}, {"13 ", "91"}, {"14 ", "91"}, {"15 ", "91"}, {"16 ", "91"}, {"17 ", "91"}, {"18 ", "91"}, {"19 ", "91"}, {"20 ", "91"}, {"21 ", "91"}, {"22 ", "91"}, {"23 ", "91"}, {"24 ", "91"}, {"25 ", "91"}, {"26 ", "91"}, {"27 ", "91"}, {"28 ", "91"}, {"29 ", "91"}, {"30 ", "91"}, {"31 ", "91"}, {"32 ", "91"}, {"33 ", "91"}, {"34 ", "91"}, {"35 ", "91"}, {"36 ", "91"}, {"37 ", "91"}, {"38 ", "91"}, {"39 ", "91"}, {"40 ", "91"}, {"41 ", "91"}, {"42 ", "91"}, {"43 ", "91"}, {"44 ", "91"}, {"45 ", "91"}, {"46 ", "91"}, {"47 ", "91"}, {"48 ", "91"}, {"49 ", "91"}, {"50 ", "91"}, {"51 ", "91"}, {"52 ", "91"}, {"53 ", "91"}, {"54 ", "91"}, {"55 ", "91"}, {"56 ", "91"}, {"57 ", "91"}, {"58 ", "91"}, {"59 ", "91"}, {"60 ", "91"}, {"61 ", "91"}, {"62 ", "91"}, {"63 ", "91"}, {"64 ", "91"}, {"1 ", "92"}, {"2 ", "92"}, {"3 ", "92"}, {"4 ", "92"}, {"5 ", "92"}, {"6 ", "92"}, {"7 ", "92"}, {"8 ", "92"}, {"9 ", "92"}, {"10 ", "92"}, {"11 ", "92"}, {"12 ", "92"}, {"13 ", "92"}, {"14 ", "92"}, {"15 ", "92"}, {"16 ", "92"}, {"17 ", "92"}, {"18 ", "92"}, {"19 ", "92"}, {"20 ", "92"}, {"21 ", "92"}, {"22 ", "92"}, {"23 ", "92"}, {"24 ", "92"}, {"25 ", "92"}, {"26 ", "92"}, {"27 ", "92"}, {"28 ", "92"}, {"29 ", "92"}, {"30 ", "92"}, {"31 ", "92"}, {"32 ", "92"}, {"33 ", "92"}, {"34 ", "92"}, {"35 ", "92"}, {"36 ", "92"}, {"37 ", "92"}, {"38 ", "92"}, {"39 ", "92"}, {"40 ", "92"}, {"41 ", "92"}, {"42 ", "92"}, {"43 ", "92"}, {"44 ", "92"}, {"45 ", "92"}, {"46 ", "92"}, {"47 ", "92"}, {"48 ", "92"}, {"49 ", "92"}, {"50 ", "92"}, {"51 ", "92"}, {"52 ", "92"}, {"53 ", "92"}, {"54 ", "92"}, {"55 ", "92"}, {"56 ", "92"}, {"57 ", "92"}, {"58 ", "92"}, {"59 ", "92"}, {"60 ", "92"}, {"61 ", "92"}, {"62 ", "92"}, {"63 ", "92"}, {"64 ", "92"}, {"1 ", "93"}, {"2 ", "93"}, {"3 ", "93"}, {"4 ", "93"}, {"5 ", "93"}, {"6 ", "93"}, {"7 ", "93"}, {"8 ", "93"}, {"9 ", "93"}, {"10 ", "93"}, {"11 ", "93"}, {"12 ", "93"}, {"13 ", "93"}, {"14 ", "93"}, {"15 ", "93"}, {"16 ", "93"}, {"17 ", "93"}, {"18 ", "93"}, {"19 ", "93"}, {"20 ", "93"}, {"21 ", "93"}, {"22 ", "93"}, {"23 ", "93"}, {"24 ", "93"}, {"25 ", "93"}, {"26 ", "93"}, {"27 ", "93"}, {"28 ", "93"}, {"29 ", "93"}, {"30 ", "93"}, {"31 ", "93"}, {"32 ", "93"}, {"33 ", "93"}, {"34 ", "93"}, {"35 ", "93"}, {"36 ", "93"}, {"37 ", "93"}, {"38 ", "93"}, {"39 ", "93"}, {"40 ", "93"}, {"41 ", "93"}, {"42 ", "93"}, {"43 ", "93"}, {"44 ", "93"}, {"45 ", "93"}, {"46 ", "93"}, {"47 ", "93"}, {"48 ", "93"}, {"49 ", "93"}, {"50 ", "93"}, {"51 ", "93"}, {"52 ", "93"}, {"53 ", "93"}, {"54 ", "93"}, {"55 ", "93"}, {"56 ", "93"}, {"57 ", "93"}, {"58 ", "93"}, {"59 ", "93"}, {"60 ", "93"}, {"61 ", "93"}, {"62 ", "93"}, {"63 ", "93"}, {"64 ", "93"}, {"1 ", "94"}, {"2 ", "94"}, {"3 ", "94"}, {"4 ", "94"}, {"5 ", "94"}, {"6 ", "94"}, {"7 ", "94"}, {"8 ", "94"}, {"9 ", "94"}, {"10 ", "94"}, {"11 ", "94"}, {"12 ", "94"}, {"13 ", "94"}, {"14 ", "94"}, {"15 ", "94"}, {"16 ", "94"}, {"17 ", "94"}, {"18 ", "94"}, {"19 ", "94"}, {"20 ", "94"}, {"21 ", "94"}, {"22 ", "94"}, {"23 ", "94"}, {"24 ", "94"}, {"25 ", "94"}, {"26 ", "94"}, {"27 ", "94"}, {"28 ", "94"}, {"29 ", "94"}, {"30 ", "94"}, {"31 ", "94"}, {"32 ", "94"}, {"33 ", "94"}, {"34 ", "94"}, {"35 ", "94"}, {"36 ", "94"}, {"37 ", "94"}, {"38 ", "94"}, {"39 ", "94"}, {"40 ", "94"}, {"41 ", "94"}, {"42 ", "94"}, {"43 ", "94"}, {"44 ", "94"}, {"45 ", "94"}, {"46 ", "94"}, {"47 ", "94"}, {"48 ", "94"}, {"49 ", "94"}, {"50 ", "94"}, {"51 ", "94"}, {"52 ", "94"}, {"53 ", "94"}, {"54 ", "94"}, {"55 ", "94"}, {"56 ", "94"}, {"57 ", "94"}, {"58 ", "94"}, {"59 ", "94"}, {"60 ", "94"}, {"61 ", "94"}, {"62 ", "94"}, {"63 ", "94"}, {"64 ", "94"}, {"1 ", "95"}, {"2 ", "95"}, {"3 ", "95"}, {"4 ", "95"}, {"5 ", "95"}, {"6 ", "95"}, {"7 ", "95"}, {"8 ", "95"}, {"9 ", "95"}, {"10 ", "95"}, {"11 ", "95"}, {"12 ", "95"}, {"13 ", "95"}, {"14 ", "95"}, {"15 ", "95"}, {"16 ", "95"}, {"17 ", "95"}, {"18 ", "95"}, {"19 ", "95"}, {"20 ", "95"}, {"21 ", "95"}, {"22 ", "95"}, {"23 ", "95"}, {"24 ", "95"}, {"25 ", "95"}, {"26 ", "95"}, {"27 ", "95"}, {"28 ", "95"}, {"29 ", "95"}, {"30 ", "95"}, {"31 ", "95"}, {"32 ", "95"}, {"33 ", "95"}, {"34 ", "95"}, {"35 ", "95"}, {"36 ", "95"}, {"37 ", "95"}, {"38 ", "95"}, {"39 ", "95"}, {"40 ", "95"}, {"41 ", "95"}, {"42 ", "95"}, {"43 ", "95"}, {"44 ", "95"}, {"45 ", "95"}, {"46 ", "95"}, {"47 ", "95"}, {"48 ", "95"}, {"49 ", "95"}, {"50 ", "95"}, {"51 ", "95"}, {"52 ", "95"}, {"53 ", "95"}, {"54 ", "95"}, {"55 ", "95"}, {"56 ", "95"}, {"57 ", "95"}, {"58 ", "95"}, {"59 ", "95"}, {"60 ", "95"}, {"61 ", "95"}, {"62 ", "95"}, {"63 ", "95"}, {"64 ", "95"}, {"1 ", "96"}, {"2 ", "96"}, {"3 ", "96"}, {"4 ", "96"}, {"5 ", "96"}, {"6 ", "96"}, {"7 ", "96"}, {"8 ", "96"}, {"9 ", "96"}, {"10 ", "96"}, {"11 ", "96"}, {"12 ", "96"}, {"13 ", "96"}, {"14 ", "96"}, {"15 ", "96"}, {"16 ", "96"}, {"17 ", "96"}, {"18 ", "96"}, {"19 ", "96"}, {"20 ", "96"}, {"21 ", "96"}, {"22 ", "96"}, {"23 ", "96"}, {"24 ", "96"}, {"25 ", "96"}, {"26 ", "96"}, {"27 ", "96"}, {"28 ", "96"}, {"29 ", "96"}, {"30 ", "96"}, {"31 ", "96"}, {"32 ", "96"}, {"33 ", "96"}, {"34 ", "96"}, {"35 ", "96"}, {"36 ", "96"}, {"37 ", "96"}, {"38 ", "96"}, {"39 ", "96"}, {"40 ", "96"}, {"41 ", "96"}, {"42 ", "96"}, {"43 ", "96"}, {"44 ", "96"}, {"45 ", "96"}, {"46 ", "96"}, {"47 ", "96"}, {"48 ", "96"}, {"49 ", "96"}, {"50 ", "96"}, {"51 ", "96"}, {"52 ", "96"}, {"53 ", "96"}, {"54 ", "96"}, {"55 ", "96"}, {"56 ", "96"}, {"57 ", "96"}, {"58 ", "96"}, {"59 ", "96"}, {"60 ", "96"}, {"61 ", "96"}, {"62 ", "96"}, {"63 ", "96"}, {"64 ", "96"}, {"1 ", "97"}, {"2 ", "97"}, {"3 ", "97"}, {"4 ", "97"}, {"5 ", "97"}, {"6 ", "97"}, {"7 ", "97"}, {"8 ", "97"}, {"9 ", "97"}, {"10 ", "97"}, {"11 ", "97"}, {"12 ", "97"}, {"13 ", "97"}, {"14 ", "97"}, {"15 ", "97"}, {"16 ", "97"}, {"17 ", "97"}, {"18 ", "97"}, {"19 ", "97"}, {"20 ", "97"}, {"21 ", "97"}, {"22 ", "97"}, {"23 ", "97"}, {"24 ", "97"}, {"25 ", "97"}, {"26 ", "97"}, {"27 ", "97"}, {"28 ", "97"}, {"29 ", "97"}, {"30 ", "97"}, {"31 ", "97"}, {"32 ", "97"}, {"33 ", "97"}, {"34 ", "97"}, {"35 ", "97"}, {"36 ", "97"}, {"37 ", "97"}, {"38 ", "97"}, {"39 ", "97"}, {"40 ", "97"}, {"41 ", "97"}, {"42 ", "97"}, {"43 ", "97"}, {"44 ", "97"}, {"45 ", "97"}, {"46 ", "97"}, {"47 ", "97"}, {"48 ", "97"}, {"49 ", "97"}, {"50 ", "97"}, {"51 ", "97"}, {"52 ", "97"}, {"53 ", "97"}, {"54 ", "97"}, {"55 ", "97"}, {"56 ", "97"}, {"57 ", "97"}, {"58 ", "97"}, {"59 ", "97"}, {"60 ", "97"}, {"61 ", "97"}, {"62 ", "97"}, {"63 ", "97"}, {"64 ", "97"}, {"1 ", "98"}, {"2 ", "98"}, {"3 ", "98"}, {"4 ", "98"}, {"5 ", "98"}, {"6 ", "98"}, {"7 ", "98"}, {"8 ", "98"}, {"9 ", "98"}, {"10 ", "98"}, {"11 ", "98"}, {"12 ", "98"}, {"13 ", "98"}, {"14 ", "98"}, {"15 ", "98"}, {"16 ", "98"}, {"17 ", "98"}, {"18 ", "98"}, {"19 ", "98"}, {"20 ", "98"}, {"21 ", "98"}, {"22 ", "98"}, {"23 ", "98"}, {"24 ", "98"}, {"25 ", "98"}, {"26 ", "98"}, {"27 ", "98"}, {"28 ", "98"}, {"29 ", "98"}, {"30 ", "98"}, {"31 ", "98"}, {"32 ", "98"}, {"33 ", "98"}, {"34 ", "98"}, {"35 ", "98"}, {"36 ", "98"}, {"37 ", "98"}, {"38 ", "98"}, {"39 ", "98"}, {"40 ", "98"}, {"41 ", "98"}, {"42 ", "98"}, {"43 ", "98"}, {"44 ", "98"}, {"45 ", "98"}, {"46 ", "98"}, {"47 ", "98"}, {"48 ", "98"}, {"49 ", "98"}, {"50 ", "98"}, {"51 ", "98"}, {"52 ", "98"}, {"53 ", "98"}, {"54 ", "98"}, {"55 ", "98"}, {"56 ", "98"}, {"57 ", "98"}, {"58 ", "98"}, {"59 ", "98"}, {"60 ", "98"}, {"61 ", "98"}, {"62 ", "98"}, {"63 ", "98"}, {"64 ", "98"}, {"1 ", "99"}, {"2 ", "99"}, {"3 ", "99"}, {"4 ", "99"}, {"5 ", "99"}, {"6 ", "99"}, {"7 ", "99"}, {"8 ", "99"}, {"9 ", "99"}, {"10 ", "99"}, {"11 ", "99"}, {"12 ", "99"}, {"13 ", "99"}, {"14 ", "99"}, {"15 ", "99"}, {"16 ", "99"}, {"17 ", "99"}, {"18 ", "99"}, {"19 ", "99"}, {"20 ", "99"}, {"21 ", "99"}, {"22 ", "99"}, {"23 ", "99"}, {"24 ", "99"}, {"25 ", "99"}, {"26 ", "99"}, {"27 ", "99"}, {"28 ", "99"}, {"29 ", "99"}, {"30 ", "99"}, {"31 ", "99"}, {"32 ", "99"}, {"33 ", "99"}, {"34 ", "99"}, {"35 ", "99"}, {"36 ", "99"}, {"37 ", "99"}, {"38 ", "99"}, {"39 ", "99"}, {"40 ", "99"}, {"41 ", "99"}, {"42 ", "99"}, {"43 ", "99"}, {"44 ", "99"}, {"45 ", "99"}, {"46 ", "99"}, {"47 ", "99"}, {"48 ", "99"}, {"49 ", "99"}, {"50 ", "99"}, {"51 ", "99"}, {"52 ", "99"}, {"53 ", "99"}, {"54 ", "99"}, {"55 ", "99"}, {"56 ", "99"}, {"57 ", "99"}, {"58 ", "99"}, {"59 ", "99"}, {"60 ", "99"}, {"61 ", "99"}, {"62 ", "99"}, {"63 ", "99"}, {"64 ", "99"}, {"1 ", "100"}, {"2 ", "100"}, {"3 ", "100"}, {"4 ", "100"}, {"5 ", "100"}, {"6 ", "100"}, {"7 ", "100"}, {"8 ", "100"}, {"9 ", "100"}, {"10 ", "100"}, {"11 ", "100"}, {"12 ", "100"}, {"13 ", "100"}, {"14 ", "100"}, {"15 ", "100"}, {"16 ", "100"}, {"17 ", "100"}, {"18 ", "100"}, {"19 ", "100"}, {"20 ", "100"}, {"21 ", "100"}, {"22 ", "100"}, {"23 ", "100"}, {"24 ", "100"}, {"25 ", "100"}, {"26 ", "100"}, {"27 ", "100"}, {"28 ", "100"}, {"29 ", "100"}, {"30 ", "100"}, {"31 ", "100"}, {"32 ", "100"}, {"33 ", "100"}, {"34 ", "100"}, {"35 ", "100"}, {"36 ", "100"}, {"37 ", "100"}, {"38 ", "100"}, {"39 ", "100"}, {"40 ", "100"}, {"41 ", "100"}, {"42 ", "100"}, {"43 ", "100"}, {"44 ", "100"}, {"45 ", "100"}, {"46 ", "100"}, {"47 ", "100"}, {"48 ", "100"}, {"49 ", "100"}, {"50 ", "100"}, {"51 ", "100"}, {"52 ", "100"}, {"53 ", "100"}, {"54 ", "100"}, {"55 ", "100"}, {"56 ", "100"}, {"57 ", "100"}, {"58 ", "100"}, {"59 ", "100"}, {"60 ", "100"}, {"61 ", "100"}, {"62 ", "100"}, {"63 ", "100"}, {"64 ", "100"}, {"1 ", "101"}, {"2 ", "101"}, {"3 ", "101"}, {"4 ", "101"}, {"5 ", "101"}, {"6 ", "101"}, {"7 ", "101"}, {"8 ", "101"}, {"9 ", "101"}, {"10 ", "101"}, {"11 ", "101"}, {"12 ", "101"}, {"13 ", "101"}, {"14 ", "101"}, {"15 ", "101"}, {"16 ", "101"}, {"17 ", "101"}, {"18 ", "101"}, {"19 ", "101"}, {"20 ", "101"}, {"21 ", "101"}, {"22 ", "101"}, {"23 ", "101"}, {"24 ", "101"}, {"25 ", "101"}, {"26 ", "101"}, {"27 ", "101"}, {"28 ", "101"}, {"29 ", "101"}, {"30 ", "101"}, {"31 ", "101"}, {"32 ", "101"}, {"33 ", "101"}, {"34 ", "101"}, {"35 ", "101"}, {"36 ", "101"}, {"37 ", "101"}, {"38 ", "101"}, {"39 ", "101"}, {"40 ", "101"}, {"41 ", "101"}, {"42 ", "101"}, {"43 ", "101"}, {"44 ", "101"}, {"45 ", "101"}, {"46 ", "101"}, {"47 ", "101"}, {"48 ", "101"}, {"49 ", "101"}, {"50 ", "101"}, {"51 ", "101"}, {"52 ", "101"}, {"53 ", "101"}, {"54 ", "101"}, {"55 ", "101"}, {"56 ", "101"}, {"57 ", "101"}, {"58 ", "101"}, {"59 ", "101"}, {"60 ", "101"}, {"61 ", "101"}, {"62 ", "101"}, {"63 ", "101"}, {"64 ", "101"}, {"1 ", "102"}, {"2 ", "102"}, {"3 ", "102"}, {"4 ", "102"}, {"5 ", "102"}, {"6 ", "102"}, {"7 ", "102"}, {"8 ", "102"}, {"9 ", "102"}, {"10 ", "102"}, {"11 ", "102"}, {"12 ", "102"}, {"13 ", "102"}, {"14 ", "102"}, {"15 ", "102"}, {"16 ", "102"}, {"17 ", "102"}, {"18 ", "102"}, {"19 ", "102"}, {"20 ", "102"}, {"21 ", "102"}, {"22 ", "102"}, {"23 ", "102"}, {"24 ", "102"}, {"25 ", "102"}, {"26 ", "102"}, {"27 ", "102"}, {"28 ", "102"}, {"29 ", "102"}, {"30 ", "102"}, {"31 ", "102"}, {"32 ", "102"}, {"33 ", "102"}, {"34 ", "102"}, {"35 ", "102"}, {"36 ", "102"}, {"37 ", "102"}, {"38 ", "102"}, {"39 ", "102"}, {"40 ", "102"}, {"41 ", "102"}, {"42 ", "102"}, {"43 ", "102"}, {"44 ", "102"}, {"45 ", "102"}, {"46 ", "102"}, {"47 ", "102"}, {"48 ", "102"}, {"49 ", "102"}, {"50 ", "102"}, {"51 ", "102"}, {"52 ", "102"}, {"53 ", "102"}, {"54 ", "102"}, {"55 ", "102"}, {"56 ", "102"}, {"57 ", "102"}, {"58 ", "102"}, {"59 ", "102"}, {"60 ", "102"}, {"61 ", "102"}, {"62 ", "102"}, {"63 ", "102"}, {"64 ", "102"}, {"1 ", "103"}, {"2 ", "103"}, {"3 ", "103"}, {"4 ", "103"}, {"5 ", "103"}, {"6 ", "103"}, {"7 ", "103"}, {"8 ", "103"}, {"9 ", "103"}, {"10 ", "103"}, {"11 ", "103"}, {"12 ", "103"}, {"13 ", "103"}, {"14 ", "103"}, {"15 ", "103"}, {"16 ", "103"}, {"17 ", "103"}, {"18 ", "103"}, {"19 ", "103"}, {"20 ", "103"}, {"21 ", "103"}, {"22 ", "103"}, {"23 ", "103"}, {"24 ", "103"}, {"25 ", "103"}, {"26 ", "103"}, {"27 ", "103"}, {"28 ", "103"}, {"29 ", "103"}, {"30 ", "103"}, {"31 ", "103"}, {"32 ", "103"}, {"33 ", "103"}, {"34 ", "103"}, {"35 ", "103"}, {"36 ", "103"}, {"37 ", "103"}, {"38 ", "103"}, {"39 ", "103"}, {"40 ", "103"}, {"41 ", "103"}, {"42 ", "103"}, {"43 ", "103"}, {"44 ", "103"}, {"45 ", "103"}, {"46 ", "103"}, {"47 ", "103"}, {"48 ", "103"}, {"49 ", "103"}, {"50 ", "103"}, {"51 ", "103"}, {"52 ", "103"}, {"53 ", "103"}, {"54 ", "103"}, {"55 ", "103"}, {"56 ", "103"}, {"57 ", "103"}, {"58 ", "103"}, {"59 ", "103"}, {"60 ", "103"}, {"61 ", "103"}, {"62 ", "103"}, {"63 ", "103"}, {"64 ", "103"}, {"1 ", "104"}, {"2 ", "104"}, {"3 ", "104"}, {"4 ", "104"}, {"5 ", "104"}, {"6 ", "104"}, {"7 ", "104"}, {"8 ", "104"}, {"9 ", "104"}, {"10 ", "104"}, {"11 ", "104"}, {"12 ", "104"}, {"13 ", "104"}, {"14 ", "104"}, {"15 ", "104"}, {"16 ", "104"}, {"17 ", "104"}, {"18 ", "104"}, {"19 ", "104"}, {"20 ", "104"}, {"21 ", "104"}, {"22 ", "104"}, {"23 ", "104"}, {"24 ", "104"}, {"25 ", "104"}, {"26 ", "104"}, {"27 ", "104"}, {"28 ", "104"}, {"29 ", "104"}, {"30 ", "104"}, {"31 ", "104"}, {"32 ", "104"}, {"33 ", "104"}, {"34 ", "104"}, {"35 ", "104"}, {"36 ", "104"}, {"37 ", "104"}, {"38 ", "104"}, {"39 ", "104"}, {"40 ", "104"}, {"41 ", "104"}, {"42 ", "104"}, {"43 ", "104"}, {"44 ", "104"}, {"45 ", "104"}, {"46 ", "104"}, {"47 ", "104"}, {"48 ", "104"}, {"49 ", "104"}, {"50 ", "104"}, {"51 ", "104"}, {"52 ", "104"}, {"53 ", "104"}, {"54 ", "104"}, {"55 ", "104"}, {"56 ", "104"}, {"57 ", "104"}, {"58 ", "104"}, {"59 ", "104"}, {"60 ", "104"}, {"61 ", "104"}, {"62 ", "104"}, {"63 ", "104"}, {"64 ", "104"}, {"1 ", "105"}, {"2 ", "105"}, {"3 ", "105"}, {"4 ", "105"}, {"5 ", "105"}, {"6 ", "105"}, {"7 ", "105"}, {"8 ", "105"}, {"9 ", "105"}, {"10 ", "105"}, {"11 ", "105"}, {"12 ", "105"}, {"13 ", "105"}, {"14 ", "105"}, {"15 ", "105"}, {"16 ", "105"}, {"17 ", "105"}, {"18 ", "105"}, {"19 ", "105"}, {"20 ", "105"}, {"21 ", "105"}, {"22 ", "105"}, {"23 ", "105"}, {"24 ", "105"}, {"25 ", "105"}, {"26 ", "105"}, {"27 ", "105"}, {"28 ", "105"}, {"29 ", "105"}, {"30 ", "105"}, {"31 ", "105"}, {"32 ", "105"}, {"33 ", "105"}, {"34 ", "105"}, {"35 ", "105"}, {"36 ", "105"}, {"37 ", "105"}, {"38 ", "105"}, {"39 ", "105"}, {"40 ", "105"}, {"41 ", "105"}, {"42 ", "105"}, {"43 ", "105"}, {"44 ", "105"}, {"45 ", "105"}, {"46 ", "105"}, {"47 ", "105"}, {"48 ", "105"}, {"49 ", "105"}, {"50 ", "105"}, {"51 ", "105"}, {"52 ", "105"}, {"53 ", "105"}, {"54 ", "105"}, {"55 ", "105"}, {"56 ", "105"}, {"57 ", "105"}, {"58 ", "105"}, {"59 ", "105"}, {"60 ", "105"}, {"61 ", "105"}, {"62 ", "105"}, {"63 ", "105"}, {"64 ", "105"}, {"1 ", "106"}, {"2 ", "106"}, {"3 ", "106"}, {"4 ", "106"}, {"5 ", "106"}, {"6 ", "106"}, {"7 ", "106"}, {"8 ", "106"}, {"9 ", "106"}, {"10 ", "106"}, {"11 ", "106"}, {"12 ", "106"}, {"13 ", "106"}, {"14 ", "106"}, {"15 ", "106"}, {"16 ", "106"}, {"17 ", "106"}, {"18 ", "106"}, {"19 ", "106"}, {"20 ", "106"}, {"21 ", "106"}, {"22 ", "106"}, {"23 ", "106"}, {"24 ", "106"}, {"25 ", "106"}, {"26 ", "106"}, {"27 ", "106"}, {"28 ", "106"}, {"29 ", "106"}, {"30 ", "106"}, {"31 ", "106"}, {"32 ", "106"}, {"33 ", "106"}, {"34 ", "106"}, {"35 ", "106"}, {"36 ", "106"}, {"37 ", "106"}, {"38 ", "106"}, {"39 ", "106"}, {"40 ", "106"}, {"41 ", "106"}, {"42 ", "106"}, {"43 ", "106"}, {"44 ", "106"}, {"45 ", "106"}, {"46 ", "106"}, {"47 ", "106"}, {"48 ", "106"}, {"49 ", "106"}, {"50 ", "106"}, {"51 ", "106"}, {"52 ", "106"}, {"53 ", "106"}, {"54 ", "106"}, {"55 ", "106"}, {"56 ", "106"}, {"57 ", "106"}, {"58 ", "106"}, {"59 ", "106"}, {"60 ", "106"}, {"61 ", "106"}, {"62 ", "106"}, {"63 ", "106"}, {"64 ", "106"}, {"1 ", "107"}, {"2 ", "107"}, {"3 ", "107"}, {"4 ", "107"}, {"5 ", "107"}, {"6 ", "107"}, {"7 ", "107"}, {"8 ", "107"}, {"9 ", "107"}, {"10 ", "107"}, {"11 ", "107"}, {"12 ", "107"}, {"13 ", "107"}, {"14 ", "107"}, {"15 ", "107"}, {"16 ", "107"}, {"17 ", "107"}, {"18 ", "107"}, {"19 ", "107"}, {"20 ", "107"}, {"21 ", "107"}, {"22 ", "107"}, {"23 ", "107"}, {"24 ", "107"}, {"25 ", "107"}, {"26 ", "107"}, {"27 ", "107"}, {"28 ", "107"}, {"29 ", "107"}, {"30 ", "107"}, {"31 ", "107"}, {"32 ", "107"}, {"33 ", "107"}, {"34 ", "107"}, {"35 ", "107"}, {"36 ", "107"}, {"37 ", "107"}, {"38 ", "107"}, {"39 ", "107"}, {"40 ", "107"}, {"41 ", "107"}, {"42 ", "107"}, {"43 ", "107"}, {"44 ", "107"}, {"45 ", "107"}, {"46 ", "107"}, {"47 ", "107"}, {"48 ", "107"}, {"49 ", "107"}, {"50 ", "107"}, {"51 ", "107"}, {"52 ", "107"}, {"53 ", "107"}, {"54 ", "107"}, {"55 ", "107"}, {"56 ", "107"}, {"57 ", "107"}, {"58 ", "107"}, {"59 ", "107"}, {"60 ", "107"}, {"61 ", "107"}, {"62 ", "107"}, {"63 ", "107"}, {"64 ", "107"}, {"1 ", "108"}, {"2 ", "108"}, {"3 ", "108"}, {"4 ", "108"}, {"5 ", "108"}, {"6 ", "108"}, {"7 ", "108"}, {"8 ", "108"}, {"9 ", "108"}, {"10 ", "108"}, {"11 ", "108"}, {"12 ", "108"}, {"13 ", "108"}, {"14 ", "108"}, {"15 ", "108"}, {"16 ", "108"}, {"17 ", "108"}, {"18 ", "108"}, {"19 ", "108"}, {"20 ", "108"}, {"21 ", "108"}, {"22 ", "108"}, {"23 ", "108"}, {"24 ", "108"}, {"25 ", "108"}, {"26 ", "108"}, {"27 ", "108"}, {"28 ", "108"}, {"29 ", "108"}, {"30 ", "108"}, {"31 ", "108"}, {"32 ", "108"}, {"33 ", "108"}, {"34 ", "108"}, {"35 ", "108"}, {"36 ", "108"}, {"37 ", "108"}, {"38 ", "108"}, {"39 ", "108"}, {"40 ", "108"}, {"41 ", "108"}, {"42 ", "108"}, {"43 ", "108"}, {"44 ", "108"}, {"45 ", "108"}, {"46 ", "108"}, {"47 ", "108"}, {"48 ", "108"}, {"49 ", "108"}, {"50 ", "108"}, {"51 ", "108"}, {"52 ", "108"}, {"53 ", "108"}, {"54 ", "108"}, {"55 ", "108"}, {"56 ", "108"}, {"57 ", "108"}, {"58 ", "108"}, {"59 ", "108"}, {"60 ", "108"}, {"61 ", "108"}, {"62 ", "108"}, {"63 ", "108"}, {"64 ", "108"}, {"1 ", "109"}, {"2 ", "109"}, {"3 ", "109"}, {"4 ", "109"}, {"5 ", "109"}, {"6 ", "109"}, {"7 ", "109"}, {"8 ", "109"}, {"9 ", "109"}, {"10 ", "109"}, {"11 ", "109"}, {"12 ", "109"}, {"13 ", "109"}, {"14 ", "109"}, {"15 ", "109"}, {"16 ", "109"}, {"17 ", "109"}, {"18 ", "109"}, {"19 ", "109"}, {"20 ", "109"}, {"21 ", "109"}, {"22 ", "109"}, {"23 ", "109"}, {"24 ", "109"}, {"25 ", "109"}, {"26 ", "109"}, {"27 ", "109"}, {"28 ", "109"}, {"29 ", "109"}, {"30 ", "109"}, {"31 ", "109"}, {"32 ", "109"}, {"33 ", "109"}, {"34 ", "109"}, {"35 ", "109"}, {"36 ", "109"}, {"37 ", "109"}, {"38 ", "109"}, {"39 ", "109"}, {"40 ", "109"}, {"41 ", "109"}, {"42 ", "109"}, {"43 ", "109"}, {"44 ", "109"}, {"45 ", "109"}, {"46 ", "109"}, {"47 ", "109"}, {"48 ", "109"}, {"49 ", "109"}, {"50 ", "109"}, {"51 ", "109"}, {"52 ", "109"}, {"53 ", "109"}, {"54 ", "109"}, {"55 ", "109"}, {"56 ", "109"}, {"57 ", "109"}, {"58 ", "109"}, {"59 ", "109"}, {"60 ", "109"}, {"61 ", "109"}, {"62 ", "109"}, {"63 ", "109"}, {"64 ", "109"}, {"1 ", "110"}, {"2 ", "110"}, {"3 ", "110"}, {"4 ", "110"}, {"5 ", "110"}, {"6 ", "110"}, {"7 ", "110"}, {"8 ", "110"}, {"9 ", "110"}, {"10 ", "110"}, {"11 ", "110"}, {"12 ", "110"}, {"13 ", "110"}, {"14 ", "110"}, {"15 ", "110"}, {"16 ", "110"}, {"17 ", "110"}, {"18 ", "110"}, {"19 ", "110"}, {"20 ", "110"}, {"21 ", "110"}, {"22 ", "110"}, {"23 ", "110"}, {"24 ", "110"}, {"25 ", "110"}, {"26 ", "110"}, {"27 ", "110"}, {"28 ", "110"}, {"29 ", "110"}, {"30 ", "110"}, {"31 ", "110"}, {"32 ", "110"}, {"33 ", "110"}, {"34 ", "110"}, {"35 ", "110"}, {"36 ", "110"}, {"37 ", "110"}, {"38 ", "110"}, {"39 ", "110"}, {"40 ", "110"}, {"41 ", "110"}, {"42 ", "110"}, {"43 ", "110"}, {"44 ", "110"}, {"45 ", "110"}, {"46 ", "110"}, {"47 ", "110"}, {"48 ", "110"}, {"49 ", "110"}, {"50 ", "110"}, {"51 ", "110"}, {"52 ", "110"}, {"53 ", "110"}, {"54 ", "110"}, {"55 ", "110"}, {"56 ", "110"}, {"57 ", "110"}, {"58 ", "110"}, {"59 ", "110"}, {"60 ", "110"}, {"61 ", "110"}, {"62 ", "110"}, {"63 ", "110"}, {"64 ", "110"}, {"1 ", "111"}, {"2 ", "111"}, {"3 ", "111"}, {"4 ", "111"}, {"5 ", "111"}, {"6 ", "111"}, {"7 ", "111"}, {"8 ", "111"}, {"9 ", "111"}, {"10 ", "111"}, {"11 ", "111"}, {"12 ", "111"}, {"13 ", "111"}, {"14 ", "111"}, {"15 ", "111"}, {"16 ", "111"}, {"17 ", "111"}, {"18 ", "111"}, {"19 ", "111"}, {"20 ", "111"}, {"21 ", "111"}, {"22 ", "111"}, {"23 ", "111"}, {"24 ", "111"}, {"25 ", "111"}, {"26 ", "111"}, {"27 ", "111"}, {"28 ", "111"}, {"29 ", "111"}, {"30 ", "111"}, {"31 ", "111"}, {"32 ", "111"}, {"33 ", "111"}, {"34 ", "111"}, {"35 ", "111"}, {"36 ", "111"}, {"37 ", "111"}, {"38 ", "111"}, {"39 ", "111"}, {"40 ", "111"}, {"41 ", "111"}, {"42 ", "111"}, {"43 ", "111"}, {"44 ", "111"}, {"45 ", "111"}, {"46 ", "111"}, {"47 ", "111"}, {"48 ", "111"}, {"49 ", "111"}, {"50 ", "111"}, {"51 ", "111"}, {"52 ", "111"}, {"53 ", "111"}, {"54 ", "111"}, {"55 ", "111"}, {"56 ", "111"}, {"57 ", "111"}, {"58 ", "111"}, {"59 ", "111"}, {"60 ", "111"}, {"61 ", "111"}, {"62 ", "111"}, {"63 ", "111"}, {"64 ", "111"}, {"1 ", "112"}, {"2 ", "112"}, {"3 ", "112"}, {"4 ", "112"}, {"5 ", "112"}, {"6 ", "112"}, {"7 ", "112"}, {"8 ", "112"}, {"9 ", "112"}, {"10 ", "112"}, {"11 ", "112"}, {"12 ", "112"}, {"13 ", "112"}, {"14 ", "112"}, {"15 ", "112"}, {"16 ", "112"}, {"17 ", "112"}, {"18 ", "112"}, {"19 ", "112"}, {"20 ", "112"}, {"21 ", "112"}, {"22 ", "112"}, {"23 ", "112"}, {"24 ", "112"}, {"25 ", "112"}, {"26 ", "112"}, {"27 ", "112"}, {"28 ", "112"}, {"29 ", "112"}, {"30 ", "112"}, {"31 ", "112"}, {"32 ", "112"}, {"33 ", "112"}, {"34 ", "112"}, {"35 ", "112"}, {"36 ", "112"}, {"37 ", "112"}, {"38 ", "112"}, {"39 ", "112"}, {"40 ", "112"}, {"41 ", "112"}, {"42 ", "112"}, {"43 ", "112"}, {"44 ", "112"}, {"45 ", "112"}, {"46 ", "112"}, {"47 ", "112"}, {"48 ", "112"}, {"49 ", "112"}, {"50 ", "112"}, {"51 ", "112"}, {"52 ", "112"}, {"53 ", "112"}, {"54 ", "112"}, {"55 ", "112"}, {"56 ", "112"}, {"57 ", "112"}, {"58 ", "112"}, {"59 ", "112"}, {"60 ", "112"}, {"61 ", "112"}, {"62 ", "112"}, {"63 ", "112"}, {"64 ", "112"}, {"1 ", "113"}, {"2 ", "113"}, {"3 ", "113"}, {"4 ", "113"}, {"5 ", "113"}, {"6 ", "113"}, {"7 ", "113"}, {"8 ", "113"}, {"9 ", "113"}, {"10 ", "113"}, {"11 ", "113"}, {"12 ", "113"}, {"13 ", "113"}, {"14 ", "113"}, {"15 ", "113"}, {"16 ", "113"}, {"17 ", "113"}, {"18 ", "113"}, {"19 ", "113"}, {"20 ", "113"}, {"21 ", "113"}, {"22 ", "113"}, {"23 ", "113"}, {"24 ", "113"}, {"25 ", "113"}, {"26 ", "113"}, {"27 ", "113"}, {"28 ", "113"}, {"29 ", "113"}, {"30 ", "113"}, {"31 ", "113"}, {"32 ", "113"}, {"33 ", "113"}, {"34 ", "113"}, {"35 ", "113"}, {"36 ", "113"}, {"37 ", "113"}, {"38 ", "113"}, {"39 ", "113"}, {"40 ", "113"}, {"41 ", "113"}, {"42 ", "113"}, {"43 ", "113"}, {"44 ", "113"}, {"45 ", "113"}, {"46 ", "113"}, {"47 ", "113"}, {"48 ", "113"}, {"49 ", "113"}, {"50 ", "113"}, {"51 ", "113"}, {"52 ", "113"}, {"53 ", "113"}, {"54 ", "113"}, {"55 ", "113"}, {"56 ", "113"}, {"57 ", "113"}, {"58 ", "113"}, {"59 ", "113"}, {"60 ", "113"}, {"61 ", "113"}, {"62 ", "113"}, {"63 ", "113"}, {"64 ", "113"}, {"1 ", "114"}, {"2 ", "114"}, {"3 ", "114"}, {"4 ", "114"}, {"5 ", "114"}, {"6 ", "114"}, {"7 ", "114"}, {"8 ", "114"}, {"9 ", "114"}, {"10 ", "114"}, {"11 ", "114"}, {"12 ", "114"}, {"13 ", "114"}, {"14 ", "114"}, {"15 ", "114"}, {"16 ", "114"}, {"17 ", "114"}, {"18 ", "114"}, {"19 ", "114"}, {"20 ", "114"}, {"21 ", "114"}, {"22 ", "114"}, {"23 ", "114"}, {"24 ", "114"}, {"25 ", "114"}, {"26 ", "114"}, {"27 ", "114"}, {"28 ", "114"}, {"29 ", "114"}, {"30 ", "114"}, {"31 ", "114"}, {"32 ", "114"}, {"33 ", "114"}, {"34 ", "114"}, {"35 ", "114"}, {"36 ", "114"}, {"37 ", "114"}, {"38 ", "114"}, {"39 ", "114"}, {"40 ", "114"}, {"41 ", "114"}, {"42 ", "114"}, {"43 ", "114"}, {"44 ", "114"}, {"45 ", "114"}, {"46 ", "114"}, {"47 ", "114"}, {"48 ", "114"}, {"49 ", "114"}, {"50 ", "114"}, {"51 ", "114"}, {"52 ", "114"}, {"53 ", "114"}, {"54 ", "114"}, {"55 ", "114"}, {"56 ", "114"}, {"57 ", "114"}, {"58 ", "114"}, {"59 ", "114"}, {"60 ", "114"}, {"61 ", "114"}, {"62 ", "114"}, {"63 ", "114"}, {"64 ", "114"}, {"1 ", "115"}, {"2 ", "115"}, {"3 ", "115"}, {"4 ", "115"}, {"5 ", "115"}, {"6 ", "115"}, {"7 ", "115"}, {"8 ", "115"}, {"9 ", "115"}, {"10 ", "115"}, {"11 ", "115"}, {"12 ", "115"}, {"13 ", "115"}, {"14 ", "115"}, {"15 ", "115"}, {"16 ", "115"}, {"17 ", "115"}, {"18 ", "115"}, {"19 ", "115"}, {"20 ", "115"}, {"21 ", "115"}, {"22 ", "115"}, {"23 ", "115"}, {"24 ", "115"}, {"25 ", "115"}, {"26 ", "115"}, {"27 ", "115"}, {"28 ", "115"}, {"29 ", "115"}, {"30 ", "115"}, {"31 ", "115"}, {"32 ", "115"}, {"33 ", "115"}, {"34 ", "115"}, {"35 ", "115"}, {"36 ", "115"}, {"37 ", "115"}, {"38 ", "115"}, {"39 ", "115"}, {"40 ", "115"}, {"41 ", "115"}, {"42 ", "115"}, {"43 ", "115"}, {"44 ", "115"}, {"45 ", "115"}, {"46 ", "115"}, {"47 ", "115"}, {"48 ", "115"}, {"49 ", "115"}, {"50 ", "115"}, {"51 ", "115"}, {"52 ", "115"}, {"53 ", "115"}, {"54 ", "115"}, {"55 ", "115"}, {"56 ", "115"}, {"57 ", "115"}, {"58 ", "115"}, {"59 ", "115"}, {"60 ", "115"}, {"61 ", "115"}, {"62 ", "115"}, {"63 ", "115"}, {"64 ", "115"}, {"1 ", "116"}, {"2 ", "116"}, {"3 ", "116"}, {"4 ", "116"}, {"5 ", "116"}, {"6 ", "116"}, {"7 ", "116"}, {"8 ", "116"}, {"9 ", "116"}, {"10 ", "116"}, {"11 ", "116"}, {"12 ", "116"}, {"13 ", "116"}, {"14 ", "116"}, {"15 ", "116"}, {"16 ", "116"}, {"17 ", "116"}, {"18 ", "116"}, {"19 ", "116"}, {"20 ", "116"}, {"21 ", "116"}, {"22 ", "116"}, {"23 ", "116"}, {"24 ", "116"}, {"25 ", "116"}, {"26 ", "116"}, {"27 ", "116"}, {"28 ", "116"}, {"29 ", "116"}, {"30 ", "116"}, {"31 ", "116"}, {"32 ", "116"}, {"33 ", "116"}, {"34 ", "116"}, {"35 ", "116"}, {"36 ", "116"}, {"37 ", "116"}, {"38 ", "116"}, {"39 ", "116"}, {"40 ", "116"}, {"41 ", "116"}, {"42 ", "116"}, {"43 ", "116"}, {"44 ", "116"}, {"45 ", "116"}, {"46 ", "116"}, {"47 ", "116"}, {"48 ", "116"}, {"49 ", "116"}, {"50 ", "116"}, {"51 ", "116"}, {"52 ", "116"}, {"53 ", "116"}, {"54 ", "116"}, {"55 ", "116"}, {"56 ", "116"}, {"57 ", "116"}, {"58 ", "116"}, {"59 ", "116"}, {"60 ", "116"}, {"61 ", "116"}, {"62 ", "116"}, {"63 ", "116"}, {"64 ", "116"}, {"1 ", "117"}, {"2 ", "117"}, {"3 ", "117"}, {"4 ", "117"}, {"5 ", "117"}, {"6 ", "117"}, {"7 ", "117"}, {"8 ", "117"}, {"9 ", "117"}, {"10 ", "117"}, {"11 ", "117"}, {"12 ", "117"}, {"13 ", "117"}, {"14 ", "117"}, {"15 ", "117"}, {"16 ", "117"}, {"17 ", "117"}, {"18 ", "117"}, {"19 ", "117"}, {"20 ", "117"}, {"21 ", "117"}, {"22 ", "117"}, {"23 ", "117"}, {"24 ", "117"}, {"25 ", "117"}, {"26 ", "117"}, {"27 ", "117"}, {"28 ", "117"}, {"29 ", "117"}, {"30 ", "117"}, {"31 ", "117"}, {"32 ", "117"}, {"33 ", "117"}, {"34 ", "117"}, {"35 ", "117"}, {"36 ", "117"}, {"37 ", "117"}, {"38 ", "117"}, {"39 ", "117"}, {"40 ", "117"}, {"41 ", "117"}, {"42 ", "117"}, {"43 ", "117"}, {"44 ", "117"}, {"45 ", "117"}, {"46 ", "117"}, {"47 ", "117"}, {"48 ", "117"}, {"49 ", "117"}, {"50 ", "117"}, {"51 ", "117"}, {"52 ", "117"}, {"53 ", "117"}, {"54 ", "117"}, {"55 ", "117"}, {"56 ", "117"}, {"57 ", "117"}, {"58 ", "117"}, {"59 ", "117"}, {"60 ", "117"}, {"61 ", "117"}, {"62 ", "117"}, {"63 ", "117"}, {"64 ", "117"}, {"1 ", "118"}, {"2 ", "118"}, {"3 ", "118"}, {"4 ", "118"}, {"5 ", "118"}, {"6 ", "118"}, {"7 ", "118"}, {"8 ", "118"}, {"9 ", "118"}, {"10 ", "118"}, {"11 ", "118"}, {"12 ", "118"}, {"13 ", "118"}, {"14 ", "118"}, {"15 ", "118"}, {"16 ", "118"}, {"17 ", "118"}, {"18 ", "118"}, {"19 ", "118"}, {"20 ", "118"}, {"21 ", "118"}, {"22 ", "118"}, {"23 ", "118"}, {"24 ", "118"}, {"25 ", "118"}, {"26 ", "118"}, {"27 ", "118"}, {"28 ", "118"}, {"29 ", "118"}, {"30 ", "118"}, {"31 ", "118"}, {"32 ", "118"}, {"33 ", "118"}, {"34 ", "118"}, {"35 ", "118"}, {"36 ", "118"}, {"37 ", "118"}, {"38 ", "118"}, {"39 ", "118"}, {"40 ", "118"}, {"41 ", "118"}, {"42 ", "118"}, {"43 ", "118"}, {"44 ", "118"}, {"45 ", "118"}, {"46 ", "118"}, {"47 ", "118"}, {"48 ", "118"}, {"49 ", "118"}, {"50 ", "118"}, {"51 ", "118"}, {"52 ", "118"}, {"53 ", "118"}, {"54 ", "118"}, {"55 ", "118"}, {"56 ", "118"}, {"57 ", "118"}, {"58 ", "118"}, {"59 ", "118"}, {"60 ", "118"}, {"61 ", "118"}, {"62 ", "118"}, {"63 ", "118"}, {"64 ", "118"}, {"1 ", "119"}, {"2 ", "119"}, {"3 ", "119"}, {"4 ", "119"}, {"5 ", "119"}, {"6 ", "119"}, {"7 ", "119"}, {"8 ", "119"}, {"9 ", "119"}, {"10 ", "119"}, {"11 ", "119"}, {"12 ", "119"}, {"13 ", "119"}, {"14 ", "119"}, {"15 ", "119"}, {"16 ", "119"}, {"17 ", "119"}, {"18 ", "119"}, {"19 ", "119"}, {"20 ", "119"}, {"21 ", "119"}, {"22 ", "119"}, {"23 ", "119"}, {"24 ", "119"}, {"25 ", "119"}, {"26 ", "119"}, {"27 ", "119"}, {"28 ", "119"}, {"29 ", "119"}, {"30 ", "119"}, {"31 ", "119"}, {"32 ", "119"}, {"33 ", "119"}, {"34 ", "119"}, {"35 ", "119"}, {"36 ", "119"}, {"37 ", "119"}, {"38 ", "119"}, {"39 ", "119"}, {"40 ", "119"}, {"41 ", "119"}, {"42 ", "119"}, {"43 ", "119"}, {"44 ", "119"}, {"45 ", "119"}, {"46 ", "119"}, {"47 ", "119"}, {"48 ", "119"}, {"49 ", "119"}, {"50 ", "119"}, {"51 ", "119"}, {"52 ", "119"}, {"53 ", "119"}, {"54 ", "119"}, {"55 ", "119"}, {"56 ", "119"}, {"57 ", "119"}, {"58 ", "119"}, {"59 ", "119"}, {"60 ", "119"}, {"61 ", "119"}, {"62 ", "119"}, {"63 ", "119"}, {"64 ", "119"}, {"1 ", "120"}, {"2 ", "120"}, {"3 ", "120"}, {"4 ", "120"}, {"5 ", "120"}, {"6 ", "120"}, {"7 ", "120"}, {"8 ", "120"}, {"9 ", "120"}, {"10 ", "120"}, {"11 ", "120"}, {"12 ", "120"}, {"13 ", "120"}, {"14 ", "120"}, {"15 ", "120"}, {"16 ", "120"}, {"17 ", "120"}, {"18 ", "120"}, {"19 ", "120"}, {"20 ", "120"}, {"21 ", "120"}, {"22 ", "120"}, {"23 ", "120"}, {"24 ", "120"}, {"25 ", "120"}, {"26 ", "120"}, {"27 ", "120"}, {"28 ", "120"}, {"29 ", "120"}, {"30 ", "120"}, {"31 ", "120"}, {"32 ", "120"}, {"33 ", "120"}, {"34 ", "120"}, {"35 ", "120"}, {"36 ", "120"}, {"37 ", "120"}, {"38 ", "120"}, {"39 ", "120"}, {"40 ", "120"}, {"41 ", "120"}, {"42 ", "120"}, {"43 ", "120"}, {"44 ", "120"}, {"45 ", "120"}, {"46 ", "120"}, {"47 ", "120"}, {"48 ", "120"}, {"49 ", "120"}, {"50 ", "120"}, {"51 ", "120"}, {"52 ", "120"}, {"53 ", "120"}, {"54 ", "120"}, {"55 ", "120"}, {"56 ", "120"}, {"57 ", "120"}, {"58 ", "120"}, {"59 ", "120"}, {"60 ", "120"}, {"61 ", "120"}, {"62 ", "120"}, {"63 ", "120"}, {"64 ", "120"}, {"1 ", "121"}, {"2 ", "121"}, {"3 ", "121"}, {"4 ", "121"}, {"5 ", "121"}, {"6 ", "121"}, {"7 ", "121"}, {"8 ", "121"}, {"9 ", "121"}, {"10 ", "121"}, {"11 ", "121"}, {"12 ", "121"}, {"13 ", "121"}, {"14 ", "121"}, {"15 ", "121"}, {"16 ", "121"}, {"17 ", "121"}, {"18 ", "121"}, {"19 ", "121"}, {"20 ", "121"}, {"21 ", "121"}, {"22 ", "121"}, {"23 ", "121"}, {"24 ", "121"}, {"25 ", "121"}, {"26 ", "121"}, {"27 ", "121"}, {"28 ", "121"}, {"29 ", "121"}, {"30 ", "121"}, {"31 ", "121"}, {"32 ", "121"}, {"33 ", "121"}, {"34 ", "121"}, {"35 ", "121"}, {"36 ", "121"}, {"37 ", "121"}, {"38 ", "121"}, {"39 ", "121"}, {"40 ", "121"}, {"41 ", "121"}, {"42 ", "121"}, {"43 ", "121"}, {"44 ", "121"}, {"45 ", "121"}, {"46 ", "121"}, {"47 ", "121"}, {"48 ", "121"}, {"49 ", "121"}, {"50 ", "121"}, {"51 ", "121"}, {"52 ", "121"}, {"53 ", "121"}, {"54 ", "121"}, {"55 ", "121"}, {"56 ", "121"}, {"57 ", "121"}, {"58 ", "121"}, {"59 ", "121"}, {"60 ", "121"}, {"61 ", "121"}, {"62 ", "121"}, {"63 ", "121"}, {"64 ", "121"}, {"1 ", "122"}, {"2 ", "122"}, {"3 ", "122"}, {"4 ", "122"}, {"5 ", "122"}, {"6 ", "122"}, {"7 ", "122"}, {"8 ", "122"}, {"9 ", "122"}, {"10 ", "122"}, {"11 ", "122"}, {"12 ", "122"}, {"13 ", "122"}, {"14 ", "122"}, {"15 ", "122"}, {"16 ", "122"}, {"17 ", "122"}, {"18 ", "122"}, {"19 ", "122"}, {"20 ", "122"}, {"21 ", "122"}, {"22 ", "122"}, {"23 ", "122"}, {"24 ", "122"}, {"25 ", "122"}, {"26 ", "122"}, {"27 ", "122"}, {"28 ", "122"}, {"29 ", "122"}, {"30 ", "122"}, {"31 ", "122"}, {"32 ", "122"}, {"33 ", "122"}, {"34 ", "122"}, {"35 ", "122"}, {"36 ", "122"}, {"37 ", "122"}, {"38 ", "122"}, {"39 ", "122"}, {"40 ", "122"}, {"41 ", "122"}, {"42 ", "122"}, {"43 ", "122"}, {"44 ", "122"}, {"45 ", "122"}, {"46 ", "122"}, {"47 ", "122"}, {"48 ", "122"}, {"49 ", "122"}, {"50 ", "122"}, {"51 ", "122"}, {"52 ", "122"}, {"53 ", "122"}, {"54 ", "122"}, {"55 ", "122"}, {"56 ", "122"}, {"57 ", "122"}, {"58 ", "122"}, {"59 ", "122"}, {"60 ", "122"}, {"61 ", "122"}, {"62 ", "122"}, {"63 ", "122"}, {"64 ", "122"}, {"1 ", "123"}, {"2 ", "123"}, {"3 ", "123"}, {"4 ", "123"}, {"5 ", "123"}, {"6 ", "123"}, {"7 ", "123"}, {"8 ", "123"}, {"9 ", "123"}, {"10 ", "123"}, {"11 ", "123"}, {"12 ", "123"}, {"13 ", "123"}, {"14 ", "123"}, {"15 ", "123"}, {"16 ", "123"}, {"17 ", "123"}, {"18 ", "123"}, {"19 ", "123"}, {"20 ", "123"}, {"21 ", "123"}, {"22 ", "123"}, {"23 ", "123"}, {"24 ", "123"}, {"25 ", "123"}, {"26 ", "123"}, {"27 ", "123"}, {"28 ", "123"}, {"29 ", "123"}, {"30 ", "123"}, {"31 ", "123"}, {"32 ", "123"}, {"33 ", "123"}, {"34 ", "123"}, {"35 ", "123"}, {"36 ", "123"}, {"37 ", "123"}, {"38 ", "123"}, {"39 ", "123"}, {"40 ", "123"}, {"41 ", "123"}, {"42 ", "123"}, {"43 ", "123"}, {"44 ", "123"}, {"45 ", "123"}, {"46 ", "123"}, {"47 ", "123"}, {"48 ", "123"}, {"49 ", "123"}, {"50 ", "123"}, {"51 ", "123"}, {"52 ", "123"}, {"53 ", "123"}, {"54 ", "123"}, {"55 ", "123"}, {"56 ", "123"}, {"57 ", "123"}, {"58 ", "123"}, {"59 ", "123"}, {"60 ", "123"}, {"61 ", "123"}, {"62 ", "123"}, {"63 ", "123"}, {"64 ", "123"}, {"1 ", "124"}, {"2 ", "124"}, {"3 ", "124"}, {"4 ", "124"}, {"5 ", "124"}, {"6 ", "124"}, {"7 ", "124"}, {"8 ", "124"}, {"9 ", "124"}, {"10 ", "124"}, {"11 ", "124"}, {"12 ", "124"}, {"13 ", "124"}, {"14 ", "124"}, {"15 ", "124"}, {"16 ", "124"}, {"17 ", "124"}, {"18 ", "124"}, {"19 ", "124"}, {"20 ", "124"}, {"21 ", "124"}, {"22 ", "124"}, {"23 ", "124"}, {"24 ", "124"}, {"25 ", "124"}, {"26 ", "124"}, {"27 ", "124"}, {"28 ", "124"}, {"29 ", "124"}, {"30 ", "124"}, {"31 ", "124"}, {"32 ", "124"}, {"33 ", "124"}, {"34 ", "124"}, {"35 ", "124"}, {"36 ", "124"}, {"37 ", "124"}, {"38 ", "124"}, {"39 ", "124"}, {"40 ", "124"}, {"41 ", "124"}, {"42 ", "124"}, {"43 ", "124"}, {"44 ", "124"}, {"45 ", "124"}, {"46 ", "124"}, {"47 ", "124"}, {"48 ", "124"}, {"49 ", "124"}, {"50 ", "124"}, {"51 ", "124"}, {"52 ", "124"}, {"53 ", "124"}, {"54 ", "124"}, {"55 ", "124"}, {"56 ", "124"}, {"57 ", "124"}, {"58 ", "124"}, {"59 ", "124"}, {"60 ", "124"}, {"61 ", "124"}, {"62 ", "124"}, {"63 ", "124"}, {"64 ", "124"}, {"1 ", "125"}, {"2 ", "125"}, {"3 ", "125"}, {"4 ", "125"}, {"5 ", "125"}, {"6 ", "125"}, {"7 ", "125"}, {"8 ", "125"}, {"9 ", "125"}, {"10 ", "125"}, {"11 ", "125"}, {"12 ", "125"}, {"13 ", "125"}, {"14 ", "125"}, {"15 ", "125"}, {"16 ", "125"}, {"17 ", "125"}, {"18 ", "125"}, {"19 ", "125"}, {"20 ", "125"}, {"21 ", "125"}, {"22 ", "125"}, {"23 ", "125"}, {"24 ", "125"}, {"25 ", "125"}, {"26 ", "125"}, {"27 ", "125"}, {"28 ", "125"}, {"29 ", "125"}, {"30 ", "125"}, {"31 ", "125"}, {"32 ", "125"}, {"33 ", "125"}, {"34 ", "125"}, {"35 ", "125"}, {"36 ", "125"}, {"37 ", "125"}, {"38 ", "125"}, {"39 ", "125"}, {"40 ", "125"}, {"41 ", "125"}, {"42 ", "125"}, {"43 ", "125"}, {"44 ", "125"}, {"45 ", "125"}, {"46 ", "125"}, {"47 ", "125"}, {"48 ", "125"}, {"49 ", "125"}, {"50 ", "125"}, {"51 ", "125"}, {"52 ", "125"}, {"53 ", "125"}, {"54 ", "125"}, {"55 ", "125"}, {"56 ", "125"}, {"57 ", "125"}, {"58 ", "125"}, {"59 ", "125"}, {"60 ", "125"}, {"61 ", "125"}, {"62 ", "125"}, {"63 ", "125"}, {"64 ", "125"}, {"1 ", "126"}, {"2 ", "126"}, {"3 ", "126"}, {"4 ", "126"}, {"5 ", "126"}, {"6 ", "126"}, {"7 ", "126"}, {"8 ", "126"}, {"9 ", "126"}, {"10 ", "126"}, {"11 ", "126"}, {"12 ", "126"}, {"13 ", "126"}, {"14 ", "126"}, {"15 ", "126"}, {"16 ", "126"}, {"17 ", "126"}, {"18 ", "126"}, {"19 ", "126"}, {"20 ", "126"}, {"21 ", "126"}, {"22 ", "126"}, {"23 ", "126"}, {"24 ", "126"}, {"25 ", "126"}, {"26 ", "126"}, {"27 ", "126"}, {"28 ", "126"}, {"29 ", "126"}, {"30 ", "126"}, {"31 ", "126"}, {"32 ", "126"}, {"33 ", "126"}, {"34 ", "126"}, {"35 ", "126"}, {"36 ", "126"}, {"37 ", "126"}, {"38 ", "126"}, {"39 ", "126"}, {"40 ", "126"}, {"41 ", "126"}, {"42 ", "126"}, {"43 ", "126"}, {"44 ", "126"}, {"45 ", "126"}, {"46 ", "126"}, {"47 ", "126"}, {"48 ", "126"}, {"49 ", "126"}, {"50 ", "126"}, {"51 ", "126"}, {"52 ", "126"}, {"53 ", "126"}, {"54 ", "126"}, {"55 ", "126"}, {"56 ", "126"}, {"57 ", "126"}, {"58 ", "126"}, {"59 ", "126"}, {"60 ", "126"}, {"61 ", "126"}, {"62 ", "126"}, {"63 ", "126"}, {"64 ", "126"}, {"1 ", "127"}, {"2 ", "127"}, {"3 ", "127"}, {"4 ", "127"}, {"5 ", "127"}, {"6 ", "127"}, {"7 ", "127"}, {"8 ", "127"}, {"9 ", "127"}, {"10 ", "127"}, {"11 ", "127"}, {"12 ", "127"}, {"13 ", "127"}, {"14 ", "127"}, {"15 ", "127"}, {"16 ", "127"}, {"17 ", "127"}, {"18 ", "127"}, {"19 ", "127"}, {"20 ", "127"}, {"21 ", "127"}, {"22 ", "127"}, {"23 ", "127"}, {"24 ", "127"}, {"25 ", "127"}, {"26 ", "127"}, {"27 ", "127"}, {"28 ", "127"}, {"29 ", "127"}, {"30 ", "127"}, {"31 ", "127"}, {"32 ", "127"}, {"33 ", "127"}, {"34 ", "127"}, {"35 ", "127"}, {"36 ", "127"}, {"37 ", "127"}, {"38 ", "127"}, {"39 ", "127"}, {"40 ", "127"}, {"41 ", "127"}, {"42 ", "127"}, {"43 ", "127"}, {"44 ", "127"}, {"45 ", "127"}, {"46 ", "127"}, {"47 ", "127"}, {"48 ", "127"}, {"49 ", "127"}, {"50 ", "127"}, {"51 ", "127"}, {"52 ", "127"}, {"53 ", "127"}, {"54 ", "127"}, {"55 ", "127"}, {"56 ", "127"}, {"57 ", "127"}, {"58 ", "127"}, {"59 ", "127"}, {"60 ", "127"}, {"61 ", "127"}, {"62 ", "127"}, {"63 ", "127"}, {"64 ", "127"}, {"1 ", "128"}, {"2 ", "128"}, {"3 ", "128"}, {"4 ", "128"}, {"5 ", "128"}, {"6 ", "128"}, {"7 ", "128"}, {"8 ", "128"}, {"9 ", "128"}, {"10 ", "128"}, {"11 ", "128"}, {"12 ", "128"}, {"13 ", "128"}, {"14 ", "128"}, {"15 ", "128"}, {"16 ", "128"}, {"17 ", "128"}, {"18 ", "128"}, {"19 ", "128"}, {"20 ", "128"}, {"21 ", "128"}, {"22 ", "128"}, {"23 ", "128"}, {"24 ", "128"}, {"25 ", "128"}, {"26 ", "128"}, {"27 ", "128"}, {"28 ", "128"}, {"29 ", "128"}, {"30 ", "128"}, {"31 ", "128"}, {"32 ", "128"}, {"33 ", "128"}, {"34 ", "128"}, {"35 ", "128"}, {"36 ", "128"}, {"37 ", "128"}, {"38 ", "128"}, {"39 ", "128"}, {"40 ", "128"}, {"41 ", "128"}, {"42 ", "128"}, {"43 ", "128"}, {"44 ", "128"}, {"45 ", "128"}, {"46 ", "128"}, {"47 ", "128"}, {"48 ", "128"}, {"49 ", "128"}, {"50 ", "128"}, {"51 ", "128"}, {"52 ", "128"}, {"53 ", "128"}, {"54 ", "128"}, {"55 ", "128"}, {"56 ", "128"}, {"57 ", "128"}, {"58 ", "128"}, {"59 ", "128"}, {"60 ", "128"}, {"61 ", "128"}, {"62 ", "128"}, {"63 ", "128"}, {"64 ", "128"}},
},
20: {
pattern: "{01...036}",
success: true,
want: [][]string{{"001"}, {"002"}, {"003"}, {"004"}, {"005"}, {"006"}, {"007"}, {"008"}, {"009"}, {"010"}, {"011"}, {"012"}, {"013"}, {"014"}, {"015"}, {"016"}, {"017"}, {"018"}, {"019"}, {"020"}, {"021"}, {"022"}, {"023"}, {"024"}, {"025"}, {"026"}, {"027"}, {"028"}, {"029"}, {"030"}, {"031"}, {"032"}, {"033"}, {"034"}, {"035"}, {"036"}},
},
21: {
pattern: "{001...036}",
success: true,
want: [][]string{{"001"}, {"002"}, {"003"}, {"004"}, {"005"}, {"006"}, {"007"}, {"008"}, {"009"}, {"010"}, {"011"}, {"012"}, {"013"}, {"014"}, {"015"}, {"016"}, {"017"}, {"018"}, {"019"}, {"020"}, {"021"}, {"022"}, {"023"}, {"024"}, {"025"}, {"026"}, {"027"}, {"028"}, {"029"}, {"030"}, {"031"}, {"032"}, {"033"}, {"034"}, {"035"}, {"036"}},
},
22: {
pattern: "{1...a}",
success: true,
want: [][]string{{"1"}, {"2"}, {"3"}, {"4"}, {"5"}, {"6"}, {"7"}, {"8"}, {"9"}, {"a"}},
},
23: {
pattern: "{01...1a}",
success: true,
want: [][]string{{"01"}, {"02"}, {"03"}, {"04"}, {"05"}, {"06"}, {"07"}, {"08"}, {"09"}, {"0a"}, {"0b"}, {"0c"}, {"0d"}, {"0e"}, {"0f"}, {"10"}, {"11"}, {"12"}, {"13"}, {"14"}, {"15"}, {"16"}, {"17"}, {"18"}, {"19"}, {"1a"}},
},
24: {
pattern: "{0f...10f}",
success: true,
want: [][]string{{"00f"}, {"010"}, {"011"}, {"012"}, {"013"}, {"014"}, {"015"}, {"016"}, {"017"}, {"018"}, {"019"}, {"01a"}, {"01b"}, {"01c"}, {"01d"}, {"01e"}, {"01f"}, {"020"}, {"021"}, {"022"}, {"023"}, {"024"}, {"025"}, {"026"}, {"027"}, {"028"}, {"029"}, {"02a"}, {"02b"}, {"02c"}, {"02d"}, {"02e"}, {"02f"}, {"030"}, {"031"}, {"032"}, {"033"}, {"034"}, {"035"}, {"036"}, {"037"}, {"038"}, {"039"}, {"03a"}, {"03b"}, {"03c"}, {"03d"}, {"03e"}, {"03f"}, {"040"}, {"041"}, {"042"}, {"043"}, {"044"}, {"045"}, {"046"}, {"047"}, {"048"}, {"049"}, {"04a"}, {"04b"}, {"04c"}, {"04d"}, {"04e"}, {"04f"}, {"050"}, {"051"}, {"052"}, {"053"}, {"054"}, {"055"}, {"056"}, {"057"}, {"058"}, {"059"}, {"05a"}, {"05b"}, {"05c"}, {"05d"}, {"05e"}, {"05f"}, {"060"}, {"061"}, {"062"}, {"063"}, {"064"}, {"065"}, {"066"}, {"067"}, {"068"}, {"069"}, {"06a"}, {"06b"}, {"06c"}, {"06d"}, {"06e"}, {"06f"}, {"070"}, {"071"}, {"072"}, {"073"}, {"074"}, {"075"}, {"076"}, {"077"}, {"078"}, {"079"}, {"07a"}, {"07b"}, {"07c"}, {"07d"}, {"07e"}, {"07f"}, {"080"}, {"081"}, {"082"}, {"083"}, {"084"}, {"085"}, {"086"}, {"087"}, {"088"}, {"089"}, {"08a"}, {"08b"}, {"08c"}, {"08d"}, {"08e"}, {"08f"}, {"090"}, {"091"}, {"092"}, {"093"}, {"094"}, {"095"}, {"096"}, {"097"}, {"098"}, {"099"}, {"09a"}, {"09b"}, {"09c"}, {"09d"}, {"09e"}, {"09f"}, {"0a0"}, {"0a1"}, {"0a2"}, {"0a3"}, {"0a4"}, {"0a5"}, {"0a6"}, {"0a7"}, {"0a8"}, {"0a9"}, {"0aa"}, {"0ab"}, {"0ac"}, {"0ad"}, {"0ae"}, {"0af"}, {"0b0"}, {"0b1"}, {"0b2"}, {"0b3"}, {"0b4"}, {"0b5"}, {"0b6"}, {"0b7"}, {"0b8"}, {"0b9"}, {"0ba"}, {"0bb"}, {"0bc"}, {"0bd"}, {"0be"}, {"0bf"}, {"0c0"}, {"0c1"}, {"0c2"}, {"0c3"}, {"0c4"}, {"0c5"}, {"0c6"}, {"0c7"}, {"0c8"}, {"0c9"}, {"0ca"}, {"0cb"}, {"0cc"}, {"0cd"}, {"0ce"}, {"0cf"}, {"0d0"}, {"0d1"}, {"0d2"}, {"0d3"}, {"0d4"}, {"0d5"}, {"0d6"}, {"0d7"}, {"0d8"}, {"0d9"}, {"0da"}, {"0db"}, {"0dc"}, {"0dd"}, {"0de"}, {"0df"}, {"0e0"}, {"0e1"}, {"0e2"}, {"0e3"}, {"0e4"}, {"0e5"}, {"0e6"}, {"0e7"}, {"0e8"}, {"0e9"}, {"0ea"}, {"0eb"}, {"0ec"}, {"0ed"}, {"0ee"}, {"0ef"}, {"0f0"}, {"0f1"}, {"0f2"}, {"0f3"}, {"0f4"}, {"0f5"}, {"0f6"}, {"0f7"}, {"0f8"}, {"0f9"}, {"0fa"}, {"0fb"}, {"0fc"}, {"0fd"}, {"0fe"}, {"0ff"}, {"100"}, {"101"}, {"102"}, {"103"}, {"104"}, {"105"}, {"106"}, {"107"}, {"108"}, {"109"}, {"10a"}, {"10b"}, {"10c"}, {"10d"}, {"10e"}, {"10f"}},
},
}
for i, testCase := range testCases {
if testCase.pattern == "" {
continue
}
t.Run(fmt.Sprintf("Test%d", i), func(t *testing.T) {
argP, err := FindEllipsesPatterns(testCase.pattern)
if err != nil && testCase.success {
t.Errorf("Expected success but failed instead %s", err)
}
if err == nil && !testCase.success {
t.Errorf("Expected failure but passed instead")
}
if err == nil {
got := argP.Expand()
gotCount := len(got)
if gotCount != len(testCase.want) {
t.Errorf("Expected %d, got %d", len(testCase.want), gotCount)
}
repl := func(v interface{}) string {
s := fmt.Sprintf("%#v", v)
// Clean up unneeded declarations
s = strings.Replace(s, `[]string{"`, `{"`, -1)
return s
}
if !reflect.DeepEqual(got, testCase.want) {
t.Errorf(fmt.Sprintf("want %s,", repl(testCase.want)))
t.Errorf("got %s,", repl(got))
}
}
})
}
}
golang-github-minio-pkg-3.0.10/ellipses/list.go 0000664 0000000 0000000 00000004447 14655770405 0021362 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package ellipses
import (
"errors"
"fmt"
"regexp"
"strings"
)
var (
// Regex to extract ellipses syntax inputs.
regexpList = regexp.MustCompile(`(.*)({[0-9a-z]+[,[0-9a-z]+]?})(.*)`)
// Ellipses constants
comma = ","
)
// HasList - returns true if input arg has list type pattern {1,3,5}
func HasList(args ...string) bool {
ok := len(args) > 0
for _, arg := range args {
if !ok {
break
}
ok = ok && regexpList.MatchString(arg)
}
return ok
}
// ErrInvalidListFormatFn error returned when invalid list format is detected.
var ErrInvalidListFormatFn = func(arg string) error {
return fmt.Errorf("Invalid list format in (%s)", arg)
}
// FindListPatterns - finds all list patterns, recursively and parses the ranges numerically.
func FindListPatterns(arg string) (ArgPattern, error) {
v, err := findPatterns(arg, regexpList, parseListRange)
if err == errFormat {
err = ErrInvalidListFormatFn(arg)
}
return v, err
}
// Parses a list pattern of following style `{1,3,4}`
func parseListRange(pattern string) (seq []string, err error) {
if !strings.HasPrefix(pattern, openBraces) {
return nil, errors.New("invalid argument")
}
if !strings.HasSuffix(pattern, closeBraces) {
return nil, errors.New("invalid argument")
}
pattern = strings.TrimPrefix(pattern, openBraces)
pattern = strings.TrimSuffix(pattern, closeBraces)
seq = strings.Split(pattern, comma)
if len(seq) < 2 {
return nil, errors.New("invalid argument")
}
for i := range seq {
if len(seq[i]) == 0 {
return nil, errors.New("invalid argument")
}
}
return seq, nil
}
golang-github-minio-pkg-3.0.10/ellipses/list_test.go 0000664 0000000 0000000 00000007421 14655770405 0022414 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package ellipses
import (
"fmt"
"reflect"
"strings"
"testing"
)
// Test tests if args has a list sequence
func TestHasList(t *testing.T) {
testCases := []struct {
args []string
expectedOk bool
}{
{
[]string{""},
false,
},
{
[]string{"64"},
false,
},
{
[]string{"{1..64}"},
false,
},
{
[]string{"{1..2..}"},
false,
},
{
[]string{"1"},
false,
},
{
[]string{"{1}"},
false,
},
{
[]string{"{1,2}"},
true,
},
{
[]string{"{a,b}"},
true,
},
{
[]string{"http://minio{1,2,3,4}/export/disk"},
true,
},
{
[]string{"http://minio{1,2,3,4}/export/disk{1,2,3,4}"},
true,
},
}
for i, testCase := range testCases {
t.Run(fmt.Sprintf("Test%d", i+1), func(t *testing.T) {
gotOk := HasList(testCase.args...)
if gotOk != testCase.expectedOk {
t.Errorf("Expected %t, got %t", testCase.expectedOk, gotOk)
}
})
}
}
// Test tests find list sequences patterns.
func TestFindListPatterns(t *testing.T) {
testCases := []struct {
pattern string
success bool
want [][]string
}{
// Tests for all invalid inputs
0: {
pattern: "{1..64}",
},
1: {
pattern: "1...64",
},
2: {
pattern: "...",
},
3: {
pattern: "{1...",
},
4: {
pattern: "...64}",
},
5: {
pattern: "{...}",
},
6: {
pattern: "{1}",
},
7: {
pattern: "{,}",
},
8: {
pattern: "{1,}",
},
9: {
pattern: "{1,,}",
},
10: {
pattern: "{1,2",
},
11: {
pattern: "mydisk-{a,z",
},
// Test for valid input.
12: {
pattern: "{1,2}",
success: true,
want: [][]string{{"1"}, {"2"}},
},
13: {
pattern: "{1,2}/{3,4}",
success: true,
want: [][]string{{"1/", "3"}, {"2/", "3"}, {"1/", "4"}, {"2/", "4"}},
},
14: {
pattern: "/mnt/disk{1,2,3,4}/",
success: true,
want: [][]string{{"/mnt/disk1/"}, {"/mnt/disk2/"}, {"/mnt/disk3/"}, {"/mnt/disk4/"}},
},
15: {
pattern: "http://minio:9000/disk/{1,2,3,4}/",
success: true,
want: [][]string{{"http://minio:9000/disk/1/"}, {"http://minio:9000/disk/2/"}, {"http://minio:9000/disk/3/"}, {"http://minio:9000/disk/4/"}},
},
}
for i, testCase := range testCases {
if testCase.pattern == "" {
continue
}
t.Run(fmt.Sprintf("Test%d", i), func(t *testing.T) {
argP, err := FindListPatterns(testCase.pattern)
if err != nil && testCase.success {
t.Errorf("Expected success but failed instead %s", err)
}
if err == nil && !testCase.success {
t.Errorf("Expected failure but passed instead")
}
if err == nil {
got := argP.Expand()
gotCount := len(got)
if gotCount != len(testCase.want) {
t.Errorf("Expected %d, got %d", len(testCase.want), gotCount)
}
repl := func(v interface{}) string {
s := fmt.Sprintf("%#v", v)
// Clean up unneeded declarations
s = strings.Replace(s, `[]string{"`, `{"`, -1)
return s
}
if !reflect.DeepEqual(got, testCase.want) {
t.Errorf(fmt.Sprintf("want %s,", repl(testCase.want)))
t.Errorf("got %s,", repl(got))
}
}
})
}
}
golang-github-minio-pkg-3.0.10/env/ 0000775 0000000 0000000 00000000000 14655770405 0017017 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/env/env.go 0000664 0000000 0000000 00000005503 14655770405 0020141 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package env
import (
"strconv"
"strings"
"sync"
"time"
)
var (
privateMutex sync.RWMutex
lockEnvMutex sync.Mutex
envOff bool
)
// LockSetEnv locks modifications to environment.
// Call returned function to unlock.
func LockSetEnv() func() {
lockEnvMutex.Lock()
return lockEnvMutex.Unlock
}
// SetEnvOff - turns off env lookup
// A global lock above this MUST ensure that
func SetEnvOff() {
privateMutex.Lock()
defer privateMutex.Unlock()
envOff = true
}
// SetEnvOn - turns on env lookup
func SetEnvOn() {
privateMutex.Lock()
defer privateMutex.Unlock()
envOff = false
}
// IsSet returns if the given env key is set.
// remember ENV must be a non-empty. All empty
// values are considered unset.
func IsSet(key string) bool {
return Get(key, "") != ""
}
// Get retrieves the value of the environment variable named
// by the key. If the variable is present in the environment the
// value (which may be empty) is not returned and this is considered
// unset. Otherwise it returns the specified default value.
func Get(key, defaultValue string) string {
privateMutex.RLock()
ok := envOff
privateMutex.RUnlock()
if !ok {
v, _, _, _ := LookupEnv(key)
if v != "" {
return strings.TrimSpace(v)
}
}
return strings.TrimSpace(defaultValue)
}
// GetInt returns an integer if found in the environment
// and returns the default value otherwise.
func GetInt(key string, defaultValue int) (int, error) {
v := Get(key, "")
if v == "" {
return defaultValue, nil
}
return strconv.Atoi(v)
}
// GetDuration returns a parsed time.Duration if found in
// the environment value, returns the default value duration
// otherwise.
func GetDuration(key string, defaultValue time.Duration) (time.Duration, error) {
v := Get(key, "")
if v == "" {
return defaultValue, nil
}
return time.ParseDuration(v)
}
// List all envs with a given prefix.
func List(prefix string) (envs []string) {
for _, env := range Environ() {
if strings.HasPrefix(env, prefix) {
values := strings.SplitN(env, "=", 2)
if len(values) == 2 {
envs = append(envs, values[0])
}
}
}
return envs
}
golang-github-minio-pkg-3.0.10/env/env_test.go 0000664 0000000 0000000 00000007207 14655770405 0021203 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package env
import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"
"github.com/minio/mux"
)
func GetenvHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
if vars["namespace"] != "default" {
http.Error(w, "namespace not found", http.StatusNotFound)
return
}
if vars["name"] != "minio" {
http.Error(w, "tenant not found", http.StatusNotFound)
return
}
if vars["key"] != "MINIO_ARGS" {
http.Error(w, "key not found", http.StatusNotFound)
return
}
w.Write([]byte("http://127.0.0.{1..4}:9000/data{1...4}"))
w.(http.Flusher).Flush()
}
func startTestServer(t *testing.T) *httptest.Server {
router := mux.NewRouter().SkipClean(true).UseEncodedPath()
router.Methods(http.MethodGet).
Path("/webhook/v1/getenv/{namespace}/{name}").
HandlerFunc(GetenvHandler).Queries("key", "{key:.*}")
ts := httptest.NewServer(router)
t.Cleanup(func() {
ts.Close()
})
return ts
}
func TestWebEnv(t *testing.T) {
ts := startTestServer(t)
u, err := url.Parse(ts.URL)
if err != nil {
t.Fatal(err)
}
v, user, pwd, err := getEnvValueFromHTTP(
fmt.Sprintf("env://minio:minio123@%s/webhook/v1/getenv/default/minio",
u.Host),
"MINIO_ARGS")
if err != nil {
t.Fatal(err)
}
if v != "http://127.0.0.{1..4}:9000/data{1...4}" {
t.Fatalf("Unexpected value %s", v)
}
if user != "minio" {
t.Fatalf("Unexpected value %s", v)
}
if pwd != "minio123" {
t.Fatalf("Unexpected value %s", v)
}
}
func TestIsSetType(t *testing.T) {
t.Setenv("_TEST_ENV", "")
v, err := GetInt("_TEST_ENV", 0)
if err != nil {
t.Fatal(err)
}
if v != 0 {
t.Fatal("unexpected")
}
t.Setenv("_TEST_ENV", "1")
v, err = GetInt("_TEST_ENV", 0)
if err != nil {
t.Fatal(err)
}
if v != 1 {
t.Fatal("unexpected")
}
t.Setenv("_TEST_ENV", "10s")
d, err := GetDuration("_TEST_ENV", time.Second)
if err != nil {
t.Fatal(err)
}
if d != 10*time.Second {
t.Fatal("unexpected")
}
t.Setenv("_TEST_ENV", "")
d, err = GetDuration("_TEST_ENV", time.Second)
if err != nil {
t.Fatal(err)
}
if d != time.Second {
t.Fatal("unexpected")
}
}
func TestIsSet(t *testing.T) {
t.Setenv("_TEST_ENV", "")
if IsSet("_TEST_ENV") {
t.Fatal("Expected IsSet(false) but found IsSet(true)")
}
t.Setenv("_TEST_ENV", "v")
if !IsSet("_TEST_ENV") {
t.Fatal("Expected IsSet(true) but found IsSet(false)")
}
}
func TestGetEnv(t *testing.T) {
// Set empty env-value, this test covers situation
// where env is set but with empty value, choose
// to fallback to default value at this point.
t.Setenv("_TEST_ENV", "")
if v := Get("_TEST_ENV", "value"); v != "value" {
t.Fatalf("Expected 'value', but got %s", v)
}
t.Setenv("_TEST_ENV", "")
if v := Get("_TEST_ENV", "value"); v != "value" {
t.Fatalf("Expected 'value', but got %s", v)
}
t.Setenv("_TEST_ENV", "value-new")
if v := Get("_TEST_ENV", "value"); v != "value-new" {
t.Fatalf("Expected 'value-new', but got %s", v)
}
}
golang-github-minio-pkg-3.0.10/env/web_env.go 0000664 0000000 0000000 00000012511 14655770405 0020773 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package env
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"os"
"regexp"
"strings"
"time"
"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/jwk"
"github.com/lestrrat-go/jwx/jwt"
)
const (
webEnvScheme = "env"
webEnvSchemeSecure = "env+tls"
)
var globalRootCAs *x509.CertPool
// RegisterGlobalCAs register the global root CAs
func RegisterGlobalCAs(CAs *x509.CertPool) {
globalRootCAs = CAs
}
var hostKeys = regexp.MustCompile("^(https?://)(.*?):(.*?)@(.*?)$")
func fetchHTTPConstituentParts(u *url.URL) (username, password, envURL string, err error) {
envURL = u.String()
if hostKeys.MatchString(envURL) {
parts := hostKeys.FindStringSubmatch(envURL)
if len(parts) != 5 {
return "", "", "", errors.New("invalid arguments")
}
username = parts[2]
password = parts[3]
envURL = fmt.Sprintf("%s%s", parts[1], parts[4])
}
if username == "" && password == "" && u.User != nil {
username = u.User.Username()
password, _ = u.User.Password()
}
return username, password, envURL, nil
}
func getEnvValueFromHTTP(urlStr, envKey string) (string, string, string, error) {
u, err := url.Parse(urlStr)
if err != nil {
return "", "", "", err
}
switch u.Scheme {
case webEnvScheme:
u.Scheme = "http"
case webEnvSchemeSecure:
u.Scheme = "https"
default:
return "", "", "", errors.New("invalid arguments")
}
username, password, envURL, err := fetchHTTPConstituentParts(u)
if err != nil {
return "", "", "", err
}
// Adding a timeout of 6.5 seconds to deal with k3s slow dns resolution caused in turn by
// CoreDNS 6 second default timeout.
ctx, cancel := context.WithTimeout(context.Background(), 6500*time.Millisecond)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, envURL+"?key="+envKey, nil)
if err != nil {
return "", "", "", err
}
skey, err := jwk.New([]byte(password))
if err != nil {
return "", "", "", err
}
skey.Set(jwk.AlgorithmKey, jwa.HS512)
skey.Set(jwk.KeyIDKey, "minio")
token := jwt.New()
t := time.Now().Add(15 * time.Minute)
if err = token.Set(jwt.IssuerKey, username); err != nil {
return "", "", "", err
}
if err = token.Set(jwt.SubjectKey, envKey); err != nil {
return "", "", "", err
}
if err = token.Set(jwt.ExpirationKey, t.Unix()); err != nil {
return "", "", "", err
}
signed, err := jwt.Sign(token, jwa.HS512, skey)
if err != nil {
return "", "", "", err
}
req.Header.Set("Authorization", "Bearer "+string(signed))
clnt := &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 3 * time.Second,
KeepAlive: 5 * time.Second,
}).DialContext,
ResponseHeaderTimeout: 3 * time.Second,
TLSHandshakeTimeout: 3 * time.Second,
ExpectContinueTimeout: 3 * time.Second,
TLSClientConfig: &tls.Config{
RootCAs: globalRootCAs,
},
// Go net/http automatically unzip if content-type is
// gzip disable this feature, as we are always interested
// in raw stream.
DisableCompression: true,
},
}
resp, err := clnt.Do(req)
if err != nil {
return "", "", "", err
}
envValueBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", "", "", err
}
return string(envValueBytes), username, password, nil
}
// Environ returns a copy of strings representing the
// environment, in the form "key=value".
func Environ() []string {
return os.Environ()
}
// LookupEnv retrieves the value of the environment variable
// named by the key. If the variable is present in the
// environment the value then that ENV is conisdered unset.
// and the boolean is false. Otherwise the returned value
// will be whatever value is set and the boolean will be true.
//
// Additionally if the input is env://username:password@remote:port/
// to fetch ENV values for the env value from a remote server.
// In this case, it also returns the credentials username and password
func LookupEnv(key string) (string, string, string, error) {
v, ok := os.LookupEnv(key)
if ok && strings.HasPrefix(v, webEnvScheme) {
// If env value starts with `env*://`
// continue to parse and fetch from remote
var err error
v, user, pwd, err := getEnvValueFromHTTP(strings.TrimSpace(v), key)
if err != nil {
env, eok := os.LookupEnv("_" + key)
if eok {
// fallback to cached value if-any.
return env, user, pwd, nil
}
return env, user, pwd, err
}
// Set the ENV value to _env value,
// this value is a fallback in-case of
// server restarts when webhook server
// is down.
os.Setenv("_"+key, v)
return v, user, pwd, nil
}
return v, "", "", nil
}
golang-github-minio-pkg-3.0.10/event/ 0000775 0000000 0000000 00000000000 14655770405 0017350 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/event/errors.go 0000664 0000000 0000000 00000001750 14655770405 0021216 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package event
import "fmt"
// ErrInvalidEventName - invalid event name error.
type ErrInvalidEventName struct {
Name string
}
func (err ErrInvalidEventName) Error() string {
return fmt.Sprintf("invalid event name '%v'", err.Name)
}
golang-github-minio-pkg-3.0.10/event/event.go 0000664 0000000 0000000 00000005767 14655770405 0021037 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package event
import "github.com/minio/madmin-go/v3"
// Identity represents access key who caused the event.
type Identity struct {
PrincipalID string `json:"principalId"`
}
// Bucket represents bucket metadata of the event.
type Bucket struct {
Name string `json:"name"`
OwnerIdentity Identity `json:"ownerIdentity"`
ARN string `json:"arn"`
}
// Object represents object metadata of the event.
type Object struct {
Key string `json:"key"`
Size int64 `json:"size,omitempty"`
ETag string `json:"eTag,omitempty"`
ContentType string `json:"contentType,omitempty"`
UserMetadata map[string]string `json:"userMetadata,omitempty"`
VersionID string `json:"versionId,omitempty"`
Sequencer string `json:"sequencer"`
}
// Metadata represents event metadata.
type Metadata struct {
SchemaVersion string `json:"s3SchemaVersion"`
ConfigurationID string `json:"configurationId"`
Bucket Bucket `json:"bucket"`
Object Object `json:"object"`
}
// Source represents client information who triggered the event.
type Source struct {
Host string `json:"host"`
Port string `json:"port"`
UserAgent string `json:"userAgent"`
}
// Event represents event notification information defined in
// http://docs.aws.amazon.com/AmazonS3/latest/dev/notification-content-structure.html.
type Event struct {
EventVersion string `json:"eventVersion"`
EventSource string `json:"eventSource"`
AwsRegion string `json:"awsRegion"`
EventTime string `json:"eventTime"`
EventName Name `json:"eventName"`
UserIdentity Identity `json:"userIdentity"`
RequestParameters map[string]string `json:"requestParameters"`
ResponseElements map[string]string `json:"responseElements"`
S3 Metadata `json:"s3"`
Source Source `json:"source"`
Type madmin.TraceType `json:"-"`
}
// Mask returns the type as mask.
func (e Event) Mask() uint64 {
return e.EventName.Mask()
}
// Log represents event information for some event targets.
type Log struct {
EventName Name
Key string
Records []Event
}
golang-github-minio-pkg-3.0.10/event/name.go 0000664 0000000 0000000 00000022731 14655770405 0020624 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package event
import (
"encoding/json"
"encoding/xml"
)
// Name - event type enum.
// Refer http://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html#notification-how-to-event-types-and-destinations
// for most basic values we have since extend this and its not really much applicable other than a reference point.
// "s3:Replication:OperationCompletedReplication" is a MinIO extension.
type Name int
// Values of event Name
const (
// Single event types (does not require expansion)
ObjectAccessedGet Name = 1 + iota
ObjectAccessedGetRetention
ObjectAccessedGetLegalHold
ObjectAccessedHead
ObjectCreatedCompleteMultipartUpload
ObjectCreatedCopy
ObjectCreatedPost
ObjectCreatedPut
ObjectCreatedPutRetention
ObjectCreatedPutLegalHold
ObjectCreatedPutTagging
ObjectCreatedDeleteTagging
ObjectRemovedDelete
ObjectRemovedDeleteMarkerCreated
BucketCreated
BucketRemoved
ObjectReplicationFailed
ObjectReplicationComplete
ObjectReplicationMissedThreshold
ObjectReplicationReplicatedAfterThreshold
ObjectReplicationNotTracked
ObjectRestorePost
ObjectRestoreCompleted
ObjectTransitionFailed
ObjectTransitionComplete
ObjectManyVersions
PrefixManyFolders
objectSingleTypesEnd
// Start Compound types that require expansion:
ObjectAccessedAll
ObjectCreatedAll
ObjectRemovedAll
ObjectReplicationAll
ObjectRestoreAll
ObjectTransitionAll
ObjectScannerAll
Everything
)
// The number of single names should not exceed 64.
// This will break masking. Use bit 63 as extension.
var _ = uint64(1 << objectSingleTypesEnd)
// Expand - returns expanded values of abbreviated event type.
func (name Name) Expand() []Name {
switch name {
case ObjectAccessedAll:
return []Name{
ObjectAccessedGet, ObjectAccessedHead,
ObjectAccessedGetRetention, ObjectAccessedGetLegalHold,
}
case ObjectCreatedAll:
return []Name{
ObjectCreatedCompleteMultipartUpload, ObjectCreatedCopy,
ObjectCreatedPost, ObjectCreatedPut,
ObjectCreatedPutRetention, ObjectCreatedPutLegalHold,
ObjectCreatedPutTagging, ObjectCreatedDeleteTagging,
}
case ObjectRemovedAll:
return []Name{
ObjectRemovedDelete,
ObjectRemovedDeleteMarkerCreated,
}
case ObjectReplicationAll:
return []Name{
ObjectReplicationFailed,
ObjectReplicationComplete,
ObjectReplicationNotTracked,
ObjectReplicationMissedThreshold,
ObjectReplicationReplicatedAfterThreshold,
}
case ObjectRestoreAll:
return []Name{
ObjectRestorePost,
ObjectRestoreCompleted,
}
case ObjectTransitionAll:
return []Name{
ObjectTransitionFailed,
ObjectTransitionComplete,
}
case ObjectScannerAll:
return []Name{
ObjectManyVersions,
PrefixManyFolders,
}
case Everything:
res := make([]Name, objectSingleTypesEnd-1)
for i := range res {
res[i] = Name(i + 1)
}
return res
default:
return []Name{name}
}
}
// Mask returns the type as mask.
// Compound "All" types are expanded.
func (name Name) Mask() uint64 {
if name < objectSingleTypesEnd {
return 1 << (name - 1)
}
var mask uint64
for _, n := range name.Expand() {
mask |= 1 << (n - 1)
}
return mask
}
// String - returns string representation of event type.
func (name Name) String() string {
switch name {
case BucketCreated:
return "s3:BucketCreated:*"
case BucketRemoved:
return "s3:BucketRemoved:*"
case ObjectAccessedAll:
return "s3:ObjectAccessed:*"
case ObjectAccessedGet:
return "s3:ObjectAccessed:Get"
case ObjectAccessedGetRetention:
return "s3:ObjectAccessed:GetRetention"
case ObjectAccessedGetLegalHold:
return "s3:ObjectAccessed:GetLegalHold"
case ObjectAccessedHead:
return "s3:ObjectAccessed:Head"
case ObjectCreatedAll:
return "s3:ObjectCreated:*"
case ObjectCreatedCompleteMultipartUpload:
return "s3:ObjectCreated:CompleteMultipartUpload"
case ObjectCreatedCopy:
return "s3:ObjectCreated:Copy"
case ObjectCreatedPost:
return "s3:ObjectCreated:Post"
case ObjectCreatedPut:
return "s3:ObjectCreated:Put"
case ObjectCreatedPutTagging:
return "s3:ObjectCreated:PutTagging"
case ObjectCreatedDeleteTagging:
return "s3:ObjectCreated:DeleteTagging"
case ObjectCreatedPutRetention:
return "s3:ObjectCreated:PutRetention"
case ObjectCreatedPutLegalHold:
return "s3:ObjectCreated:PutLegalHold"
case ObjectRemovedAll:
return "s3:ObjectRemoved:*"
case ObjectRemovedDelete:
return "s3:ObjectRemoved:Delete"
case ObjectRemovedDeleteMarkerCreated:
return "s3:ObjectRemoved:DeleteMarkerCreated"
case ObjectReplicationAll:
return "s3:Replication:*"
case ObjectReplicationFailed:
return "s3:Replication:OperationFailedReplication"
case ObjectReplicationComplete:
return "s3:Replication:OperationCompletedReplication"
case ObjectReplicationNotTracked:
return "s3:Replication:OperationNotTracked"
case ObjectReplicationMissedThreshold:
return "s3:Replication:OperationMissedThreshold"
case ObjectReplicationReplicatedAfterThreshold:
return "s3:Replication:OperationReplicatedAfterThreshold"
case ObjectRestoreAll:
return "s3:ObjectRestore:*"
case ObjectRestorePost:
return "s3:ObjectRestore:Post"
case ObjectRestoreCompleted:
return "s3:ObjectRestore:Completed"
case ObjectTransitionAll:
return "s3:ObjectTransition:*"
case ObjectTransitionFailed:
return "s3:ObjectTransition:Failed"
case ObjectTransitionComplete:
return "s3:ObjectTransition:Complete"
case ObjectManyVersions:
return "s3:Scanner:ManyVersions"
case PrefixManyFolders:
return "s3:Scanner:BigPrefix"
}
return ""
}
// MarshalXML - encodes to XML data.
func (name Name) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
return e.EncodeElement(name.String(), start)
}
// UnmarshalXML - decodes XML data.
func (name *Name) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var s string
if err := d.DecodeElement(&s, &start); err != nil {
return err
}
eventName, err := ParseName(s)
if err != nil {
return err
}
*name = eventName
return nil
}
// MarshalJSON - encodes to JSON data.
func (name Name) MarshalJSON() ([]byte, error) {
return json.Marshal(name.String())
}
// UnmarshalJSON - decodes JSON data.
func (name *Name) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
eventName, err := ParseName(s)
if err != nil {
return err
}
*name = eventName
return nil
}
// ParseName - parses string to Name.
func ParseName(s string) (Name, error) {
switch s {
case "s3:BucketCreated:*":
return BucketCreated, nil
case "s3:BucketRemoved:*":
return BucketRemoved, nil
case "s3:ObjectAccessed:*":
return ObjectAccessedAll, nil
case "s3:ObjectAccessed:Get":
return ObjectAccessedGet, nil
case "s3:ObjectAccessed:GetRetention":
return ObjectAccessedGetRetention, nil
case "s3:ObjectAccessed:GetLegalHold":
return ObjectAccessedGetLegalHold, nil
case "s3:ObjectAccessed:Head":
return ObjectAccessedHead, nil
case "s3:ObjectCreated:*":
return ObjectCreatedAll, nil
case "s3:ObjectCreated:CompleteMultipartUpload":
return ObjectCreatedCompleteMultipartUpload, nil
case "s3:ObjectCreated:Copy":
return ObjectCreatedCopy, nil
case "s3:ObjectCreated:Post":
return ObjectCreatedPost, nil
case "s3:ObjectCreated:Put":
return ObjectCreatedPut, nil
case "s3:ObjectCreated:PutRetention":
return ObjectCreatedPutRetention, nil
case "s3:ObjectCreated:PutLegalHold":
return ObjectCreatedPutLegalHold, nil
case "s3:ObjectCreated:PutTagging":
return ObjectCreatedPutTagging, nil
case "s3:ObjectCreated:DeleteTagging":
return ObjectCreatedDeleteTagging, nil
case "s3:ObjectRemoved:*":
return ObjectRemovedAll, nil
case "s3:ObjectRemoved:Delete":
return ObjectRemovedDelete, nil
case "s3:ObjectRemoved:DeleteMarkerCreated":
return ObjectRemovedDeleteMarkerCreated, nil
case "s3:Replication:*":
return ObjectReplicationAll, nil
case "s3:Replication:OperationFailedReplication":
return ObjectReplicationFailed, nil
case "s3:Replication:OperationCompletedReplication":
return ObjectReplicationComplete, nil
case "s3:Replication:OperationMissedThreshold":
return ObjectReplicationMissedThreshold, nil
case "s3:Replication:OperationReplicatedAfterThreshold":
return ObjectReplicationReplicatedAfterThreshold, nil
case "s3:Replication:OperationNotTracked":
return ObjectReplicationNotTracked, nil
case "s3:ObjectRestore:*":
return ObjectRestoreAll, nil
case "s3:ObjectRestore:Post":
return ObjectRestorePost, nil
case "s3:ObjectRestore:Completed":
return ObjectRestoreCompleted, nil
case "s3:ObjectTransition:Failed":
return ObjectTransitionFailed, nil
case "s3:ObjectTransition:Complete":
return ObjectTransitionComplete, nil
case "s3:ObjectTransition:*":
return ObjectTransitionAll, nil
case "s3:Scanner:ManyVersions":
return ObjectManyVersions, nil
case "s3:Scanner:BigPrefix":
return PrefixManyFolders, nil
default:
return 0, &ErrInvalidEventName{s}
}
}
golang-github-minio-pkg-3.0.10/go.mod 0000664 0000000 0000000 00000006305 14655770405 0017341 0 ustar 00root root 0000000 0000000 module github.com/minio/pkg/v3
go 1.21
require (
github.com/cheggaaa/pb v1.0.29
github.com/fatih/color v1.16.0
github.com/fatih/structs v1.1.0
github.com/go-ldap/ldap/v3 v3.4.8
github.com/lestrrat-go/jwx v1.2.29
github.com/mattn/go-colorable v0.1.13
github.com/mattn/go-isatty v0.0.20
github.com/minio/madmin-go/v3 v3.0.51
github.com/minio/minio-go/v7 v7.0.70
github.com/minio/mux v1.8.2
github.com/montanaflynn/stats v0.7.1
github.com/rjeczalik/notify v0.9.3
github.com/tinylib/msgp v1.2.0
go.etcd.io/etcd/client/v3 v3.5.13
golang.org/x/crypto v0.24.0
golang.org/x/sys v0.21.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/kr/pretty v0.2.1 // indirect
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/option v1.0.1 // indirect
github.com/lufia/plan9stats v0.0.0-20240408141607-282e7b5d6b74 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.53.0 // indirect
github.com/prometheus/procfs v0.14.0 // indirect
github.com/prometheus/prom2json v1.3.3 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rs/xid v1.5.0 // indirect
github.com/safchain/ethtool v0.3.0 // indirect
github.com/secure-io/sio-go v0.3.1 // indirect
github.com/shirou/gopsutil/v3 v3.24.4 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/tklauser/go-sysconf v0.3.14 // indirect
github.com/tklauser/numcpus v0.8.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.etcd.io/etcd/api/v3 v3.5.13 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.13 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/text v0.16.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240509183442-62759503f434 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434 // indirect
google.golang.org/grpc v1.63.2 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
)
golang-github-minio-pkg-3.0.10/go.sum 0000664 0000000 0000000 00000063505 14655770405 0017373 0 ustar 00root root 0000000 0000000 github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo=
github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk=
github.com/go-asn1-ber/asn1-ber v1.5.7/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ=
github.com/go-ldap/ldap/v3 v3.4.8/go.mod h1:qS3Sjlu76eHfHGpUdWkAXQTw4beih+cHsco2jXlIXrk=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A=
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k=
github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
github.com/lestrrat-go/jwx v1.2.29 h1:QT0utmUJ4/12rmsVQrJ3u55bycPkKqGYuGT4tyRhxSQ=
github.com/lestrrat-go/jwx v1.2.29/go.mod h1:hU8k2l6WF0ncx20uQdOmik/Gjg6E3/wIRtXSNFeZuB8=
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lufia/plan9stats v0.0.0-20240408141607-282e7b5d6b74 h1:1KuuSOy4ZNgW0KA2oYIngXVFhQcXxhLqCVK7cBcldkk=
github.com/lufia/plan9stats v0.0.0-20240408141607-282e7b5d6b74/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/minio/madmin-go/v3 v3.0.51 h1:brGOvDP8KvoHb/bdzCHUPFCbTtrN8o507uPHZpyuinM=
github.com/minio/madmin-go/v3 v3.0.51/go.mod h1:IFAwr0XMrdsLovxAdCcuq/eoL4nRuMVQQv0iubJANQw=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.70 h1:1u9NtMgfK1U42kUxcsl5v0yj6TEOPR497OAQxpJnn2g=
github.com/minio/minio-go/v7 v7.0.70/go.mod h1:4yBA8v80xGA30cfM3fz0DKYMXunWl/AV/6tWEs9ryzo=
github.com/minio/mux v1.8.2 h1:r9oVDFM09y+u8CF4HPLanguAG41niXgYwZAFkVHce9M=
github.com/minio/mux v1.8.2/go.mod h1:1pAare17ZRL5GpmNL+9YmqHoWnLmMZF9C/ioUCfy0BQ=
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986 h1:jYi87L8j62qkXzaYHAQAhEapgukhenIMZRBKTNRLHJ4=
github.com/philhofer/fwd v1.1.3-0.20240612014219-fbbf4953d986/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE=
github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U=
github.com/prometheus/procfs v0.14.0 h1:Lw4VdGGoKEZilJsayHf0B+9YgLGREba2C6xr+Fdfq6s=
github.com/prometheus/procfs v0.14.0/go.mod h1:XL+Iwz8k8ZabyZfMFHPiilCniixqQarAy5Mu67pHlNQ=
github.com/prometheus/prom2json v1.3.3 h1:IYfSMiZ7sSOfliBoo89PcufjWO4eAR0gznGcETyaUgo=
github.com/prometheus/prom2json v1.3.3/go.mod h1:Pv4yIPktEkK7btWsrUTWDDDrnpUrAELaOCj+oFwlgmc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY=
github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
github.com/secure-io/sio-go v0.3.1 h1:dNvY9awjabXTYGsTF1PiCySl9Ltofk9GA3VdWlo7rRc=
github.com/secure-io/sio-go v0.3.1/go.mod h1:+xbkjDzPjwh4Axd07pRKSNriS9SCiYksWnZqdnfpQxs=
github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU=
github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tinylib/msgp v1.2.0 h1:0uKB/662twsVBpYUPbokj4sTSKhWFKB7LopO2kWK8lY=
github.com/tinylib/msgp v1.2.0/go.mod h1:2vIGs3lcUo8izAATNobrCHevYZC/LMsJtw4JPiYPHro=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.etcd.io/etcd/api/v3 v3.5.13 h1:8WXU2/NBge6AUF1K1gOexB6e07NgsN1hXK0rSTtgSp4=
go.etcd.io/etcd/api/v3 v3.5.13/go.mod h1:gBqlqkcMMZMVTMm4NDZloEVJzxQOQIls8splbqBDa0c=
go.etcd.io/etcd/client/pkg/v3 v3.5.13 h1:RVZSAnWWWiI5IrYAXjQorajncORbS0zI48LQlE2kQWg=
go.etcd.io/etcd/client/pkg/v3 v3.5.13/go.mod h1:XxHT4u1qU12E2+po+UVPrEeL94Um6zL58ppuJWXSAB8=
go.etcd.io/etcd/client/v3 v3.5.13 h1:o0fHTNJLeO0MyVbc7I3fsCf6nrOqn5d+diSarKnB2js=
go.etcd.io/etcd/client/v3 v3.5.13/go.mod h1:cqiAeY8b5DEEcpxvgWKsbLIWNM/8Wy2xJSDMtioMcoI=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20240509183442-62759503f434 h1:OpXbo8JnN8+jZGPrL4SSfaDjSCjupr8lXyBAbexEm/U=
google.golang.org/genproto/googleapis/api v0.0.0-20240509183442-62759503f434/go.mod h1:FfiGhwUm6CJviekPrc0oJ+7h29e+DmWU6UtjX0ZvI7Y=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434 h1:umK/Ey0QEzurTNlsV3R+MfxHAb78HCEX/IkuR+zH4WQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM=
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
golang-github-minio-pkg-3.0.10/ldap/ 0000775 0000000 0000000 00000000000 14655770405 0017147 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/ldap/decode_dn_contrib.go 0000664 0000000 0000000 00000007467 14655770405 0023140 0 ustar 00root root 0000000 0000000 // The MIT License (MIT)
// Copyright (c) 2011-2015 Michael Mitton (mmitton@gmail.com)
// Portions copyright (c) 2015-2016 go-ldap Authors
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package ldap
import (
"encoding/hex"
"errors"
"fmt"
"strings"
ldap "github.com/go-ldap/ldap/v3"
)
// DecodeDN - remove leading and trailing spaces from the attribute type and value
// and unescape any escaped characters in these fields
//
// pulled from the go-ldap library
// https://github.com/go-ldap/ldap/blob/dbdc485259442f987d83e604cd4f5859cfc1be58/dn.go
func DecodeDN(str string) (string, error) {
s := []rune(stripLeadingAndTrailingSpaces(str))
builder := strings.Builder{}
for i := 0; i < len(s); i++ {
char := s[i]
// If the character is not an escape character, just add it to the
// builder and continue
if char != '\\' {
builder.WriteRune(char)
continue
}
// If the escape character is the last character, it's a corrupted
// escaped character
if i+1 >= len(s) {
return "", ldap.NewError(34, fmt.Errorf("got corrupted escaped character: '%s'", string(s)))
}
// If the escaped character is a special character, just add it to
// the builder and continue
switch s[i+1] {
case ' ', '"', '#', '+', ',', ';', '<', '=', '>', '\\':
builder.WriteRune(s[i+1])
i++
continue
}
// If the escaped character is not a special character, it should
// be a hex-encoded character of the form \XX if it's not at least
// two characters long, it's a corrupted escaped character
if i+2 >= len(s) {
return "", ldap.NewError(34, errors.New("unable to decode escaped character: encoding/hex: invalid byte: "+string(s[i+1])))
}
// Get the runes for the two characters after the escape character
// and convert them to a byte slice
xx := []byte(string(s[i+1 : i+3]))
// If the two runes are not hex characters and result in more than
// two bytes when converted to a byte slice, it's a corrupted
// escaped character
if len(xx) != 2 {
return "", ldap.NewError(34, fmt.Errorf("unable to decode escaped character: invalid byte: %s", string(xx)))
}
// Decode the hex-encoded character and add it to the builder
dst := []byte{0}
if n, err := hex.Decode(dst, xx); err != nil {
return "", ldap.NewError(34, errors.New("unable to decode escaped character: "+err.Error()))
} else if n != 1 {
return "", ldap.NewError(34, fmt.Errorf("unable to decode escaped character: encoding/hex: expected 1 byte when un-escaping, got %d", n))
}
builder.WriteByte(dst[0])
i += 2
}
return builder.String(), nil
}
func stripLeadingAndTrailingSpaces(inVal string) string {
noSpaces := strings.Trim(inVal, " ")
// Re-add the trailing space if it was an escaped space
if len(noSpaces) > 0 && noSpaces[len(noSpaces)-1] == '\\' && inVal[len(inVal)-1] == ' ' {
noSpaces += " "
}
return noSpaces
}
golang-github-minio-pkg-3.0.10/ldap/decode_dn_test.go 0000664 0000000 0000000 00000005316 14655770405 0022446 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2024 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package ldap
import (
"errors"
"fmt"
"testing"
)
func TestDecodeDN(t *testing.T) {
testCases := []struct {
input string
expected string
err error
}{
{
input: "cn=foo,dc=example,dc=com",
expected: "cn=foo,dc=example,dc=com",
},
{
input: `cn=\d0\bf\d1\80\d0\b5\d1\86\d0\b5\d0\b4\d0\b5\d0\bd\d1\82 \d1\82\d0\b5\d1\81\d1\82,dc=example,dc=com`,
expected: "cn=прецедент тест,dc=example,dc=com",
},
{
input: `cn=pr\c3\bcfen,dc=example,dc=com`,
expected: "cn=prüfen,dc=example,dc=com",
},
{
input: `cn=fo\20o,dc=example,dc=com`,
expected: "cn=fo o,dc=example,dc=com",
},
{
input: `cn=\e6\b5\8b\e8\af\95,dc=example,dc=com`,
expected: "cn=测试,dc=example,dc=com",
},
{
input: `cn=\e6\b8\ac\e8\a9\a6,dc=example,dc=com`,
expected: "cn=測試,dc=example,dc=com",
},
{
input: `cn=svc\ef\b9\92algorithm,dc=example,dc=com`,
expected: "cn=svc﹒algorithm,dc=example,dc=com",
},
{
input: `cn=\e0\a4\9c\e0\a4\be\e0\a4\81\e0\a4\9a,dc=example,dc=com`,
expected: "cn=जाँच,dc=example,dc=com",
},
{
input: `cn=\f0\9f\a7\aa\f0\9f\93\9d,dc=example,dc=com`,
expected: "cn=🧪📝,dc=example,dc=com",
},
{
input: `cn=foo,dc=example,dc=com\`,
err: fmt.Errorf("got corrupted escaped character: '%s'", `cn=foo,dc=example,dc=com\`),
},
{
input: `cn=foo,dc=example,dc=com\a`,
err: fmt.Errorf("unable to decode escaped character: encoding/hex: invalid byte: %s", "a"),
},
}
for i, testCase := range testCases {
t.Run(fmt.Sprintf("test case %d", i), func(t *testing.T) {
output, err := DecodeDN(testCase.input)
if err != nil && testCase.err == nil {
t.Fatalf("unexpected error: %v", err)
}
if testCase.err != nil && errors.Is(err, testCase.err) {
t.Fatalf("expected error `%v`, got `%v`", testCase.err, err)
}
if output != testCase.expected {
t.Fatalf("expected %q, got %q", testCase.expected, output)
}
})
}
}
golang-github-minio-pkg-3.0.10/ldap/ldap.go 0000664 0000000 0000000 00000027356 14655770405 0020433 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2022 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
// Package ldap defines the LDAP configuration object and methods used by the
// MinIO server.
package ldap
import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net"
"strings"
"time"
ldap "github.com/go-ldap/ldap/v3"
)
const (
dnDelimiter = ";"
attrDelimiter = ","
)
var (
// noAttrsSpec should be used in an LDAP search when no attributes are
// requested to be fetched. Ref:
// https://www.rfc-editor.org/rfc/rfc4511#section-4.5.1.8
noAttrsSpec = []string{"1.1"}
)
// BaseDNInfo contains information about a base DN.
type BaseDNInfo struct {
// User provided base DN.
Original string
// DN string returned by the LDAP server. This value is used as the
// canonical form of the DN.
ServerDN string
// Parsed DN (from `ServerDN` value, not `Original`).
Parsed *ldap.DN
}
// Config contains configuration to connect to an LDAP server.
type Config struct {
Enabled bool
// E.g. "ldap.minio.io:636"
ServerAddr string
SRVRecordName string
TLSSkipVerify bool // allows skipping TLS verification
ServerInsecure bool // allows plain text connection to LDAP server
ServerStartTLS bool // allows using StartTLS connection to LDAP server
RootCAs *x509.CertPool
// Lookup bind LDAP service account
LookupBindDN string
LookupBindPassword string
// User DN search parameters
UserDNSearchBaseDistName string
// this is a computed value from UserDNSearchBaseDistName
userDNSearchBaseDistNames []BaseDNInfo
UserDNSearchFilter string
// Additional attributes to fetch from the user DN search.
UserDNAttributes string
// this is a computed value from UserDNAttributes
userDNAttributesList []string
// Group search parameters
GroupSearchBaseDistName string
// this is a computed value from GroupSearchBaseDistName
groupSearchBaseDistNames []BaseDNInfo
GroupSearchFilter string
}
// Clone creates a copy of the config.
func (l *Config) Clone() (cloned Config) {
cloned = *l
return cloned
}
func (l *Config) connect(ldapAddr string) (ldapConn *ldap.Conn, err error) {
tlsConfig := &tls.Config{
InsecureSkipVerify: l.TLSSkipVerify,
RootCAs: l.RootCAs,
}
if l.ServerInsecure {
ldapConn, err = ldap.Dial("tcp", ldapAddr)
} else {
if l.ServerStartTLS {
ldapConn, err = ldap.Dial("tcp", ldapAddr)
} else {
ldapConn, err = ldap.DialTLS("tcp", ldapAddr, tlsConfig)
}
}
if ldapConn != nil {
ldapConn.SetTimeout(30 * time.Second) // Change default timeout to 30 seconds.
if l.ServerStartTLS {
err = ldapConn.StartTLS(tlsConfig)
}
}
return ldapConn, err
}
// Connect connect to ldap server.
func (l *Config) Connect() (ldapConn *ldap.Conn, err error) {
if l == nil || !l.Enabled {
return nil, errors.New("LDAP is not configured")
}
var srvService, srvProto, srvName string
switch l.SRVRecordName {
case "on":
srvName = l.ServerAddr
case "ldap", "ldaps":
srvService = l.SRVRecordName
srvProto = "tcp"
srvName = l.ServerAddr
case "":
default:
return nil, errors.New("Invalid SRV Record Name parameter")
}
if srvName == "" {
// No SRV Record lookup case.
ldapAddr := l.ServerAddr
_, _, err = net.SplitHostPort(ldapAddr)
if err != nil {
if strings.Contains(err.Error(), "missing port in address") {
// Use default LDAP port if none specified "636"
ldapAddr = net.JoinHostPort(ldapAddr, "636")
} else {
return nil, err
}
}
return l.connect(ldapAddr)
}
// SRV Record lookup is enabled.
_, addrs, err := net.LookupSRV(srvService, srvProto, srvName)
if err != nil {
return nil, fmt.Errorf("DNS SRV Record lookup error: %w", err)
}
var errs []error
// Return a connection to the first server to which we could connect.
for _, addr := range addrs {
ldapAddr := fmt.Sprintf("%s:%d", addr.Target, addr.Port)
ldapConn, err = l.connect(ldapAddr)
if err == nil {
return ldapConn, nil
}
errs = append(errs, err)
}
// If none of the servers could connect, we all the errors.
var errMsgs []string
for i, e := range errs {
errMsgs = append(errMsgs, fmt.Sprintf("Connect err to %s:%d - %v", addrs[i].Target, addrs[i].Port, e))
}
err = fmt.Errorf("Could not connect to any LDAP server: %s", strings.Join(errMsgs, "; "))
return nil, err
}
// LookupBind connects to LDAP server using the bind user credentials.
func (l *Config) LookupBind(conn *ldap.Conn) error {
var err error
if l.LookupBindPassword == "" {
err = conn.UnauthenticatedBind(l.LookupBindDN)
} else {
err = conn.Bind(l.LookupBindDN, l.LookupBindPassword)
}
if err != nil {
if ldap.IsErrorWithCode(err, 49) {
return fmt.Errorf("LDAP Lookup Bind user invalid credentials error: %w", err)
}
return fmt.Errorf("LDAP client: %w", err)
}
return nil
}
// GetUserDNSearchBaseDistNames returns the user DN search base DN list.
func (l *Config) GetUserDNSearchBaseDistNames() []BaseDNInfo {
return l.userDNSearchBaseDistNames
}
// GetUserDNAttributesList returns the user attributes list.
func (l *Config) GetUserDNAttributesList() []string {
return l.userDNAttributesList
}
// GetGroupSearchBaseDistNames returns the group search base DN list.
func (l *Config) GetGroupSearchBaseDistNames() []BaseDNInfo {
return l.groupSearchBaseDistNames
}
// DNSearchResult contains the result of a DN search. The attibutes map may be
// empty if no attributes were requested or if no attributes were found.
type DNSearchResult struct {
// Normalized DN of the user.
NormDN string
// Actual DN of the user.
ActualDN string
// Attributes of the user.
Attributes map[string][]string
}
// LookupUsername searches for the DN of the user given their login username.
// conn is assumed to be using the lookup bind service account.
//
// It is required that the search return at most one result.
//
// If the user does not exist, an error is returned that starts with:
//
// "User DN not found for:"
func (l *Config) LookupUsername(conn *ldap.Conn, username string) (*DNSearchResult, error) {
attrsToFetch := noAttrsSpec
if len(l.userDNAttributesList) > 0 {
attrsToFetch = l.userDNAttributesList
}
filter := strings.ReplaceAll(l.UserDNSearchFilter, "%s", ldap.EscapeFilter(username))
var foundDistNames []DNSearchResult
for _, userSearchBase := range l.userDNSearchBaseDistNames {
searchRequest := ldap.NewSearchRequest(
userSearchBase.ServerDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
filter,
attrsToFetch,
nil,
)
searchResult, err := conn.Search(searchRequest)
if err != nil {
// For a search, if the base DN does not exist, we get a 32 error code.
// Ref: https://ldap.com/ldap-result-code-reference/
//
// This situation is an error because the base DN should exist -
// it's existence is checked during configuration validation but it
// is possible that the base DN was deleted after the validation.
if ldap.IsErrorWithCode(err, 32) {
return nil, fmt.Errorf("Base DN (%s) for user DN search does not exist: %w",
searchRequest.BaseDN, err)
}
return nil, err
}
for _, entry := range searchResult.Entries {
normDN, err := NormalizeDN(entry.DN)
if err != nil {
return nil, err
}
attrs := make(map[string][]string, len(entry.Attributes))
for _, attr := range entry.Attributes {
attrs[attr.Name] = attr.Values
}
foundDistNames = append(foundDistNames, DNSearchResult{
NormDN: normDN,
ActualDN: entry.DN,
Attributes: attrs,
})
}
}
if len(foundDistNames) == 0 {
return nil, fmt.Errorf("User DN not found for: %s", username)
}
if len(foundDistNames) != 1 {
return nil, fmt.Errorf("Multiple DNs for %s found - please fix the search filter", username)
}
return &foundDistNames[0], nil
}
// SearchForUserGroups finds the groups of the user.
func (l *Config) SearchForUserGroups(conn *ldap.Conn, username, bindDN string) ([]string, error) {
// User groups lookup.
var groups []string
if l.GroupSearchFilter != "" {
for _, groupSearchBase := range l.groupSearchBaseDistNames {
filter := strings.ReplaceAll(l.GroupSearchFilter, "%s", ldap.EscapeFilter(username))
filter = strings.ReplaceAll(filter, "%d", ldap.EscapeFilter(bindDN))
searchRequest := ldap.NewSearchRequest(
groupSearchBase.ServerDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
filter,
noAttrsSpec,
nil,
)
var newGroups []string
newGroups, err := getGroups(conn, searchRequest)
if err != nil {
errRet := fmt.Errorf("Error finding groups of %s: %w", bindDN, err)
return nil, errRet
}
groups = append(groups, newGroups...)
}
}
return groups, nil
}
func getGroups(conn *ldap.Conn, sreq *ldap.SearchRequest) ([]string, error) {
var groups []string
sres, err := conn.Search(sreq)
if err != nil {
// For a search, if the base DN does not exist, we get a 32 error code.
// Ref: https://ldap.com/ldap-result-code-reference/
if ldap.IsErrorWithCode(err, 32) {
return nil, fmt.Errorf("Base DN (%s) for group search does not exist: %w",
sreq.BaseDN, err)
}
return nil, fmt.Errorf("LDAP client: %w", err)
}
for _, entry := range sres.Entries {
// We only queried one attribute,
// so we only look up the first one.
normalizedDN, err := NormalizeDN(entry.DN)
if err != nil {
return nil, err
}
groups = append(groups, normalizedDN)
}
return groups, nil
}
// LookupDN looks a given DN and returns its normalized form along with any
// requested attributes. It only performs a base object search to check if the
// DN exists. If the DN does not exist on the server, it returns a nil result
// and a nil error.
func LookupDN(conn *ldap.Conn, dn string, attrs []string) (*DNSearchResult, error) {
attrsToFetch := noAttrsSpec
if len(attrs) > 0 {
attrsToFetch = attrs
}
// Check if the DN is valid.
searchRequest := ldap.NewSearchRequest(
dn,
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
"(objectClass=*)",
attrsToFetch,
nil,
)
// This search should return at most one result as it is a base object
// search.
searchResult, err := conn.Search(searchRequest)
if err != nil {
// For a search, if the base DN does not exist, we get a 32 error code.
// Ref: https://ldap.com/ldap-result-code-reference/
//
// Return no DN and nil error.
if ldap.IsErrorWithCode(err, 32) {
return nil, nil
}
return nil, fmt.Errorf("LDAP client: %w", err)
}
if len(searchResult.Entries) != 1 {
return nil, fmt.Errorf(
"Multiple DNs found for %s - this should not happen for a base object search",
dn)
}
foundDistName, err := NormalizeDN(searchResult.Entries[0].DN)
if err != nil {
return nil, err
}
foundAttrs := make(map[string][]string, len(searchResult.Entries[0].Attributes))
for _, attr := range searchResult.Entries[0].Attributes {
foundAttrs[attr.Name] = attr.Values
}
return &DNSearchResult{
NormDN: foundDistName,
ActualDN: searchResult.Entries[0].DN,
Attributes: foundAttrs,
}, nil
}
// NormalizeDN normalizes the DN. The ldap library here mainly lowercases the
// attribute type names in the DN.
func NormalizeDN(dn string) (string, error) {
parsedDN, err := ldap.ParseDN(dn)
if err != nil {
return "", fmt.Errorf("DN (%s) parse failure: %w", dn, err)
}
return parsedDN.String(), nil
}
golang-github-minio-pkg-3.0.10/ldap/validation.go 0000664 0000000 0000000 00000036202 14655770405 0021633 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2022 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package ldap
import (
"fmt"
"regexp"
"strings"
"github.com/go-ldap/ldap/v3"
"github.com/minio/minio-go/v7/pkg/set"
)
// Result - type for high-level names for the validation status of the config.
type Result string
// Constant values for Result type.
const (
ConfigOk Result = "Config OK"
ConnectivityError Result = "LDAP Server Connection Error"
ConnectionParamMisconfigured Result = "LDAP Server Connection Parameters Misconfigured"
LookupBindError Result = "LDAP Lookup Bind Error"
UserSearchParamsMisconfigured Result = "User Search Parameters Misconfigured"
GroupSearchParamsMisconfigured Result = "Group Search Parameters Misconfigured"
UserDNLookupError Result = "User DN Lookup Error"
GroupMembershipsLookupError Result = "Group Memberships Lookup Error"
)
// Validation returns feedback on the configuration. The `Suggestion` field
// needs to be "printed" for friendly display (it can contain escaped newlines
// `\n`).
type Validation struct {
Result Result
Detail string
Suggestion string
ErrCause error
}
// Error instance for Validation.
func (v Validation) Error() string {
if v.Result == ConfigOk {
return ""
}
return fmt.Sprintf("%s: %s", string(v.Result), v.Detail)
}
// FormatError returns detailed validation error information.
func (v Validation) FormatError() string {
if v.Result == ConfigOk {
return ""
}
messages := []string{
fmt.Sprintf("Result: %s", string(v.Result)),
fmt.Sprintf("Detail: %s", v.Detail),
}
if v.Suggestion != "" {
messages = append(messages, fmt.Sprintf("Suggestion: %s", v.Suggestion))
}
if v.ErrCause != nil {
messages = append(messages, fmt.Sprintf("Due to: %s", v.ErrCause.Error()))
}
return strings.Join(messages, "\n")
}
// IsOk - returns if the validation succeeded.
func (v Validation) IsOk() bool {
return v.Result == ConfigOk
}
// UserLookupResult returns the DN found for the test user and their group
// memberships.
type UserLookupResult struct {
DN string
DNAttributes map[string][]string
GroupDNMemberships []string
}
var validSRVRecordNames = set.CreateStringSet("ldap", "ldaps", "on")
// Validate validates the LDAP configuration. It can be called with any subset
// of configuration parameters provided by the user - it will return
// information on what needs to be done to fix the problem if any.
//
// This function updates the UserDNSearchBaseDistNames and
// GroupSearchBaseDistNames fields of the Config - however this an idempotent
// operation. This is done to support configuration validation in Console/mc and
// for tests.
func (l *Config) Validate() Validation {
if !l.Enabled {
return Validation{Result: ConfigOk, Detail: "Config is not enabled"}
}
if l.ServerAddr == "" {
return Validation{
Result: ConnectionParamMisconfigured,
Detail: "Address is empty",
Suggestion: "Set a server address.",
}
}
if l.SRVRecordName != "" && !validSRVRecordNames.Contains(l.SRVRecordName) {
return Validation{
Result: ConnectionParamMisconfigured,
Detail: "SRV Record Name is invalid",
Suggestion: `If given, SRV Record Name must be one of "ldap", "ldaps" or "on".
Please refer to documentation for more details`,
}
}
conn, err := l.Connect()
if err != nil {
return Validation{
Result: ConnectivityError,
Detail: fmt.Sprintf("Could not connect to LDAP server: %v", err),
ErrCause: err,
Suggestion: `Check:
(1) server address
(2) TLS parameters,
(3) LDAP server's TLS certificate is trusted by MinIO (when using TLS - highly recommended)
(4) SRV Record lookup if given, and
(5) LDAP service is up and reachable`,
}
}
defer conn.Close()
if l.LookupBindDN == "" {
return Validation{
Result: LookupBindError,
Detail: "Lookup Bind UserDN not specified",
Suggestion: "Specify LDAP service account credentials for performing lookups.",
}
}
if err := l.LookupBind(conn); err != nil {
return Validation{
Result: LookupBindError,
ErrCause: err,
Detail: fmt.Sprintf("Error connecting as LDAP Lookup Bind user: %v", err),
Suggestion: "Check LDAP Lookup Bind user credentials and if user is allowed to login",
}
}
// Validate User Lookup parameters
userBaseDNList := splitAndTrim(l.UserDNSearchBaseDistName, dnDelimiter)
l.userDNSearchBaseDistNames, err = validateAndParseBaseDNList(conn, userBaseDNList)
if err != nil {
return Validation{
Result: UserSearchParamsMisconfigured,
Detail: fmt.Sprintf("UserDN search base DN failed to validate/parse: %v", err),
Suggestion: "Set the UserDN search base to a valid DN - e.g. as returned by an LDAP search",
}
}
if len(l.userDNSearchBaseDistNames) == 0 {
return Validation{
Result: UserSearchParamsMisconfigured,
Detail: "UserDN search base is empty",
Suggestion: "Set the UserDN search base to the DN of the directory subtree where users are present",
}
}
// Validate that BaseDNs represent non-overlapping subtrees.
if ancestor, descendant := checkForDNOverlaps(l.userDNSearchBaseDistNames); ancestor != "" {
return Validation{
Result: UserSearchParamsMisconfigured,
Detail: fmt.Sprintf("User Search Base DN `%s` is an ancestor of `%s`", ancestor, descendant),
Suggestion: "No two base DNs may overlap - please remove one of them",
}
}
userDNAttributes := splitAndTrim(l.UserDNAttributes, attrDelimiter)
if len(userDNAttributes) > 0 {
// Check that the attributes are valid.
if err := validateAttributes(userDNAttributes); err != nil {
return Validation{
Result: UserSearchParamsMisconfigured,
Detail: fmt.Sprintf("UserDN attributes `%s` are invalid: %v", l.UserDNAttributes, err),
Suggestion: "Ensure that the attribute names are valid LDAP short names of attributes (not OIDs)",
}
}
}
l.userDNAttributesList = userDNAttributes
if l.UserDNSearchFilter == "" {
return Validation{
Result: UserSearchParamsMisconfigured,
Detail: "UserDN search filter is empty",
Suggestion: `Set the UserDN search filter template:
Use "%s" - it will be replaced by the login user name and sent to the LDAP server.
For example: "(uid=%s)"`,
}
}
if strings.Contains(l.UserDNSearchFilter, "%d") {
return Validation{
Result: UserSearchParamsMisconfigured,
Detail: "User DN search filter contains `%d`",
Suggestion: `User DN search filter is a template where "%s" is replaced by the login username.
"%d" is not supported here.
Please provide a search filter containing "%s"`,
}
}
if !strings.Contains(l.UserDNSearchFilter, "%s") {
return Validation{
Result: UserSearchParamsMisconfigured,
Detail: "User DN search filter does not contain `%s`",
Suggestion: `During login, the user's DN is looked up using the search filter template:
"%s" gets replaced by the given username - it must be used.
Enter an LDAP search filter containing "%s"`,
}
}
// Check that the LDAP filter compiles.
if err := compileFilter(l.UserDNSearchFilter); err != nil {
return Validation{
Result: UserSearchParamsMisconfigured,
Detail: fmt.Sprintf("User DN search filter `%s` failed to compile: %v", l.UserDNSearchFilter, err),
Suggestion: `Ensure that the User DN search filter is valid`,
}
}
// If group lookup is not configured, it's ok.
if l.GroupSearchBaseDistName != "" || l.GroupSearchFilter != "" {
// Validate Group Search parameters.
groupBaseDNList := splitAndTrim(l.GroupSearchBaseDistName, dnDelimiter)
l.groupSearchBaseDistNames, err = validateAndParseBaseDNList(conn, groupBaseDNList)
if err != nil {
return Validation{
Result: GroupSearchParamsMisconfigured,
Detail: fmt.Sprintf("Group Search Base DN failed to parse: %v", err),
Suggestion: "Set the Group Search Base DN to a valid DN - e.g. as returned by an LDAP search",
}
}
if len(l.groupSearchBaseDistNames) == 0 {
return Validation{
Result: GroupSearchParamsMisconfigured,
Detail: "Group Search Base DN is required.",
Suggestion: `Since you entered a value for the Group Search Filter - enter a value for the Group Search Base DN too:
Enter this value as the DN of the subtree where groups will be found.`,
}
}
// Validate that BaseDNs represent non-overlapping subtrees.
if ancestor, descendant := checkForDNOverlaps(l.groupSearchBaseDistNames); ancestor != "" {
return Validation{
Result: GroupSearchParamsMisconfigured,
Detail: fmt.Sprintf("Group Search Base DN `%s` is an ancestor of `%s`", ancestor, descendant),
Suggestion: "No two base DNs may overlap - please remove one of them",
}
}
if l.GroupSearchFilter == "" {
return Validation{
Result: GroupSearchParamsMisconfigured,
Detail: "Group Search Filter is required.",
Suggestion: `Since you entered a value for the Group Search Base DN - enter a value for the Group Search Filter too. This is a template where, before the query is sent to the server:
"%s" is replaced with the login username;
"%d" is replaced with the DN of the login user.
For example: "(&(objectclass=groupOfNames)(memberUid=%s))"`,
}
}
if !strings.Contains(l.GroupSearchFilter, "%d") && !strings.Contains(l.GroupSearchFilter, "%s") {
return Validation{
Result: GroupSearchParamsMisconfigured,
Detail: `GroupSearchFilter must contain at least one of "%s" or "%d"`,
Suggestion: `During group membership lookup the group search filter template is used:
"%s" gets replaced by the given username, and
"%d" gets replaced by the user's DN.
Either one is needed to find only groups that the user is a member of.
Enter an LDAP search filter template using at least one of these.`,
}
}
// Check that the LDAP filter compiles.
if err := compileFilter(l.GroupSearchFilter); err != nil {
return Validation{
Result: GroupSearchParamsMisconfigured,
Detail: fmt.Sprintf("Group DN search filter `%s` failed to compile: %v", l.GroupSearchFilter, err),
Suggestion: `Ensure that the Group DN search filter is valid`,
}
}
}
return Validation{
Result: ConfigOk,
}
}
// ValidateLookup takes a test username and performs user and group lookup (if
// configured) and returns the result. It is to validate the LDAP configuration.
// The lookup is performed without requiring the password for the test user -
// and so can be used to test any LDAP user intending to use MinIO.
func (l *Config) ValidateLookup(testUsername string) (*UserLookupResult, Validation) {
if testUsername == "" {
return nil, Validation{
Result: UserDNLookupError,
Detail: "Provided username is empty",
}
}
if r := l.Validate(); !r.IsOk() {
return nil, r
}
conn, err := l.Connect()
if err != nil {
return nil, Validation{
Result: ConnectivityError,
Detail: fmt.Sprintf("Could not connect to LDAP server: %v", err),
ErrCause: err,
Suggestion: `Check:
(1) server address
(2) TLS parameters, and
(3) LDAP server's TLS certificate is trusted by MinIO (when using TLS - highly recommended)`,
}
}
defer conn.Close()
if err := l.LookupBind(conn); err != nil {
return nil, Validation{
Result: LookupBindError,
ErrCause: err,
Detail: fmt.Sprintf("Error connecting as LDAP Lookup Bind user: %v", err),
Suggestion: "Check LDAP Lookup Bind user credentials and if user is allowed to login",
}
}
// Lookup the given username.
dnResult, err := l.LookupUsername(conn, testUsername)
if err != nil {
return nil, Validation{
Result: UserDNLookupError,
Detail: fmt.Sprintf("Got an error when looking up user (%s) DN: %v", testUsername, err),
ErrCause: err,
Suggestion: `Check if this is a temporary error and try again.
Perhaps there is an error in the user search filter or user search base DN.`,
}
}
// Lookup groups.
groups, err := l.SearchForUserGroups(conn, testUsername, dnResult.NormDN)
if err != nil {
return nil, Validation{
Result: GroupMembershipsLookupError,
Detail: fmt.Sprintf("Got an error when looking up groups for user(=>%s, dn=>%s): %v",
testUsername, dnResult.NormDN, err),
ErrCause: err,
Suggestion: `Check if this is a temporary error and try again.
Perhaps there is an error in the group search filter or group search base DN.`,
}
}
return &UserLookupResult{
DN: dnResult.NormDN,
DNAttributes: dnResult.Attributes,
GroupDNMemberships: groups,
}, Validation{
Result: ConfigOk,
Detail: "User lookup done.",
}
}
// Splits on given delimiter, trims leading/trailing whitespace and removes
// empty values.
func splitAndTrim(s, sep string) (res []string) {
parts := strings.Split(s, sep)
for i := range parts {
v := strings.TrimSpace(parts[i])
if len(v) == 0 {
continue
}
res = append(res, v)
}
return
}
// Validates that the given DNs are present in the LDAP server.
func validateAndParseBaseDNList(conn *ldap.Conn, baseDNList []string) ([]BaseDNInfo, error) {
var res []BaseDNInfo
for _, dn := range baseDNList {
lookupResult, err := LookupDN(conn, dn, nil)
if err != nil {
return nil, fmt.Errorf("Base DN `%s` lookup failed: %w", dn, err)
}
if lookupResult == nil {
return nil, fmt.Errorf("Base DN `%s` not found in the LDAP server", dn)
}
serverDN := lookupResult.NormDN
parsed, err := ldap.ParseDN(serverDN)
if err != nil {
return nil, fmt.Errorf("Unexpectedly failed to parse DN `%s`: %w", serverDN, err)
}
res = append(res, BaseDNInfo{Original: dn, ServerDN: serverDN, Parsed: parsed})
}
return res, nil
}
var (
validAttributeRegex = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9-]*$`)
)
// Validates that the given attributes are valid LDAP attribute names according
// to a regular expression.
func validateAttributes(attrs []string) error {
for _, attr := range attrs {
if !validAttributeRegex.MatchString(attr) {
return fmt.Errorf("Attribute name `%s` is invalid", attr)
}
}
return nil
}
// checks if given DNs overlap - returns the first pair of DNs having an overlap
// or empty strings.
func checkForDNOverlaps(s []BaseDNInfo) (string, string) {
n := len(s)
for i := 0; i < n; i++ {
for j := i + 1; j < n; j++ {
if s[i].Parsed.AncestorOf(s[j].Parsed) {
return s[i].Original, s[j].Original
} else if s[j].Parsed.AncestorOf(s[i].Parsed) {
return s[j].Original, s[i].Original
}
}
}
return "", ""
}
const (
dummyUser = "a"
dummyDN = "uid=a,dc=min,dc=io"
)
func compileFilter(s string) error {
s1 := strings.ReplaceAll(s, "%s", dummyUser)
s2 := strings.ReplaceAll(s1, "%d", dummyDN)
_, err := ldap.CompileFilter(s2)
return err
}
golang-github-minio-pkg-3.0.10/ldap/validation_test.go 0000664 0000000 0000000 00000021235 14655770405 0022672 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2022 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package ldap
import (
"fmt"
"os"
"testing"
"github.com/minio/minio-go/v7/pkg/set"
)
const (
EnvTestLDAPServer = "LDAP_TEST_SERVER"
)
func TestConfigValidator(t *testing.T) {
ldapServer := os.Getenv(EnvTestLDAPServer)
if ldapServer == "" {
t.Logf("Skipping test as %s is not set", EnvTestLDAPServer)
t.Skip()
}
testCases := []struct {
cfg Config
expectedResult Result
}{
{
cfg: func() Config {
v := Config{Enabled: true}
return v
}(),
expectedResult: ConnectionParamMisconfigured,
},
{
cfg: func() Config {
v := Config{Enabled: true}
v.ServerAddr = ldapServer
return v
}(),
expectedResult: ConnectivityError,
},
{
cfg: func() Config {
v := Config{Enabled: true}
v.ServerAddr = ldapServer
v.ServerInsecure = true
return v
}(),
expectedResult: LookupBindError,
},
{
cfg: func() Config {
v := Config{Enabled: true}
v.ServerAddr = ldapServer
v.ServerInsecure = true
v.LookupBindDN = "cn=admin,dc=min,dc=io"
v.LookupBindPassword = "admin1"
return v
}(),
expectedResult: LookupBindError,
},
{ // Case 4
cfg: func() Config {
v := Config{Enabled: true}
v.ServerAddr = ldapServer
v.ServerInsecure = true
v.LookupBindDN = "cn=admin,dc=min,dc=io"
v.LookupBindPassword = "admin"
return v
}(),
expectedResult: UserSearchParamsMisconfigured,
},
{
cfg: func() Config {
v := Config{Enabled: true}
v.ServerAddr = ldapServer
v.ServerInsecure = true
v.LookupBindDN = "cn=admin,dc=min,dc=io"
v.LookupBindPassword = "admin"
v.UserDNSearchFilter = "(uid=x)"
v.UserDNSearchBaseDistName = "dc=min,dc=io"
return v
}(),
expectedResult: UserSearchParamsMisconfigured,
},
{
cfg: func() Config {
v := Config{Enabled: true}
v.ServerAddr = ldapServer
v.ServerInsecure = true
v.LookupBindDN = "cn=admin,dc=min,dc=io"
v.LookupBindPassword = "admin"
v.UserDNSearchFilter = "(uid=%s)"
v.UserDNSearchBaseDistName = "dc=min,dc=io"
return v
}(),
expectedResult: ConfigOk,
},
{ // Case 7
cfg: func() Config {
v := Config{Enabled: true}
v.ServerAddr = ldapServer
v.ServerInsecure = true
v.LookupBindDN = "cn=admin,dc=min,dc=io"
v.LookupBindPassword = "admin"
v.UserDNSearchFilter = "(uid=%s)"
v.UserDNSearchBaseDistName = "dc=min,dc=io"
v.GroupSearchBaseDistName = "ou=swengg,dc=min,dc=io"
v.GroupSearchFilter = "(&(objectclass=groupofnames)(member=x))"
return v
}(),
expectedResult: GroupSearchParamsMisconfigured,
},
{
cfg: func() Config {
v := Config{Enabled: true}
v.ServerAddr = ldapServer
v.ServerInsecure = true
v.LookupBindDN = "cn=admin,dc=min,dc=io"
v.LookupBindPassword = "admin"
v.UserDNSearchFilter = "(uid=%s)"
v.UserDNSearchBaseDistName = "dc=min,dc=io"
v.GroupSearchFilter = "(&(objectclass=groupofnames)(member=x))"
return v
}(),
expectedResult: GroupSearchParamsMisconfigured,
},
{ // Case 9
cfg: func() Config {
v := Config{Enabled: true}
v.ServerAddr = ldapServer
v.ServerInsecure = true
v.LookupBindDN = "cn=admin,dc=min,dc=io"
v.LookupBindPassword = "admin"
v.UserDNSearchFilter = "(uid=%s)"
v.UserDNSearchBaseDistName = "dc=min,dc=io"
v.GroupSearchBaseDistName = "ou=swengg,dc=min,dc=io"
v.GroupSearchFilter = "(&(objectclass=groupofnames)(member=%d))"
return v
}(),
expectedResult: ConfigOk,
},
{
cfg: func() Config {
v := Config{Enabled: true}
v.ServerAddr = ldapServer
v.ServerInsecure = true
v.LookupBindDN = "cn=admin,dc=min,dc=io"
v.LookupBindPassword = "admin"
v.UserDNSearchFilter = "(uid=%s)"
v.UserDNSearchBaseDistName = "min,dc=io" // not a valid DN
v.GroupSearchBaseDistName = "ou=swengg,dc=min,dc=io"
v.GroupSearchFilter = "(&(objectclass=groupofnames)(member=%d))"
return v
}(),
expectedResult: UserSearchParamsMisconfigured,
},
{
cfg: func() Config {
v := Config{Enabled: true}
v.ServerAddr = ldapServer
v.ServerInsecure = true
v.LookupBindDN = "cn=admin,dc=min,dc=io"
v.LookupBindPassword = "admin"
v.UserDNSearchFilter = "uid=%s" // not a valid filter
v.UserDNSearchBaseDistName = "dc=min,dc=io"
v.GroupSearchBaseDistName = "ou=swengg,dc=min,dc=io"
v.GroupSearchFilter = "(&(objectclass=groupofnames)(member=%d))"
return v
}(),
expectedResult: UserSearchParamsMisconfigured,
},
{ // Case 12
cfg: func() Config {
v := Config{Enabled: true}
v.ServerAddr = ldapServer
v.ServerInsecure = true
v.LookupBindDN = "cn=admin,dc=min,dc=io"
v.LookupBindPassword = "admin"
v.UserDNSearchFilter = "(uid=%s)"
v.UserDNSearchBaseDistName = "dc=min,dc=io"
v.GroupSearchBaseDistName = "a" // Not a valid DN
v.GroupSearchFilter = "(&(objectclass=groupofnames)(member=%d))"
return v
}(),
expectedResult: GroupSearchParamsMisconfigured,
},
{
cfg: func() Config {
v := Config{Enabled: true}
v.ServerAddr = ldapServer
v.ServerInsecure = true
v.LookupBindDN = "cn=admin,dc=min,dc=io"
v.LookupBindPassword = "admin"
v.UserDNSearchFilter = "(uid=%s)"
v.UserDNSearchBaseDistName = "dc=min,dc=io"
v.GroupSearchBaseDistName = "ou=swengg,dc=min,dc=io"
v.GroupSearchFilter = "member=%d" // Invalid search filter
return v
}(),
expectedResult: GroupSearchParamsMisconfigured,
},
{
cfg: func() Config {
v := Config{Enabled: true}
v.ServerAddr = ldapServer
v.ServerInsecure = true
v.LookupBindDN = "cn=admin,dc=min,dc=io"
v.LookupBindPassword = "admin"
v.UserDNSearchFilter = "(uid=%s)"
v.UserDNSearchBaseDistName = "dc=min,dc=io;adcio" // Invalid DN (multiple)
v.GroupSearchBaseDistName = "ou=swengg,dc=min,dc=io"
v.GroupSearchFilter = "(&(objectclass=groupofnames)(member=%d))"
return v
}(),
expectedResult: UserSearchParamsMisconfigured,
},
{ // Case 15
cfg: func() Config {
v := Config{Enabled: true}
v.ServerAddr = ldapServer
v.ServerInsecure = true
v.LookupBindDN = "cn=admin,dc=min,dc=io"
v.LookupBindPassword = "admin"
v.UserDNSearchFilter = "(uid=%s)"
v.UserDNSearchBaseDistName = "dc=min,dc=io;dc=io" // Overlapping subtrees
v.GroupSearchBaseDistName = "ou=swengg,dc=min,dc=io"
v.GroupSearchFilter = "(&(objectclass=groupofnames)(member=%d))"
return v
}(),
expectedResult: UserSearchParamsMisconfigured,
},
{
cfg: func() Config {
v := Config{Enabled: true}
v.ServerAddr = ldapServer
v.SRVRecordName = "thingy" // Bad SRV record name.
v.ServerInsecure = true
v.LookupBindDN = "cn=admin,dc=min,dc=io"
v.LookupBindPassword = "admin"
v.UserDNSearchFilter = "(uid=%s)"
v.UserDNSearchBaseDistName = "dc=min,dc=io"
v.GroupSearchBaseDistName = "ou=swengg,dc=min,dc=io"
v.GroupSearchFilter = "(&(objectclass=groupofnames)(member=%d))"
return v
}(),
expectedResult: ConnectionParamMisconfigured,
},
}
expectedDN := "uid=dillon,ou=people,ou=swengg,dc=min,dc=io"
expectedGroups := set.CreateStringSet(
"cn=projecta,ou=groups,ou=swengg,dc=min,dc=io",
"cn=projectb,ou=groups,ou=swengg,dc=min,dc=io",
)
for i, test := range testCases {
result := test.cfg.Validate()
if result.Result != test.expectedResult {
fmt.Printf("Result: %#v\n", result)
t.Fatalf("Case %d: Got `%s` expected `%s`", i, result.Result, string(test.expectedResult))
}
if result.IsOk() {
lookupResult, validationResult := test.cfg.ValidateLookup("dillon")
if !validationResult.IsOk() {
t.Fatalf("Case %d: Got unexpected validation failure: %#v\n", i, validationResult)
}
if lookupResult.DN != expectedDN {
t.Fatalf("Case %d: Got unexpected DN: %v", i, lookupResult.DN)
}
if test.cfg.GroupSearchFilter == "" {
continue
}
if !set.CreateStringSet(lookupResult.GroupDNMemberships...).Equals(expectedGroups) {
t.Fatalf("Case %d: Got unexpected groups: %v", i, lookupResult.GroupDNMemberships)
}
}
}
}
golang-github-minio-pkg-3.0.10/licverifier/ 0000775 0000000 0000000 00000000000 14655770405 0020532 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/licverifier/verifier.go 0000664 0000000 0000000 00000013063 14655770405 0022677 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
// Package licverifier implements a simple library to verify MinIO Subnet license keys.
package licverifier
import (
"context"
"crypto/ecdsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"time"
"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/jwk"
"github.com/lestrrat-go/jwx/jwt"
)
// LicenseVerifier needs an ECDSA public key in PEM format for initialization.
type LicenseVerifier struct {
keySet jwk.Set
}
// LicenseInfo holds customer metadata present in the license key.
type LicenseInfo struct {
LicenseToken string // License token
LicenseID string // Unique id of the license
Email string // Email of the license key requestor
Organization string // Subnet organization name
AccountID int64 // Subnet account id
DeploymentID string // Cluster deployment ID
StorageCapacity int64 // Storage capacity used in TB
Plan string // Subnet plan
IssuedAt time.Time // Time of license issue
ExpiresAt time.Time // Time of license expiry
APIKey string // Subnet account API Key
IsTrial bool // Is this a TRIAL license?
}
// license key JSON field names
const (
licenseID = "lid"
accountID = "aid"
deploymentID = "did"
organization = "org"
capacity = "cap"
issuedAt = "iat"
plan = "plan"
apiKey = "apiKey"
trial = "trial"
)
// parse PEM encoded PKCS1 or PKCS8 public key
func parseECPublicKeyFromPEM(key []byte) (*ecdsa.PublicKey, error) {
var err error
// Parse PEM block
var block *pem.Block
if block, _ = pem.Decode(key); block == nil {
return nil, errors.New("key must be a PEM encoded PKCS1 or PKCS8 key")
}
// Parse the key
var parsedKey interface{}
if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil {
if cert, err := x509.ParseCertificate(block.Bytes); err == nil {
parsedKey = cert.PublicKey
} else {
return nil, err
}
}
var pkey *ecdsa.PublicKey
var ok bool
if pkey, ok = parsedKey.(*ecdsa.PublicKey); !ok {
return nil, errors.New("key is not a valid RSA public key")
}
return pkey, nil
}
// NewLicenseVerifier returns an initialized license verifier with the given
// ECDSA public key in PEM format.
func NewLicenseVerifier(pemBytes []byte) (*LicenseVerifier, error) {
pbKey, err := parseECPublicKeyFromPEM(pemBytes)
if err != nil {
return nil, fmt.Errorf("Failed to parse public key: %s", err)
}
key, err := jwk.New(pbKey)
if err != nil {
return nil, err
}
key.Set(jwk.AlgorithmKey, jwa.ES384)
keyset := jwk.NewSet()
keyset.Add(key)
return &LicenseVerifier{
keySet: keyset,
}, nil
}
// toLicenseInfo extracts LicenseInfo from claims. It returns an error if any of
// the claim values are invalid.
func toLicenseInfo(license string, token jwt.Token) (LicenseInfo, error) {
claims, err := token.AsMap(context.Background())
if err != nil {
return LicenseInfo{}, err
}
accID, ok := claims[accountID].(float64)
if !ok || ok && accID < 0 {
return LicenseInfo{}, errors.New("Invalid accountId in claims")
}
// deployment id may not be present in older licenses.
// so don't fail if it's not found.
depUUID, _ := claims[deploymentID].(string)
// license id may not be present in older licenses.
// so don't fail if it's not found.
licID, _ := claims[licenseID].(string)
orgName, ok := claims[organization].(string)
if !ok {
return LicenseInfo{}, errors.New("Invalid organization in claims")
}
storageCap, ok := claims[capacity].(float64)
if !ok {
return LicenseInfo{}, errors.New("Invalid storage capacity in claims")
}
plan, ok := claims[plan].(string)
if !ok {
return LicenseInfo{}, errors.New("Invalid plan in claims")
}
iAt, ok := claims[issuedAt].(time.Time)
if !ok {
return LicenseInfo{}, errors.New("Invalid issuedAt in claims")
}
// apiKey is optional as it's not present in older licenses
apiKey, _ := claims[apiKey].(string)
// isTrial is optional as it's not present in older licenses
// default value = false
isTrial, _ := claims[trial].(bool)
return LicenseInfo{
LicenseToken: license,
LicenseID: licID,
Email: token.Subject(),
Organization: orgName,
AccountID: int64(accID),
DeploymentID: depUUID,
StorageCapacity: int64(storageCap),
Plan: plan,
IssuedAt: iAt,
ExpiresAt: token.Expiration(),
APIKey: apiKey,
IsTrial: isTrial,
}, nil
}
// Verify verifies the license key and validates the claims present in it.
func (lv *LicenseVerifier) Verify(license string, options ...jwt.ParseOption) (LicenseInfo, error) {
options = append(options, jwt.WithKeySet(lv.keySet), jwt.UseDefaultKey(true), jwt.WithValidate(true))
token, err := jwt.ParseString(license, options...)
if err != nil {
return LicenseInfo{}, fmt.Errorf("failed to verify license: %s", err)
}
return toLicenseInfo(license, token)
}
golang-github-minio-pkg-3.0.10/licverifier/verifier_test.go 0000664 0000000 0000000 00000011666 14655770405 0023745 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package licverifier
import (
"fmt"
"testing"
"time"
"github.com/lestrrat-go/jwx/jwt"
)
func areEqLicenseInfo(a, b LicenseInfo) bool {
if a.Email == b.Email && a.Organization == b.Organization && a.AccountID == b.AccountID && a.Plan == b.Plan && a.StorageCapacity == b.StorageCapacity && a.ExpiresAt.Equal(b.ExpiresAt) {
return true
}
return false
}
// TestLicenseVerify tests the license key verification process with a valid and
// an invalid key.
func TestLicenseVerify(t *testing.T) {
pemBytes := []byte(`-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEbo+e1wpBY4tBq9AONKww3Kq7m6QP/TBQ
mr/cKCUyBL7rcAvg0zNq1vcSrUSGlAmY3SEDCu3GOKnjG/U4E7+p957ocWSV+mQU
9NKlTdQFGF3+aO6jbQ4hX/S5qPyF+a3z
-----END PUBLIC KEY-----`)
lv, err := NewLicenseVerifier(pemBytes)
if err != nil {
t.Fatalf("Failed to create license verifier: %s", err)
}
testCases := []struct {
lic string
expectedLicInfo LicenseInfo
shouldPass bool
iat time.Time
}{
{"", LicenseInfo{}, false, time.Now()},
{"eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJrYW5hZ2FyYWorYzFAbWluaW8uaW8iLCJjYXAiOjUwLCJvcmciOiJHcmluZ290dHMgSW5jLiIsImV4cCI6MS42NDE0NDYxNjkwMDExOTg4OTRlOSwicGxhbiI6IlNUQU5EQVJEIiwiaXNzIjoic3VibmV0QG1pbi5pbyIsImFpZCI6MSwiaWF0IjoxLjYwOTkxMDE2OTAwMTE5ODg5NGU5fQ.EhTL2xwMHnUoLQF4UR-5bjUCja3whseLU5mb9XEj7PvAae6HEIDCOMEF8Hhh20DN_v_LRE283j2ZlA5zulcXSZXS0CLcrKqbVy6QLvZfvvLuerOjJI-NBa9dSJWJ0WoN", LicenseInfo{
Email: "kanagaraj+c1@minio.io",
Organization: "Gringotts Inc.",
AccountID: 1,
StorageCapacity: 50,
Plan: "STANDARD",
ExpiresAt: time.Date(2022, time.January, 6, 5, 16, 9, 0, time.UTC),
}, true, time.Unix(int64(1609910169), 0)},
{"eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbkBzdG9yYWdlLWl0LmNvbSIsImNhcCI6MTAsIm9yZyI6IlN0b3JhZ2VJVCIsImV4cCI6MS42MjU4NTY5NjMxOTQ3NzYzMWU5LCJwbGFuIjoiVFJJQUwiLCJpc3MiOiJrYW5hZ2FyYWpAbWluaW8uaW8iLCJhaWQiOjAsImlhdCI6MS42MjM0Mzc3NjMxOTQ3NzYzMWU5LCJsaWQiOiIwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDAifQ.2vBsvURSIrRqrBwpN1dXUcuVxaHIsT3yvAU7SRgUZ5XWHVGF66LR-KlxTWEdU_vThIzvr7vPe-W-quciNxvFaEEmmKDpMfk16jq1wHPcK0gLU5cey6-w4CaEUTxqfiz_", LicenseInfo{
Email: "admin@storage-it.com",
Organization: "StorageIT",
AccountID: 0,
StorageCapacity: 10,
Plan: "TRIAL",
ExpiresAt: time.Date(2021, time.July, 9, 18, 56, 3, 0, time.UTC),
}, true, time.Unix(int64(1623437763), 0)},
}
for i, tc := range testCases {
// Fixing the jwt.TimeFunc at 2021-01-06 05:16:09 +0000 UTC to
// ensure that the license JWT doesn't expire ever.
licInfo, err := lv.Verify(tc.lic, jwt.ParseOption(jwt.WithClock(jwt.ClockFunc(func() time.Time {
return tc.iat
}))))
if err != nil && tc.shouldPass {
t.Fatalf("%d: Expected license to pass verification but failed with %s", i+1, err)
}
if err == nil {
if !tc.shouldPass {
t.Fatalf("%d: Expected license to fail verification but passed", i+1)
}
if !areEqLicenseInfo(tc.expectedLicInfo, licInfo) {
t.Fatalf("%d: Expected license info %v but got %v", i+1, tc.expectedLicInfo, licInfo)
}
}
}
}
// Example creates a LicenseVerifier using the ECDSA public key in pemBytes. It
// uses the Verify method of the LicenseVerifier to verify and extract the
// claims present in the license key.
func Example() {
pemBytes := []byte(`-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEbo+e1wpBY4tBq9AONKww3Kq7m6QP/TBQ
mr/cKCUyBL7rcAvg0zNq1vcSrUSGlAmY3SEDCu3GOKnjG/U4E7+p957ocWSV+mQU
9NKlTdQFGF3+aO6jbQ4hX/S5qPyF+a3z
-----END PUBLIC KEY-----`)
lv, err := NewLicenseVerifier(pemBytes)
if err != nil {
fmt.Println("Failed to create license verifier", err)
}
licenseKey := "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJrYW5hZ2FyYWorYzFAbWluaW8uaW8iLCJjYXAiOjUwLCJvcmciOiJHcmluZ290dHMgSW5jLiIsImV4cCI6MS42NDE0NDYxNjkwMDExOTg4OTRlOSwicGxhbiI6IlNUQU5EQVJEIiwiaXNzIjoic3VibmV0QG1pbi5pbyIsImFpZCI6MSwiaWF0IjoxLjYwOTkxMDE2OTAwMTE5ODg5NGU5fQ.EhTL2xwMHnUoLQF4UR-5bjUCja3whseLU5mb9XEj7PvAae6HEIDCOMEF8Hhh20DN_v_LRE283j2ZlA5zulcXSZXS0CLcrKqbVy6QLvZfvvLuerOjJI-NBa9dSJWJ0WoN"
licInfo, err := lv.Verify(licenseKey)
if err != nil {
fmt.Println("Failed to verify license key", err)
}
fmt.Println("License metadata", licInfo)
}
golang-github-minio-pkg-3.0.10/logger/ 0000775 0000000 0000000 00000000000 14655770405 0017506 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/logger/message/ 0000775 0000000 0000000 00000000000 14655770405 0021132 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/logger/message/audit/ 0000775 0000000 0000000 00000000000 14655770405 0022240 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/logger/message/audit/entry.go 0000664 0000000 0000000 00000006042 14655770405 0023732 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package audit
import "time"
// ObjectVersion object version key/versionId
type ObjectVersion struct {
ObjectName string `json:"objectName"`
VersionID string `json:"versionId,omitempty"`
}
// Entry - audit entry logs.
type Entry struct {
Version string `json:"version"`
DeploymentID string `json:"deploymentid,omitempty"`
Time time.Time `json:"time"`
Event string `json:"event"`
// Class of audit message - S3, admin ops, bucket management
Type string `json:"type,omitempty"`
// deprecated replaced by 'Event', kept here for some
// time for backward compatibility with k8s Operator.
Trigger string `json:"trigger"`
API struct {
Name string `json:"name,omitempty"`
Bucket string `json:"bucket,omitempty"`
Object string `json:"object,omitempty"`
Objects []ObjectVersion `json:"objects,omitempty"`
Status string `json:"status,omitempty"`
StatusCode int `json:"statusCode,omitempty"`
InputBytes int64 `json:"rx"`
OutputBytes int64 `json:"tx"`
HeaderBytes int64 `json:"txHeaders,omitempty"`
TimeToFirstByte string `json:"timeToFirstByte,omitempty"`
TimeToFirstByteInNS string `json:"timeToFirstByteInNS,omitempty"`
TimeToResponse string `json:"timeToResponse,omitempty"`
TimeToResponseInNS string `json:"timeToResponseInNS,omitempty"`
} `json:"api"`
RemoteHost string `json:"remotehost,omitempty"`
RequestID string `json:"requestID,omitempty"`
UserAgent string `json:"userAgent,omitempty"`
ReqPath string `json:"requestPath,omitempty"`
ReqHost string `json:"requestHost,omitempty"`
ReqClaims map[string]interface{} `json:"requestClaims,omitempty"`
ReqQuery map[string]string `json:"requestQuery,omitempty"`
ReqHeader map[string]string `json:"requestHeader,omitempty"`
RespHeader map[string]string `json:"responseHeader,omitempty"`
Tags map[string]interface{} `json:"tags,omitempty"`
AccessKey string `json:"accessKey,omitempty"`
ParentUser string `json:"parentUser,omitempty"`
Error string `json:"error,omitempty"`
}
golang-github-minio-pkg-3.0.10/logger/message/log/ 0000775 0000000 0000000 00000000000 14655770405 0021713 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/logger/message/log/entry.go 0000664 0000000 0000000 00000005621 14655770405 0023407 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2024 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package log
import (
"strings"
"time"
"github.com/minio/madmin-go/v3"
)
// ObjectVersion object version key/versionId
type ObjectVersion struct {
ObjectName string `json:"objectName"`
VersionID string `json:"versionId,omitempty"`
}
// Args - defines the arguments for the API.
type Args struct {
Bucket string `json:"bucket,omitempty"`
Object string `json:"object,omitempty"`
VersionID string `json:"versionId,omitempty"`
Objects []ObjectVersion `json:"objects,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
}
// Trace - defines the trace.
type Trace struct {
Message string `json:"message,omitempty"`
Source []string `json:"source,omitempty"`
Variables map[string]interface{} `json:"variables,omitempty"`
}
// API - defines the api type and its args.
type API struct {
Name string `json:"name,omitempty"`
Args *Args `json:"args,omitempty"`
}
// Entry - defines fields and values of each log entry.
type Entry struct {
DeploymentID string `json:"deploymentid,omitempty"`
Level madmin.LogKind `json:"level"`
LogKind madmin.LogKind `json:"errKind,omitempty"` // Deprecated Jan 2024
Time time.Time `json:"time"`
API *API `json:"api,omitempty"`
RemoteHost string `json:"remotehost,omitempty"`
Host string `json:"host,omitempty"`
RequestID string `json:"requestID,omitempty"`
UserAgent string `json:"userAgent,omitempty"`
Message string `json:"message,omitempty"`
Trace *Trace `json:"error,omitempty"`
}
// Info holds console log messages
type Info struct {
Entry
ConsoleMsg string
NodeName string `json:"node"`
Err error `json:"-"`
}
// Mask returns the mask based on the error level.
func (l Info) Mask() uint64 {
return l.Level.LogMask().Mask()
}
// SendLog returns true if log pertains to node specified in args.
func (l Info) SendLog(node string, logKind madmin.LogMask) bool {
if logKind.Contains(l.Level.LogMask()) {
return node == "" || strings.EqualFold(node, l.NodeName) && !l.Time.IsZero()
}
return false
}
golang-github-minio-pkg-3.0.10/mimedb/ 0000775 0000000 0000000 00000000000 14655770405 0017464 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/mimedb/Makefile 0000664 0000000 0000000 00000001040 14655770405 0021117 0 ustar 00root root 0000000 0000000 # Generate db.go from db.json downloaded nodejs mime-db project.
# NOTE: Autogenerated db.go needs to be vet proofed. \
Manually edit json -> JSON for all variable names
all: download build
# Download db.json from NodeJS's mime-db project. It is under MIT license.
download:
@mkdir db
@wget -nv -q https://cdn.rawgit.com/jshttp/mime-db/master/db.json -O db/db.json
# After generating db.go, clean up downloaded db.json.
build: download
@go run util/gen-db.go db/db.json > db.go
@rm -f db/db.json
@rm -rf db
@echo Generated \"db.go\".
golang-github-minio-pkg-3.0.10/mimedb/db.go 0000664 0000000 0000000 00000276507 14655770405 0020421 0 ustar 00root root 0000000 0000000 // DO NOT EDIT THIS FILE. IT IS AUTO-GENERATED BY "gen-db.go".
// Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
// Package mimedb is a database of file extension to mime content-type.
// Definitions are imported from NodeJS mime-db project under MIT license.
package mimedb
// DB - Mime is a collection of mime types with extension as key and content-type as value.
var DB = map[string]struct {
ContentType string
Compressible bool
}{
"123": {
ContentType: "application/vnd.lotus-1-2-3",
Compressible: false,
},
"1km": {
ContentType: "application/vnd.1000minds.decision-model+xml",
Compressible: false,
},
"3dml": {
ContentType: "text/vnd.in3d.3dml",
Compressible: false,
},
"3ds": {
ContentType: "image/x-3ds",
Compressible: false,
},
"3g2": {
ContentType: "video/3gpp2",
Compressible: false,
},
"3gp": {
ContentType: "video/3gpp",
Compressible: false,
},
"3gpp": {
ContentType: "video/3gpp",
Compressible: false,
},
"3mf": {
ContentType: "model/3mf",
Compressible: false,
},
"7z": {
ContentType: "application/x-7z-compressed",
Compressible: false,
},
"aab": {
ContentType: "application/x-authorware-bin",
Compressible: false,
},
"aac": {
ContentType: "audio/x-aac",
Compressible: false,
},
"aam": {
ContentType: "application/x-authorware-map",
Compressible: false,
},
"aas": {
ContentType: "application/x-authorware-seg",
Compressible: false,
},
"abw": {
ContentType: "application/x-abiword",
Compressible: false,
},
"ac": {
ContentType: "application/vnd.nokia.n-gage.ac+xml",
Compressible: false,
},
"acc": {
ContentType: "application/vnd.americandynamics.acc",
Compressible: false,
},
"ace": {
ContentType: "application/x-ace-compressed",
Compressible: false,
},
"acu": {
ContentType: "application/vnd.acucobol",
Compressible: false,
},
"acutc": {
ContentType: "application/vnd.acucorp",
Compressible: false,
},
"adp": {
ContentType: "audio/adpcm",
Compressible: false,
},
"adts": {
ContentType: "audio/aac",
Compressible: false,
},
"aep": {
ContentType: "application/vnd.audiograph",
Compressible: false,
},
"afm": {
ContentType: "application/x-font-type1",
Compressible: false,
},
"afp": {
ContentType: "application/vnd.ibm.modcap",
Compressible: false,
},
"age": {
ContentType: "application/vnd.age",
Compressible: false,
},
"ahead": {
ContentType: "application/vnd.ahead.space",
Compressible: false,
},
"ai": {
ContentType: "application/postscript",
Compressible: false,
},
"aif": {
ContentType: "audio/x-aiff",
Compressible: false,
},
"aifc": {
ContentType: "audio/x-aiff",
Compressible: false,
},
"aiff": {
ContentType: "audio/x-aiff",
Compressible: false,
},
"air": {
ContentType: "application/vnd.adobe.air-application-installer-package+zip",
Compressible: false,
},
"ait": {
ContentType: "application/vnd.dvb.ait",
Compressible: false,
},
"ami": {
ContentType: "application/vnd.amiga.ami",
Compressible: false,
},
"aml": {
ContentType: "application/automationml-aml+xml",
Compressible: false,
},
"amlx": {
ContentType: "application/automationml-amlx+zip",
Compressible: false,
},
"amr": {
ContentType: "audio/amr",
Compressible: false,
},
"apk": {
ContentType: "application/vnd.android.package-archive",
Compressible: false,
},
"apng": {
ContentType: "image/apng",
Compressible: false,
},
"appcache": {
ContentType: "text/cache-manifest",
Compressible: false,
},
"appinstaller": {
ContentType: "application/appinstaller",
Compressible: false,
},
"application": {
ContentType: "application/x-ms-application",
Compressible: false,
},
"appx": {
ContentType: "application/appx",
Compressible: false,
},
"appxbundle": {
ContentType: "application/appxbundle",
Compressible: false,
},
"apr": {
ContentType: "application/vnd.lotus-approach",
Compressible: false,
},
"arc": {
ContentType: "application/x-freearc",
Compressible: false,
},
"arj": {
ContentType: "application/x-arj",
Compressible: false,
},
"asc": {
ContentType: "application/pgp-signature",
Compressible: false,
},
"asf": {
ContentType: "video/x-ms-asf",
Compressible: false,
},
"asm": {
ContentType: "text/x-asm",
Compressible: false,
},
"aso": {
ContentType: "application/vnd.accpac.simply.aso",
Compressible: false,
},
"asx": {
ContentType: "video/x-ms-asf",
Compressible: false,
},
"atc": {
ContentType: "application/vnd.acucorp",
Compressible: false,
},
"atom": {
ContentType: "application/atom+xml",
Compressible: false,
},
"atomcat": {
ContentType: "application/atomcat+xml",
Compressible: false,
},
"atomdeleted": {
ContentType: "application/atomdeleted+xml",
Compressible: false,
},
"atomsvc": {
ContentType: "application/atomsvc+xml",
Compressible: false,
},
"atx": {
ContentType: "application/vnd.antix.game-component",
Compressible: false,
},
"au": {
ContentType: "audio/basic",
Compressible: false,
},
"avci": {
ContentType: "image/avci",
Compressible: false,
},
"avcs": {
ContentType: "image/avcs",
Compressible: false,
},
"avi": {
ContentType: "video/x-msvideo",
Compressible: false,
},
"avif": {
ContentType: "image/avif",
Compressible: false,
},
"aw": {
ContentType: "application/applixware",
Compressible: false,
},
"azf": {
ContentType: "application/vnd.airzip.filesecure.azf",
Compressible: false,
},
"azs": {
ContentType: "application/vnd.airzip.filesecure.azs",
Compressible: false,
},
"azv": {
ContentType: "image/vnd.airzip.accelerator.azv",
Compressible: false,
},
"azw": {
ContentType: "application/vnd.amazon.ebook",
Compressible: false,
},
"b16": {
ContentType: "image/vnd.pco.b16",
Compressible: false,
},
"bat": {
ContentType: "application/x-msdownload",
Compressible: false,
},
"bcpio": {
ContentType: "application/x-bcpio",
Compressible: false,
},
"bdf": {
ContentType: "application/x-font-bdf",
Compressible: false,
},
"bdm": {
ContentType: "application/vnd.syncml.dm+wbxml",
Compressible: false,
},
"bdoc": {
ContentType: "application/x-bdoc",
Compressible: false,
},
"bed": {
ContentType: "application/vnd.realvnc.bed",
Compressible: false,
},
"bh2": {
ContentType: "application/vnd.fujitsu.oasysprs",
Compressible: false,
},
"bin": {
ContentType: "application/octet-stream",
Compressible: false,
},
"blb": {
ContentType: "application/x-blorb",
Compressible: false,
},
"blorb": {
ContentType: "application/x-blorb",
Compressible: false,
},
"bmi": {
ContentType: "application/vnd.bmi",
Compressible: false,
},
"bmml": {
ContentType: "application/vnd.balsamiq.bmml+xml",
Compressible: false,
},
"bmp": {
ContentType: "image/x-ms-bmp",
Compressible: false,
},
"book": {
ContentType: "application/vnd.framemaker",
Compressible: false,
},
"box": {
ContentType: "application/vnd.previewsystems.box",
Compressible: false,
},
"boz": {
ContentType: "application/x-bzip2",
Compressible: false,
},
"bpk": {
ContentType: "application/octet-stream",
Compressible: false,
},
"bsp": {
ContentType: "model/vnd.valve.source.compiled-map",
Compressible: false,
},
"btf": {
ContentType: "image/prs.btif",
Compressible: false,
},
"btif": {
ContentType: "image/prs.btif",
Compressible: false,
},
"buffer": {
ContentType: "application/octet-stream",
Compressible: false,
},
"bz": {
ContentType: "application/x-bzip",
Compressible: false,
},
"bz2": {
ContentType: "application/x-bzip2",
Compressible: false,
},
"c": {
ContentType: "text/x-c",
Compressible: false,
},
"c11amc": {
ContentType: "application/vnd.cluetrust.cartomobile-config",
Compressible: false,
},
"c11amz": {
ContentType: "application/vnd.cluetrust.cartomobile-config-pkg",
Compressible: false,
},
"c4d": {
ContentType: "application/vnd.clonk.c4group",
Compressible: false,
},
"c4f": {
ContentType: "application/vnd.clonk.c4group",
Compressible: false,
},
"c4g": {
ContentType: "application/vnd.clonk.c4group",
Compressible: false,
},
"c4p": {
ContentType: "application/vnd.clonk.c4group",
Compressible: false,
},
"c4u": {
ContentType: "application/vnd.clonk.c4group",
Compressible: false,
},
"cab": {
ContentType: "application/vnd.ms-cab-compressed",
Compressible: false,
},
"caf": {
ContentType: "audio/x-caf",
Compressible: false,
},
"cap": {
ContentType: "application/vnd.tcpdump.pcap",
Compressible: false,
},
"car": {
ContentType: "application/vnd.curl.car",
Compressible: false,
},
"cat": {
ContentType: "application/vnd.ms-pki.seccat",
Compressible: false,
},
"cb7": {
ContentType: "application/x-cbr",
Compressible: false,
},
"cba": {
ContentType: "application/x-cbr",
Compressible: false,
},
"cbr": {
ContentType: "application/x-cbr",
Compressible: false,
},
"cbt": {
ContentType: "application/x-cbr",
Compressible: false,
},
"cbz": {
ContentType: "application/x-cbr",
Compressible: false,
},
"cc": {
ContentType: "text/x-c",
Compressible: false,
},
"cco": {
ContentType: "application/x-cocoa",
Compressible: false,
},
"cct": {
ContentType: "application/x-director",
Compressible: false,
},
"ccxml": {
ContentType: "application/ccxml+xml",
Compressible: false,
},
"cdbcmsg": {
ContentType: "application/vnd.contact.cmsg",
Compressible: false,
},
"cdf": {
ContentType: "application/x-netcdf",
Compressible: false,
},
"cdfx": {
ContentType: "application/cdfx+xml",
Compressible: false,
},
"cdkey": {
ContentType: "application/vnd.mediastation.cdkey",
Compressible: false,
},
"cdmia": {
ContentType: "application/cdmi-capability",
Compressible: false,
},
"cdmic": {
ContentType: "application/cdmi-container",
Compressible: false,
},
"cdmid": {
ContentType: "application/cdmi-domain",
Compressible: false,
},
"cdmio": {
ContentType: "application/cdmi-object",
Compressible: false,
},
"cdmiq": {
ContentType: "application/cdmi-queue",
Compressible: false,
},
"cdx": {
ContentType: "chemical/x-cdx",
Compressible: false,
},
"cdxml": {
ContentType: "application/vnd.chemdraw+xml",
Compressible: false,
},
"cdy": {
ContentType: "application/vnd.cinderella",
Compressible: false,
},
"cer": {
ContentType: "application/pkix-cert",
Compressible: false,
},
"cfs": {
ContentType: "application/x-cfs-compressed",
Compressible: false,
},
"cgm": {
ContentType: "image/cgm",
Compressible: false,
},
"chat": {
ContentType: "application/x-chat",
Compressible: false,
},
"chm": {
ContentType: "application/vnd.ms-htmlhelp",
Compressible: false,
},
"chrt": {
ContentType: "application/vnd.kde.kchart",
Compressible: false,
},
"cif": {
ContentType: "chemical/x-cif",
Compressible: false,
},
"cii": {
ContentType: "application/vnd.anser-web-certificate-issue-initiation",
Compressible: false,
},
"cil": {
ContentType: "application/vnd.ms-artgalry",
Compressible: false,
},
"cjs": {
ContentType: "application/node",
Compressible: false,
},
"cla": {
ContentType: "application/vnd.claymore",
Compressible: false,
},
"class": {
ContentType: "application/java-vm",
Compressible: false,
},
"cld": {
ContentType: "model/vnd.cld",
Compressible: false,
},
"clkk": {
ContentType: "application/vnd.crick.clicker.keyboard",
Compressible: false,
},
"clkp": {
ContentType: "application/vnd.crick.clicker.palette",
Compressible: false,
},
"clkt": {
ContentType: "application/vnd.crick.clicker.template",
Compressible: false,
},
"clkw": {
ContentType: "application/vnd.crick.clicker.wordbank",
Compressible: false,
},
"clkx": {
ContentType: "application/vnd.crick.clicker",
Compressible: false,
},
"clp": {
ContentType: "application/x-msclip",
Compressible: false,
},
"cmc": {
ContentType: "application/vnd.cosmocaller",
Compressible: false,
},
"cmdf": {
ContentType: "chemical/x-cmdf",
Compressible: false,
},
"cml": {
ContentType: "chemical/x-cml",
Compressible: false,
},
"cmp": {
ContentType: "application/vnd.yellowriver-custom-menu",
Compressible: false,
},
"cmx": {
ContentType: "image/x-cmx",
Compressible: false,
},
"cod": {
ContentType: "application/vnd.rim.cod",
Compressible: false,
},
"coffee": {
ContentType: "text/coffeescript",
Compressible: false,
},
"com": {
ContentType: "application/x-msdownload",
Compressible: false,
},
"conf": {
ContentType: "text/plain",
Compressible: false,
},
"cpio": {
ContentType: "application/x-cpio",
Compressible: false,
},
"cpl": {
ContentType: "application/cpl+xml",
Compressible: false,
},
"cpp": {
ContentType: "text/x-c",
Compressible: false,
},
"cpt": {
ContentType: "application/mac-compactpro",
Compressible: false,
},
"crd": {
ContentType: "application/x-mscardfile",
Compressible: false,
},
"crl": {
ContentType: "application/pkix-crl",
Compressible: false,
},
"crt": {
ContentType: "application/x-x509-ca-cert",
Compressible: false,
},
"crx": {
ContentType: "application/x-chrome-extension",
Compressible: false,
},
"cryptonote": {
ContentType: "application/vnd.rig.cryptonote",
Compressible: false,
},
"csh": {
ContentType: "application/x-csh",
Compressible: false,
},
"csl": {
ContentType: "application/vnd.citationstyles.style+xml",
Compressible: false,
},
"csml": {
ContentType: "chemical/x-csml",
Compressible: false,
},
"csp": {
ContentType: "application/vnd.commonspace",
Compressible: false,
},
"css": {
ContentType: "text/css",
Compressible: false,
},
"cst": {
ContentType: "application/x-director",
Compressible: false,
},
"csv": {
ContentType: "text/csv",
Compressible: false,
},
"cu": {
ContentType: "application/cu-seeme",
Compressible: false,
},
"curl": {
ContentType: "text/vnd.curl",
Compressible: false,
},
"cwl": {
ContentType: "application/cwl",
Compressible: false,
},
"cww": {
ContentType: "application/prs.cww",
Compressible: false,
},
"cxt": {
ContentType: "application/x-director",
Compressible: false,
},
"cxx": {
ContentType: "text/x-c",
Compressible: false,
},
"dae": {
ContentType: "model/vnd.collada+xml",
Compressible: false,
},
"daf": {
ContentType: "application/vnd.mobius.daf",
Compressible: false,
},
"dart": {
ContentType: "application/vnd.dart",
Compressible: false,
},
"dataless": {
ContentType: "application/vnd.fdsn.seed",
Compressible: false,
},
"davmount": {
ContentType: "application/davmount+xml",
Compressible: false,
},
"dbf": {
ContentType: "application/vnd.dbf",
Compressible: false,
},
"dbk": {
ContentType: "application/docbook+xml",
Compressible: false,
},
"dcr": {
ContentType: "application/x-director",
Compressible: false,
},
"dcurl": {
ContentType: "text/vnd.curl.dcurl",
Compressible: false,
},
"dd2": {
ContentType: "application/vnd.oma.dd2+xml",
Compressible: false,
},
"ddd": {
ContentType: "application/vnd.fujixerox.ddd",
Compressible: false,
},
"ddf": {
ContentType: "application/vnd.syncml.dmddf+xml",
Compressible: false,
},
"dds": {
ContentType: "image/vnd.ms-dds",
Compressible: false,
},
"deb": {
ContentType: "application/x-debian-package",
Compressible: false,
},
"def": {
ContentType: "text/plain",
Compressible: false,
},
"deploy": {
ContentType: "application/octet-stream",
Compressible: false,
},
"der": {
ContentType: "application/x-x509-ca-cert",
Compressible: false,
},
"dfac": {
ContentType: "application/vnd.dreamfactory",
Compressible: false,
},
"dgc": {
ContentType: "application/x-dgc-compressed",
Compressible: false,
},
"dib": {
ContentType: "image/bmp",
Compressible: false,
},
"dic": {
ContentType: "text/x-c",
Compressible: false,
},
"dir": {
ContentType: "application/x-director",
Compressible: false,
},
"dis": {
ContentType: "application/vnd.mobius.dis",
Compressible: false,
},
"disposition-notification": {
ContentType: "message/disposition-notification",
Compressible: false,
},
"dist": {
ContentType: "application/octet-stream",
Compressible: false,
},
"distz": {
ContentType: "application/octet-stream",
Compressible: false,
},
"djv": {
ContentType: "image/vnd.djvu",
Compressible: false,
},
"djvu": {
ContentType: "image/vnd.djvu",
Compressible: false,
},
"dll": {
ContentType: "application/x-msdownload",
Compressible: false,
},
"dmg": {
ContentType: "application/x-apple-diskimage",
Compressible: false,
},
"dmp": {
ContentType: "application/vnd.tcpdump.pcap",
Compressible: false,
},
"dms": {
ContentType: "application/octet-stream",
Compressible: false,
},
"dna": {
ContentType: "application/vnd.dna",
Compressible: false,
},
"doc": {
ContentType: "application/msword",
Compressible: false,
},
"docm": {
ContentType: "application/vnd.ms-word.document.macroenabled.12",
Compressible: false,
},
"docx": {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
Compressible: false,
},
"dot": {
ContentType: "application/msword",
Compressible: false,
},
"dotm": {
ContentType: "application/vnd.ms-word.template.macroenabled.12",
Compressible: false,
},
"dotx": {
ContentType: "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
Compressible: false,
},
"dp": {
ContentType: "application/vnd.osgi.dp",
Compressible: false,
},
"dpg": {
ContentType: "application/vnd.dpgraph",
Compressible: false,
},
"dpx": {
ContentType: "image/dpx",
Compressible: false,
},
"dra": {
ContentType: "audio/vnd.dra",
Compressible: false,
},
"drle": {
ContentType: "image/dicom-rle",
Compressible: false,
},
"dsc": {
ContentType: "text/prs.lines.tag",
Compressible: false,
},
"dssc": {
ContentType: "application/dssc+der",
Compressible: false,
},
"dtb": {
ContentType: "application/x-dtbook+xml",
Compressible: false,
},
"dtd": {
ContentType: "application/xml-dtd",
Compressible: false,
},
"dts": {
ContentType: "audio/vnd.dts",
Compressible: false,
},
"dtshd": {
ContentType: "audio/vnd.dts.hd",
Compressible: false,
},
"dump": {
ContentType: "application/octet-stream",
Compressible: false,
},
"dvb": {
ContentType: "video/vnd.dvb.file",
Compressible: false,
},
"dvi": {
ContentType: "application/x-dvi",
Compressible: false,
},
"dwd": {
ContentType: "application/atsc-dwd+xml",
Compressible: false,
},
"dwf": {
ContentType: "model/vnd.dwf",
Compressible: false,
},
"dwg": {
ContentType: "image/vnd.dwg",
Compressible: false,
},
"dxf": {
ContentType: "image/vnd.dxf",
Compressible: false,
},
"dxp": {
ContentType: "application/vnd.spotfire.dxp",
Compressible: false,
},
"dxr": {
ContentType: "application/x-director",
Compressible: false,
},
"ear": {
ContentType: "application/java-archive",
Compressible: false,
},
"ecelp4800": {
ContentType: "audio/vnd.nuera.ecelp4800",
Compressible: false,
},
"ecelp7470": {
ContentType: "audio/vnd.nuera.ecelp7470",
Compressible: false,
},
"ecelp9600": {
ContentType: "audio/vnd.nuera.ecelp9600",
Compressible: false,
},
"ecma": {
ContentType: "application/ecmascript",
Compressible: false,
},
"edm": {
ContentType: "application/vnd.novadigm.edm",
Compressible: false,
},
"edx": {
ContentType: "application/vnd.novadigm.edx",
Compressible: false,
},
"efif": {
ContentType: "application/vnd.picsel",
Compressible: false,
},
"ei6": {
ContentType: "application/vnd.pg.osasli",
Compressible: false,
},
"elc": {
ContentType: "application/octet-stream",
Compressible: false,
},
"emf": {
ContentType: "image/emf",
Compressible: false,
},
"eml": {
ContentType: "message/rfc822",
Compressible: false,
},
"emma": {
ContentType: "application/emma+xml",
Compressible: false,
},
"emotionml": {
ContentType: "application/emotionml+xml",
Compressible: false,
},
"emz": {
ContentType: "application/x-msmetafile",
Compressible: false,
},
"eol": {
ContentType: "audio/vnd.digital-winds",
Compressible: false,
},
"eot": {
ContentType: "application/vnd.ms-fontobject",
Compressible: false,
},
"eps": {
ContentType: "application/postscript",
Compressible: false,
},
"epub": {
ContentType: "application/epub+zip",
Compressible: false,
},
"es3": {
ContentType: "application/vnd.eszigno3+xml",
Compressible: false,
},
"esa": {
ContentType: "application/vnd.osgi.subsystem",
Compressible: false,
},
"esf": {
ContentType: "application/vnd.epson.esf",
Compressible: false,
},
"et3": {
ContentType: "application/vnd.eszigno3+xml",
Compressible: false,
},
"etx": {
ContentType: "text/x-setext",
Compressible: false,
},
"eva": {
ContentType: "application/x-eva",
Compressible: false,
},
"evy": {
ContentType: "application/x-envoy",
Compressible: false,
},
"exe": {
ContentType: "application/x-msdownload",
Compressible: false,
},
"exi": {
ContentType: "application/exi",
Compressible: false,
},
"exp": {
ContentType: "application/express",
Compressible: false,
},
"exr": {
ContentType: "image/aces",
Compressible: false,
},
"ext": {
ContentType: "application/vnd.novadigm.ext",
Compressible: false,
},
"ez": {
ContentType: "application/andrew-inset",
Compressible: false,
},
"ez2": {
ContentType: "application/vnd.ezpix-album",
Compressible: false,
},
"ez3": {
ContentType: "application/vnd.ezpix-package",
Compressible: false,
},
"f": {
ContentType: "text/x-fortran",
Compressible: false,
},
"f4v": {
ContentType: "video/x-f4v",
Compressible: false,
},
"f77": {
ContentType: "text/x-fortran",
Compressible: false,
},
"f90": {
ContentType: "text/x-fortran",
Compressible: false,
},
"fbs": {
ContentType: "image/vnd.fastbidsheet",
Compressible: false,
},
"fcdt": {
ContentType: "application/vnd.adobe.formscentral.fcdt",
Compressible: false,
},
"fcs": {
ContentType: "application/vnd.isac.fcs",
Compressible: false,
},
"fdf": {
ContentType: "application/vnd.fdf",
Compressible: false,
},
"fdt": {
ContentType: "application/fdt+xml",
Compressible: false,
},
"fe_launch": {
ContentType: "application/vnd.denovo.fcselayout-link",
Compressible: false,
},
"fg5": {
ContentType: "application/vnd.fujitsu.oasysgp",
Compressible: false,
},
"fgd": {
ContentType: "application/x-director",
Compressible: false,
},
"fh": {
ContentType: "image/x-freehand",
Compressible: false,
},
"fh4": {
ContentType: "image/x-freehand",
Compressible: false,
},
"fh5": {
ContentType: "image/x-freehand",
Compressible: false,
},
"fh7": {
ContentType: "image/x-freehand",
Compressible: false,
},
"fhc": {
ContentType: "image/x-freehand",
Compressible: false,
},
"fig": {
ContentType: "application/x-xfig",
Compressible: false,
},
"fits": {
ContentType: "image/fits",
Compressible: false,
},
"flac": {
ContentType: "audio/x-flac",
Compressible: false,
},
"fli": {
ContentType: "video/x-fli",
Compressible: false,
},
"flo": {
ContentType: "application/vnd.micrografx.flo",
Compressible: false,
},
"flv": {
ContentType: "video/x-flv",
Compressible: false,
},
"flw": {
ContentType: "application/vnd.kde.kivio",
Compressible: false,
},
"flx": {
ContentType: "text/vnd.fmi.flexstor",
Compressible: false,
},
"fly": {
ContentType: "text/vnd.fly",
Compressible: false,
},
"fm": {
ContentType: "application/vnd.framemaker",
Compressible: false,
},
"fnc": {
ContentType: "application/vnd.frogans.fnc",
Compressible: false,
},
"fo": {
ContentType: "application/vnd.software602.filler.form+xml",
Compressible: false,
},
"for": {
ContentType: "text/x-fortran",
Compressible: false,
},
"fpx": {
ContentType: "image/vnd.fpx",
Compressible: false,
},
"frame": {
ContentType: "application/vnd.framemaker",
Compressible: false,
},
"fsc": {
ContentType: "application/vnd.fsc.weblaunch",
Compressible: false,
},
"fst": {
ContentType: "image/vnd.fst",
Compressible: false,
},
"ftc": {
ContentType: "application/vnd.fluxtime.clip",
Compressible: false,
},
"fti": {
ContentType: "application/vnd.anser-web-funds-transfer-initiation",
Compressible: false,
},
"fvt": {
ContentType: "video/vnd.fvt",
Compressible: false,
},
"fxp": {
ContentType: "application/vnd.adobe.fxp",
Compressible: false,
},
"fxpl": {
ContentType: "application/vnd.adobe.fxp",
Compressible: false,
},
"fzs": {
ContentType: "application/vnd.fuzzysheet",
Compressible: false,
},
"g2w": {
ContentType: "application/vnd.geoplan",
Compressible: false,
},
"g3": {
ContentType: "image/g3fax",
Compressible: false,
},
"g3w": {
ContentType: "application/vnd.geospace",
Compressible: false,
},
"gac": {
ContentType: "application/vnd.groove-account",
Compressible: false,
},
"gam": {
ContentType: "application/x-tads",
Compressible: false,
},
"gbr": {
ContentType: "application/rpki-ghostbusters",
Compressible: false,
},
"gca": {
ContentType: "application/x-gca-compressed",
Compressible: false,
},
"gdl": {
ContentType: "model/vnd.gdl",
Compressible: false,
},
"gdoc": {
ContentType: "application/vnd.google-apps.document",
Compressible: false,
},
"ged": {
ContentType: "text/vnd.familysearch.gedcom",
Compressible: false,
},
"geo": {
ContentType: "application/vnd.dynageo",
Compressible: false,
},
"geojson": {
ContentType: "application/geo+json",
Compressible: false,
},
"gex": {
ContentType: "application/vnd.geometry-explorer",
Compressible: false,
},
"ggb": {
ContentType: "application/vnd.geogebra.file",
Compressible: false,
},
"ggt": {
ContentType: "application/vnd.geogebra.tool",
Compressible: false,
},
"ghf": {
ContentType: "application/vnd.groove-help",
Compressible: false,
},
"gif": {
ContentType: "image/gif",
Compressible: false,
},
"gim": {
ContentType: "application/vnd.groove-identity-message",
Compressible: false,
},
"glb": {
ContentType: "model/gltf-binary",
Compressible: false,
},
"gltf": {
ContentType: "model/gltf+json",
Compressible: false,
},
"gml": {
ContentType: "application/gml+xml",
Compressible: false,
},
"gmx": {
ContentType: "application/vnd.gmx",
Compressible: false,
},
"gnumeric": {
ContentType: "application/x-gnumeric",
Compressible: false,
},
"gph": {
ContentType: "application/vnd.flographit",
Compressible: false,
},
"gpx": {
ContentType: "application/gpx+xml",
Compressible: false,
},
"gqf": {
ContentType: "application/vnd.grafeq",
Compressible: false,
},
"gqs": {
ContentType: "application/vnd.grafeq",
Compressible: false,
},
"gram": {
ContentType: "application/srgs",
Compressible: false,
},
"gramps": {
ContentType: "application/x-gramps-xml",
Compressible: false,
},
"gre": {
ContentType: "application/vnd.geometry-explorer",
Compressible: false,
},
"grv": {
ContentType: "application/vnd.groove-injector",
Compressible: false,
},
"grxml": {
ContentType: "application/srgs+xml",
Compressible: false,
},
"gsf": {
ContentType: "application/x-font-ghostscript",
Compressible: false,
},
"gsheet": {
ContentType: "application/vnd.google-apps.spreadsheet",
Compressible: false,
},
"gslides": {
ContentType: "application/vnd.google-apps.presentation",
Compressible: false,
},
"gtar": {
ContentType: "application/x-gtar",
Compressible: false,
},
"gtm": {
ContentType: "application/vnd.groove-tool-message",
Compressible: false,
},
"gtw": {
ContentType: "model/vnd.gtw",
Compressible: false,
},
"gv": {
ContentType: "text/vnd.graphviz",
Compressible: false,
},
"gxf": {
ContentType: "application/gxf",
Compressible: false,
},
"gxt": {
ContentType: "application/vnd.geonext",
Compressible: false,
},
"gz": {
ContentType: "application/gzip",
Compressible: false,
},
"h": {
ContentType: "text/x-c",
Compressible: false,
},
"h261": {
ContentType: "video/h261",
Compressible: false,
},
"h263": {
ContentType: "video/h263",
Compressible: false,
},
"h264": {
ContentType: "video/h264",
Compressible: false,
},
"hal": {
ContentType: "application/vnd.hal+xml",
Compressible: false,
},
"hbci": {
ContentType: "application/vnd.hbci",
Compressible: false,
},
"hbs": {
ContentType: "text/x-handlebars-template",
Compressible: false,
},
"hdd": {
ContentType: "application/x-virtualbox-hdd",
Compressible: false,
},
"hdf": {
ContentType: "application/x-hdf",
Compressible: false,
},
"heic": {
ContentType: "image/heic",
Compressible: false,
},
"heics": {
ContentType: "image/heic-sequence",
Compressible: false,
},
"heif": {
ContentType: "image/heif",
Compressible: false,
},
"heifs": {
ContentType: "image/heif-sequence",
Compressible: false,
},
"hej2": {
ContentType: "image/hej2k",
Compressible: false,
},
"held": {
ContentType: "application/atsc-held+xml",
Compressible: false,
},
"hh": {
ContentType: "text/x-c",
Compressible: false,
},
"hjson": {
ContentType: "application/hjson",
Compressible: false,
},
"hlp": {
ContentType: "application/winhlp",
Compressible: false,
},
"hpgl": {
ContentType: "application/vnd.hp-hpgl",
Compressible: false,
},
"hpid": {
ContentType: "application/vnd.hp-hpid",
Compressible: false,
},
"hps": {
ContentType: "application/vnd.hp-hps",
Compressible: false,
},
"hqx": {
ContentType: "application/mac-binhex40",
Compressible: false,
},
"hsj2": {
ContentType: "image/hsj2",
Compressible: false,
},
"htc": {
ContentType: "text/x-component",
Compressible: false,
},
"htke": {
ContentType: "application/vnd.kenameaapp",
Compressible: false,
},
"htm": {
ContentType: "text/html",
Compressible: false,
},
"html": {
ContentType: "text/html",
Compressible: false,
},
"hvd": {
ContentType: "application/vnd.yamaha.hv-dic",
Compressible: false,
},
"hvp": {
ContentType: "application/vnd.yamaha.hv-voice",
Compressible: false,
},
"hvs": {
ContentType: "application/vnd.yamaha.hv-script",
Compressible: false,
},
"i2g": {
ContentType: "application/vnd.intergeo",
Compressible: false,
},
"icc": {
ContentType: "application/vnd.iccprofile",
Compressible: false,
},
"ice": {
ContentType: "x-conference/x-cooltalk",
Compressible: false,
},
"icm": {
ContentType: "application/vnd.iccprofile",
Compressible: false,
},
"ico": {
ContentType: "image/x-icon",
Compressible: false,
},
"ics": {
ContentType: "text/calendar",
Compressible: false,
},
"ief": {
ContentType: "image/ief",
Compressible: false,
},
"ifb": {
ContentType: "text/calendar",
Compressible: false,
},
"ifm": {
ContentType: "application/vnd.shana.informed.formdata",
Compressible: false,
},
"iges": {
ContentType: "model/iges",
Compressible: false,
},
"igl": {
ContentType: "application/vnd.igloader",
Compressible: false,
},
"igm": {
ContentType: "application/vnd.insors.igm",
Compressible: false,
},
"igs": {
ContentType: "model/iges",
Compressible: false,
},
"igx": {
ContentType: "application/vnd.micrografx.igx",
Compressible: false,
},
"iif": {
ContentType: "application/vnd.shana.informed.interchange",
Compressible: false,
},
"img": {
ContentType: "application/octet-stream",
Compressible: false,
},
"imp": {
ContentType: "application/vnd.accpac.simply.imp",
Compressible: false,
},
"ims": {
ContentType: "application/vnd.ms-ims",
Compressible: false,
},
"in": {
ContentType: "text/plain",
Compressible: false,
},
"ini": {
ContentType: "text/plain",
Compressible: false,
},
"ink": {
ContentType: "application/inkml+xml",
Compressible: false,
},
"inkml": {
ContentType: "application/inkml+xml",
Compressible: false,
},
"install": {
ContentType: "application/x-install-instructions",
Compressible: false,
},
"iota": {
ContentType: "application/vnd.astraea-software.iota",
Compressible: false,
},
"ipfix": {
ContentType: "application/ipfix",
Compressible: false,
},
"ipk": {
ContentType: "application/vnd.shana.informed.package",
Compressible: false,
},
"irm": {
ContentType: "application/vnd.ibm.rights-management",
Compressible: false,
},
"irp": {
ContentType: "application/vnd.irepository.package+xml",
Compressible: false,
},
"iso": {
ContentType: "application/x-iso9660-image",
Compressible: false,
},
"itp": {
ContentType: "application/vnd.shana.informed.formtemplate",
Compressible: false,
},
"its": {
ContentType: "application/its+xml",
Compressible: false,
},
"ivp": {
ContentType: "application/vnd.immervision-ivp",
Compressible: false,
},
"ivu": {
ContentType: "application/vnd.immervision-ivu",
Compressible: false,
},
"jad": {
ContentType: "text/vnd.sun.j2me.app-descriptor",
Compressible: false,
},
"jade": {
ContentType: "text/jade",
Compressible: false,
},
"jam": {
ContentType: "application/vnd.jam",
Compressible: false,
},
"jar": {
ContentType: "application/java-archive",
Compressible: false,
},
"jardiff": {
ContentType: "application/x-java-archive-diff",
Compressible: false,
},
"java": {
ContentType: "text/x-java-source",
Compressible: false,
},
"jhc": {
ContentType: "image/jphc",
Compressible: false,
},
"jisp": {
ContentType: "application/vnd.jisp",
Compressible: false,
},
"jls": {
ContentType: "image/jls",
Compressible: false,
},
"jlt": {
ContentType: "application/vnd.hp-jlyt",
Compressible: false,
},
"jng": {
ContentType: "image/x-jng",
Compressible: false,
},
"jnlp": {
ContentType: "application/x-java-jnlp-file",
Compressible: false,
},
"joda": {
ContentType: "application/vnd.joost.joda-archive",
Compressible: false,
},
"jp2": {
ContentType: "image/jp2",
Compressible: false,
},
"jpe": {
ContentType: "image/jpeg",
Compressible: false,
},
"jpeg": {
ContentType: "image/jpeg",
Compressible: false,
},
"jpf": {
ContentType: "image/jpx",
Compressible: false,
},
"jpg": {
ContentType: "image/jpeg",
Compressible: false,
},
"jpg2": {
ContentType: "image/jp2",
Compressible: false,
},
"jpgm": {
ContentType: "video/jpm",
Compressible: false,
},
"jpgv": {
ContentType: "video/jpeg",
Compressible: false,
},
"jph": {
ContentType: "image/jph",
Compressible: false,
},
"jpm": {
ContentType: "video/jpm",
Compressible: false,
},
"jpx": {
ContentType: "image/jpx",
Compressible: false,
},
"js": {
ContentType: "text/javascript",
Compressible: false,
},
"json": {
ContentType: "application/json",
Compressible: false,
},
"json5": {
ContentType: "application/json5",
Compressible: false,
},
"jsonld": {
ContentType: "application/ld+json",
Compressible: false,
},
"jsonml": {
ContentType: "application/jsonml+json",
Compressible: false,
},
"jsx": {
ContentType: "text/jsx",
Compressible: false,
},
"jt": {
ContentType: "model/jt",
Compressible: false,
},
"jxr": {
ContentType: "image/jxr",
Compressible: false,
},
"jxra": {
ContentType: "image/jxra",
Compressible: false,
},
"jxrs": {
ContentType: "image/jxrs",
Compressible: false,
},
"jxs": {
ContentType: "image/jxs",
Compressible: false,
},
"jxsc": {
ContentType: "image/jxsc",
Compressible: false,
},
"jxsi": {
ContentType: "image/jxsi",
Compressible: false,
},
"jxss": {
ContentType: "image/jxss",
Compressible: false,
},
"kar": {
ContentType: "audio/midi",
Compressible: false,
},
"karbon": {
ContentType: "application/vnd.kde.karbon",
Compressible: false,
},
"kdbx": {
ContentType: "application/x-keepass2",
Compressible: false,
},
"key": {
ContentType: "application/x-iwork-keynote-sffkey",
Compressible: false,
},
"kfo": {
ContentType: "application/vnd.kde.kformula",
Compressible: false,
},
"kia": {
ContentType: "application/vnd.kidspiration",
Compressible: false,
},
"kml": {
ContentType: "application/vnd.google-earth.kml+xml",
Compressible: false,
},
"kmz": {
ContentType: "application/vnd.google-earth.kmz",
Compressible: false,
},
"kne": {
ContentType: "application/vnd.kinar",
Compressible: false,
},
"knp": {
ContentType: "application/vnd.kinar",
Compressible: false,
},
"kon": {
ContentType: "application/vnd.kde.kontour",
Compressible: false,
},
"kpr": {
ContentType: "application/vnd.kde.kpresenter",
Compressible: false,
},
"kpt": {
ContentType: "application/vnd.kde.kpresenter",
Compressible: false,
},
"kpxx": {
ContentType: "application/vnd.ds-keypoint",
Compressible: false,
},
"ksp": {
ContentType: "application/vnd.kde.kspread",
Compressible: false,
},
"ktr": {
ContentType: "application/vnd.kahootz",
Compressible: false,
},
"ktx": {
ContentType: "image/ktx",
Compressible: false,
},
"ktx2": {
ContentType: "image/ktx2",
Compressible: false,
},
"ktz": {
ContentType: "application/vnd.kahootz",
Compressible: false,
},
"kwd": {
ContentType: "application/vnd.kde.kword",
Compressible: false,
},
"kwt": {
ContentType: "application/vnd.kde.kword",
Compressible: false,
},
"lasxml": {
ContentType: "application/vnd.las.las+xml",
Compressible: false,
},
"latex": {
ContentType: "application/x-latex",
Compressible: false,
},
"lbd": {
ContentType: "application/vnd.llamagraphics.life-balance.desktop",
Compressible: false,
},
"lbe": {
ContentType: "application/vnd.llamagraphics.life-balance.exchange+xml",
Compressible: false,
},
"les": {
ContentType: "application/vnd.hhe.lesson-player",
Compressible: false,
},
"less": {
ContentType: "text/less",
Compressible: false,
},
"lgr": {
ContentType: "application/lgr+xml",
Compressible: false,
},
"lha": {
ContentType: "application/x-lzh-compressed",
Compressible: false,
},
"link66": {
ContentType: "application/vnd.route66.link66+xml",
Compressible: false,
},
"list": {
ContentType: "text/plain",
Compressible: false,
},
"list3820": {
ContentType: "application/vnd.ibm.modcap",
Compressible: false,
},
"listafp": {
ContentType: "application/vnd.ibm.modcap",
Compressible: false,
},
"litcoffee": {
ContentType: "text/coffeescript",
Compressible: false,
},
"lnk": {
ContentType: "application/x-ms-shortcut",
Compressible: false,
},
"log": {
ContentType: "text/plain",
Compressible: false,
},
"lostxml": {
ContentType: "application/lost+xml",
Compressible: false,
},
"lrf": {
ContentType: "application/octet-stream",
Compressible: false,
},
"lrm": {
ContentType: "application/vnd.ms-lrm",
Compressible: false,
},
"ltf": {
ContentType: "application/vnd.frogans.ltf",
Compressible: false,
},
"lua": {
ContentType: "text/x-lua",
Compressible: false,
},
"luac": {
ContentType: "application/x-lua-bytecode",
Compressible: false,
},
"lvp": {
ContentType: "audio/vnd.lucent.voice",
Compressible: false,
},
"lwp": {
ContentType: "application/vnd.lotus-wordpro",
Compressible: false,
},
"lzh": {
ContentType: "application/x-lzh-compressed",
Compressible: false,
},
"m13": {
ContentType: "application/x-msmediaview",
Compressible: false,
},
"m14": {
ContentType: "application/x-msmediaview",
Compressible: false,
},
"m1v": {
ContentType: "video/mpeg",
Compressible: false,
},
"m21": {
ContentType: "application/mp21",
Compressible: false,
},
"m2a": {
ContentType: "audio/mpeg",
Compressible: false,
},
"m2v": {
ContentType: "video/mpeg",
Compressible: false,
},
"m3a": {
ContentType: "audio/mpeg",
Compressible: false,
},
"m3u": {
ContentType: "audio/x-mpegurl",
Compressible: false,
},
"m3u8": {
ContentType: "application/vnd.apple.mpegurl",
Compressible: false,
},
"m4a": {
ContentType: "audio/x-m4a",
Compressible: false,
},
"m4p": {
ContentType: "application/mp4",
Compressible: false,
},
"m4s": {
ContentType: "video/iso.segment",
Compressible: false,
},
"m4u": {
ContentType: "video/vnd.mpegurl",
Compressible: false,
},
"m4v": {
ContentType: "video/x-m4v",
Compressible: false,
},
"ma": {
ContentType: "application/mathematica",
Compressible: false,
},
"mads": {
ContentType: "application/mads+xml",
Compressible: false,
},
"maei": {
ContentType: "application/mmt-aei+xml",
Compressible: false,
},
"mag": {
ContentType: "application/vnd.ecowin.chart",
Compressible: false,
},
"maker": {
ContentType: "application/vnd.framemaker",
Compressible: false,
},
"man": {
ContentType: "text/troff",
Compressible: false,
},
"manifest": {
ContentType: "text/cache-manifest",
Compressible: false,
},
"map": {
ContentType: "application/json",
Compressible: false,
},
"mar": {
ContentType: "application/octet-stream",
Compressible: false,
},
"markdown": {
ContentType: "text/markdown",
Compressible: false,
},
"mathml": {
ContentType: "application/mathml+xml",
Compressible: false,
},
"mb": {
ContentType: "application/mathematica",
Compressible: false,
},
"mbk": {
ContentType: "application/vnd.mobius.mbk",
Compressible: false,
},
"mbox": {
ContentType: "application/mbox",
Compressible: false,
},
"mc1": {
ContentType: "application/vnd.medcalcdata",
Compressible: false,
},
"mcd": {
ContentType: "application/vnd.mcd",
Compressible: false,
},
"mcurl": {
ContentType: "text/vnd.curl.mcurl",
Compressible: false,
},
"md": {
ContentType: "text/markdown",
Compressible: false,
},
"mdb": {
ContentType: "application/x-msaccess",
Compressible: false,
},
"mdi": {
ContentType: "image/vnd.ms-modi",
Compressible: false,
},
"mdx": {
ContentType: "text/mdx",
Compressible: false,
},
"me": {
ContentType: "text/troff",
Compressible: false,
},
"mesh": {
ContentType: "model/mesh",
Compressible: false,
},
"meta4": {
ContentType: "application/metalink4+xml",
Compressible: false,
},
"metalink": {
ContentType: "application/metalink+xml",
Compressible: false,
},
"mets": {
ContentType: "application/mets+xml",
Compressible: false,
},
"mfm": {
ContentType: "application/vnd.mfmp",
Compressible: false,
},
"mft": {
ContentType: "application/rpki-manifest",
Compressible: false,
},
"mgp": {
ContentType: "application/vnd.osgeo.mapguide.package",
Compressible: false,
},
"mgz": {
ContentType: "application/vnd.proteus.magazine",
Compressible: false,
},
"mid": {
ContentType: "audio/midi",
Compressible: false,
},
"midi": {
ContentType: "audio/midi",
Compressible: false,
},
"mie": {
ContentType: "application/x-mie",
Compressible: false,
},
"mif": {
ContentType: "application/vnd.mif",
Compressible: false,
},
"mime": {
ContentType: "message/rfc822",
Compressible: false,
},
"mj2": {
ContentType: "video/mj2",
Compressible: false,
},
"mjp2": {
ContentType: "video/mj2",
Compressible: false,
},
"mjs": {
ContentType: "text/javascript",
Compressible: false,
},
"mk3d": {
ContentType: "video/x-matroska",
Compressible: false,
},
"mka": {
ContentType: "audio/x-matroska",
Compressible: false,
},
"mkd": {
ContentType: "text/x-markdown",
Compressible: false,
},
"mks": {
ContentType: "video/x-matroska",
Compressible: false,
},
"mkv": {
ContentType: "video/x-matroska",
Compressible: false,
},
"mlp": {
ContentType: "application/vnd.dolby.mlp",
Compressible: false,
},
"mmd": {
ContentType: "application/vnd.chipnuts.karaoke-mmd",
Compressible: false,
},
"mmf": {
ContentType: "application/vnd.smaf",
Compressible: false,
},
"mml": {
ContentType: "text/mathml",
Compressible: false,
},
"mmr": {
ContentType: "image/vnd.fujixerox.edmics-mmr",
Compressible: false,
},
"mng": {
ContentType: "video/x-mng",
Compressible: false,
},
"mny": {
ContentType: "application/x-msmoney",
Compressible: false,
},
"mobi": {
ContentType: "application/x-mobipocket-ebook",
Compressible: false,
},
"mods": {
ContentType: "application/mods+xml",
Compressible: false,
},
"mov": {
ContentType: "video/quicktime",
Compressible: false,
},
"movie": {
ContentType: "video/x-sgi-movie",
Compressible: false,
},
"mp2": {
ContentType: "audio/mpeg",
Compressible: false,
},
"mp21": {
ContentType: "application/mp21",
Compressible: false,
},
"mp2a": {
ContentType: "audio/mpeg",
Compressible: false,
},
"mp3": {
ContentType: "audio/mpeg",
Compressible: false,
},
"mp4": {
ContentType: "video/mp4",
Compressible: false,
},
"mp4a": {
ContentType: "audio/mp4",
Compressible: false,
},
"mp4s": {
ContentType: "application/mp4",
Compressible: false,
},
"mp4v": {
ContentType: "video/mp4",
Compressible: false,
},
"mpc": {
ContentType: "application/vnd.mophun.certificate",
Compressible: false,
},
"mpd": {
ContentType: "application/dash+xml",
Compressible: false,
},
"mpe": {
ContentType: "video/mpeg",
Compressible: false,
},
"mpeg": {
ContentType: "video/mpeg",
Compressible: false,
},
"mpf": {
ContentType: "application/media-policy-dataset+xml",
Compressible: false,
},
"mpg": {
ContentType: "video/mpeg",
Compressible: false,
},
"mpg4": {
ContentType: "video/mp4",
Compressible: false,
},
"mpga": {
ContentType: "audio/mpeg",
Compressible: false,
},
"mpkg": {
ContentType: "application/vnd.apple.installer+xml",
Compressible: false,
},
"mpm": {
ContentType: "application/vnd.blueice.multipass",
Compressible: false,
},
"mpn": {
ContentType: "application/vnd.mophun.application",
Compressible: false,
},
"mpp": {
ContentType: "application/vnd.ms-project",
Compressible: false,
},
"mpt": {
ContentType: "application/vnd.ms-project",
Compressible: false,
},
"mpy": {
ContentType: "application/vnd.ibm.minipay",
Compressible: false,
},
"mqy": {
ContentType: "application/vnd.mobius.mqy",
Compressible: false,
},
"mrc": {
ContentType: "application/marc",
Compressible: false,
},
"mrcx": {
ContentType: "application/marcxml+xml",
Compressible: false,
},
"ms": {
ContentType: "text/troff",
Compressible: false,
},
"mscml": {
ContentType: "application/mediaservercontrol+xml",
Compressible: false,
},
"mseed": {
ContentType: "application/vnd.fdsn.mseed",
Compressible: false,
},
"mseq": {
ContentType: "application/vnd.mseq",
Compressible: false,
},
"msf": {
ContentType: "application/vnd.epson.msf",
Compressible: false,
},
"msg": {
ContentType: "application/vnd.ms-outlook",
Compressible: false,
},
"msh": {
ContentType: "model/mesh",
Compressible: false,
},
"msi": {
ContentType: "application/x-msdownload",
Compressible: false,
},
"msix": {
ContentType: "application/msix",
Compressible: false,
},
"msixbundle": {
ContentType: "application/msixbundle",
Compressible: false,
},
"msl": {
ContentType: "application/vnd.mobius.msl",
Compressible: false,
},
"msm": {
ContentType: "application/octet-stream",
Compressible: false,
},
"msp": {
ContentType: "application/octet-stream",
Compressible: false,
},
"msty": {
ContentType: "application/vnd.muvee.style",
Compressible: false,
},
"mtl": {
ContentType: "model/mtl",
Compressible: false,
},
"mts": {
ContentType: "model/vnd.mts",
Compressible: false,
},
"mus": {
ContentType: "application/vnd.musician",
Compressible: false,
},
"musd": {
ContentType: "application/mmt-usd+xml",
Compressible: false,
},
"musicxml": {
ContentType: "application/vnd.recordare.musicxml+xml",
Compressible: false,
},
"mvb": {
ContentType: "application/x-msmediaview",
Compressible: false,
},
"mvt": {
ContentType: "application/vnd.mapbox-vector-tile",
Compressible: false,
},
"mwf": {
ContentType: "application/vnd.mfer",
Compressible: false,
},
"mxf": {
ContentType: "application/mxf",
Compressible: false,
},
"mxl": {
ContentType: "application/vnd.recordare.musicxml",
Compressible: false,
},
"mxmf": {
ContentType: "audio/mobile-xmf",
Compressible: false,
},
"mxml": {
ContentType: "application/xv+xml",
Compressible: false,
},
"mxs": {
ContentType: "application/vnd.triscape.mxs",
Compressible: false,
},
"mxu": {
ContentType: "video/vnd.mpegurl",
Compressible: false,
},
"n-gage": {
ContentType: "application/vnd.nokia.n-gage.symbian.install",
Compressible: false,
},
"n3": {
ContentType: "text/n3",
Compressible: false,
},
"nb": {
ContentType: "application/mathematica",
Compressible: false,
},
"nbp": {
ContentType: "application/vnd.wolfram.player",
Compressible: false,
},
"nc": {
ContentType: "application/x-netcdf",
Compressible: false,
},
"ncx": {
ContentType: "application/x-dtbncx+xml",
Compressible: false,
},
"nfo": {
ContentType: "text/x-nfo",
Compressible: false,
},
"ngdat": {
ContentType: "application/vnd.nokia.n-gage.data",
Compressible: false,
},
"nitf": {
ContentType: "application/vnd.nitf",
Compressible: false,
},
"nlu": {
ContentType: "application/vnd.neurolanguage.nlu",
Compressible: false,
},
"nml": {
ContentType: "application/vnd.enliven",
Compressible: false,
},
"nnd": {
ContentType: "application/vnd.noblenet-directory",
Compressible: false,
},
"nns": {
ContentType: "application/vnd.noblenet-sealer",
Compressible: false,
},
"nnw": {
ContentType: "application/vnd.noblenet-web",
Compressible: false,
},
"npx": {
ContentType: "image/vnd.net-fpx",
Compressible: false,
},
"nq": {
ContentType: "application/n-quads",
Compressible: false,
},
"nsc": {
ContentType: "application/x-conference",
Compressible: false,
},
"nsf": {
ContentType: "application/vnd.lotus-notes",
Compressible: false,
},
"nt": {
ContentType: "application/n-triples",
Compressible: false,
},
"ntf": {
ContentType: "application/vnd.nitf",
Compressible: false,
},
"numbers": {
ContentType: "application/x-iwork-numbers-sffnumbers",
Compressible: false,
},
"nzb": {
ContentType: "application/x-nzb",
Compressible: false,
},
"oa2": {
ContentType: "application/vnd.fujitsu.oasys2",
Compressible: false,
},
"oa3": {
ContentType: "application/vnd.fujitsu.oasys3",
Compressible: false,
},
"oas": {
ContentType: "application/vnd.fujitsu.oasys",
Compressible: false,
},
"obd": {
ContentType: "application/x-msbinder",
Compressible: false,
},
"obgx": {
ContentType: "application/vnd.openblox.game+xml",
Compressible: false,
},
"obj": {
ContentType: "model/obj",
Compressible: false,
},
"oda": {
ContentType: "application/oda",
Compressible: false,
},
"odb": {
ContentType: "application/vnd.oasis.opendocument.database",
Compressible: false,
},
"odc": {
ContentType: "application/vnd.oasis.opendocument.chart",
Compressible: false,
},
"odf": {
ContentType: "application/vnd.oasis.opendocument.formula",
Compressible: false,
},
"odft": {
ContentType: "application/vnd.oasis.opendocument.formula-template",
Compressible: false,
},
"odg": {
ContentType: "application/vnd.oasis.opendocument.graphics",
Compressible: false,
},
"odi": {
ContentType: "application/vnd.oasis.opendocument.image",
Compressible: false,
},
"odm": {
ContentType: "application/vnd.oasis.opendocument.text-master",
Compressible: false,
},
"odp": {
ContentType: "application/vnd.oasis.opendocument.presentation",
Compressible: false,
},
"ods": {
ContentType: "application/vnd.oasis.opendocument.spreadsheet",
Compressible: false,
},
"odt": {
ContentType: "application/vnd.oasis.opendocument.text",
Compressible: false,
},
"oga": {
ContentType: "audio/ogg",
Compressible: false,
},
"ogex": {
ContentType: "model/vnd.opengex",
Compressible: false,
},
"ogg": {
ContentType: "audio/ogg",
Compressible: false,
},
"ogv": {
ContentType: "video/ogg",
Compressible: false,
},
"ogx": {
ContentType: "application/ogg",
Compressible: false,
},
"omdoc": {
ContentType: "application/omdoc+xml",
Compressible: false,
},
"onepkg": {
ContentType: "application/onenote",
Compressible: false,
},
"onetmp": {
ContentType: "application/onenote",
Compressible: false,
},
"onetoc": {
ContentType: "application/onenote",
Compressible: false,
},
"onetoc2": {
ContentType: "application/onenote",
Compressible: false,
},
"opf": {
ContentType: "application/oebps-package+xml",
Compressible: false,
},
"opml": {
ContentType: "text/x-opml",
Compressible: false,
},
"oprc": {
ContentType: "application/vnd.palm",
Compressible: false,
},
"opus": {
ContentType: "audio/ogg",
Compressible: false,
},
"org": {
ContentType: "text/x-org",
Compressible: false,
},
"osf": {
ContentType: "application/vnd.yamaha.openscoreformat",
Compressible: false,
},
"osfpvg": {
ContentType: "application/vnd.yamaha.openscoreformat.osfpvg+xml",
Compressible: false,
},
"osm": {
ContentType: "application/vnd.openstreetmap.data+xml",
Compressible: false,
},
"otc": {
ContentType: "application/vnd.oasis.opendocument.chart-template",
Compressible: false,
},
"otf": {
ContentType: "font/otf",
Compressible: false,
},
"otg": {
ContentType: "application/vnd.oasis.opendocument.graphics-template",
Compressible: false,
},
"oth": {
ContentType: "application/vnd.oasis.opendocument.text-web",
Compressible: false,
},
"oti": {
ContentType: "application/vnd.oasis.opendocument.image-template",
Compressible: false,
},
"otp": {
ContentType: "application/vnd.oasis.opendocument.presentation-template",
Compressible: false,
},
"ots": {
ContentType: "application/vnd.oasis.opendocument.spreadsheet-template",
Compressible: false,
},
"ott": {
ContentType: "application/vnd.oasis.opendocument.text-template",
Compressible: false,
},
"ova": {
ContentType: "application/x-virtualbox-ova",
Compressible: false,
},
"ovf": {
ContentType: "application/x-virtualbox-ovf",
Compressible: false,
},
"owl": {
ContentType: "application/rdf+xml",
Compressible: false,
},
"oxps": {
ContentType: "application/oxps",
Compressible: false,
},
"oxt": {
ContentType: "application/vnd.openofficeorg.extension",
Compressible: false,
},
"p": {
ContentType: "text/x-pascal",
Compressible: false,
},
"p10": {
ContentType: "application/pkcs10",
Compressible: false,
},
"p12": {
ContentType: "application/x-pkcs12",
Compressible: false,
},
"p7b": {
ContentType: "application/x-pkcs7-certificates",
Compressible: false,
},
"p7c": {
ContentType: "application/pkcs7-mime",
Compressible: false,
},
"p7m": {
ContentType: "application/pkcs7-mime",
Compressible: false,
},
"p7r": {
ContentType: "application/x-pkcs7-certreqresp",
Compressible: false,
},
"p7s": {
ContentType: "application/pkcs7-signature",
Compressible: false,
},
"p8": {
ContentType: "application/pkcs8",
Compressible: false,
},
"pac": {
ContentType: "application/x-ns-proxy-autoconfig",
Compressible: false,
},
"pages": {
ContentType: "application/x-iwork-pages-sffpages",
Compressible: false,
},
"pas": {
ContentType: "text/x-pascal",
Compressible: false,
},
"paw": {
ContentType: "application/vnd.pawaafile",
Compressible: false,
},
"pbd": {
ContentType: "application/vnd.powerbuilder6",
Compressible: false,
},
"pbm": {
ContentType: "image/x-portable-bitmap",
Compressible: false,
},
"pcap": {
ContentType: "application/vnd.tcpdump.pcap",
Compressible: false,
},
"pcf": {
ContentType: "application/x-font-pcf",
Compressible: false,
},
"pcl": {
ContentType: "application/vnd.hp-pcl",
Compressible: false,
},
"pclxl": {
ContentType: "application/vnd.hp-pclxl",
Compressible: false,
},
"pct": {
ContentType: "image/x-pict",
Compressible: false,
},
"pcurl": {
ContentType: "application/vnd.curl.pcurl",
Compressible: false,
},
"pcx": {
ContentType: "image/x-pcx",
Compressible: false,
},
"pdb": {
ContentType: "application/x-pilot",
Compressible: false,
},
"pde": {
ContentType: "text/x-processing",
Compressible: false,
},
"pdf": {
ContentType: "application/pdf",
Compressible: false,
},
"pem": {
ContentType: "application/x-x509-ca-cert",
Compressible: false,
},
"pfa": {
ContentType: "application/x-font-type1",
Compressible: false,
},
"pfb": {
ContentType: "application/x-font-type1",
Compressible: false,
},
"pfm": {
ContentType: "application/x-font-type1",
Compressible: false,
},
"pfr": {
ContentType: "application/font-tdpfr",
Compressible: false,
},
"pfx": {
ContentType: "application/x-pkcs12",
Compressible: false,
},
"pgm": {
ContentType: "image/x-portable-graymap",
Compressible: false,
},
"pgn": {
ContentType: "application/x-chess-pgn",
Compressible: false,
},
"pgp": {
ContentType: "application/pgp-encrypted",
Compressible: false,
},
"php": {
ContentType: "application/x-httpd-php",
Compressible: false,
},
"pic": {
ContentType: "image/x-pict",
Compressible: false,
},
"pkg": {
ContentType: "application/octet-stream",
Compressible: false,
},
"pki": {
ContentType: "application/pkixcmp",
Compressible: false,
},
"pkipath": {
ContentType: "application/pkix-pkipath",
Compressible: false,
},
"pkpass": {
ContentType: "application/vnd.apple.pkpass",
Compressible: false,
},
"pl": {
ContentType: "application/x-perl",
Compressible: false,
},
"plb": {
ContentType: "application/vnd.3gpp.pic-bw-large",
Compressible: false,
},
"plc": {
ContentType: "application/vnd.mobius.plc",
Compressible: false,
},
"plf": {
ContentType: "application/vnd.pocketlearn",
Compressible: false,
},
"pls": {
ContentType: "application/pls+xml",
Compressible: false,
},
"pm": {
ContentType: "application/x-perl",
Compressible: false,
},
"pml": {
ContentType: "application/vnd.ctc-posml",
Compressible: false,
},
"png": {
ContentType: "image/png",
Compressible: false,
},
"pnm": {
ContentType: "image/x-portable-anymap",
Compressible: false,
},
"portpkg": {
ContentType: "application/vnd.macports.portpkg",
Compressible: false,
},
"pot": {
ContentType: "application/vnd.ms-powerpoint",
Compressible: false,
},
"potm": {
ContentType: "application/vnd.ms-powerpoint.template.macroenabled.12",
Compressible: false,
},
"potx": {
ContentType: "application/vnd.openxmlformats-officedocument.presentationml.template",
Compressible: false,
},
"ppam": {
ContentType: "application/vnd.ms-powerpoint.addin.macroenabled.12",
Compressible: false,
},
"ppd": {
ContentType: "application/vnd.cups-ppd",
Compressible: false,
},
"ppm": {
ContentType: "image/x-portable-pixmap",
Compressible: false,
},
"pps": {
ContentType: "application/vnd.ms-powerpoint",
Compressible: false,
},
"ppsm": {
ContentType: "application/vnd.ms-powerpoint.slideshow.macroenabled.12",
Compressible: false,
},
"ppsx": {
ContentType: "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
Compressible: false,
},
"ppt": {
ContentType: "application/vnd.ms-powerpoint",
Compressible: false,
},
"pptm": {
ContentType: "application/vnd.ms-powerpoint.presentation.macroenabled.12",
Compressible: false,
},
"pptx": {
ContentType: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
Compressible: false,
},
"pqa": {
ContentType: "application/vnd.palm",
Compressible: false,
},
"prc": {
ContentType: "model/prc",
Compressible: false,
},
"pre": {
ContentType: "application/vnd.lotus-freelance",
Compressible: false,
},
"prf": {
ContentType: "application/pics-rules",
Compressible: false,
},
"provx": {
ContentType: "application/provenance+xml",
Compressible: false,
},
"ps": {
ContentType: "application/postscript",
Compressible: false,
},
"psb": {
ContentType: "application/vnd.3gpp.pic-bw-small",
Compressible: false,
},
"psd": {
ContentType: "image/vnd.adobe.photoshop",
Compressible: false,
},
"psf": {
ContentType: "application/x-font-linux-psf",
Compressible: false,
},
"pskcxml": {
ContentType: "application/pskc+xml",
Compressible: false,
},
"pti": {
ContentType: "image/prs.pti",
Compressible: false,
},
"ptid": {
ContentType: "application/vnd.pvi.ptid1",
Compressible: false,
},
"pub": {
ContentType: "application/x-mspublisher",
Compressible: false,
},
"pvb": {
ContentType: "application/vnd.3gpp.pic-bw-var",
Compressible: false,
},
"pwn": {
ContentType: "application/vnd.3m.post-it-notes",
Compressible: false,
},
"pya": {
ContentType: "audio/vnd.ms-playready.media.pya",
Compressible: false,
},
"pyo": {
ContentType: "model/vnd.pytha.pyox",
Compressible: false,
},
"pyox": {
ContentType: "model/vnd.pytha.pyox",
Compressible: false,
},
"pyv": {
ContentType: "video/vnd.ms-playready.media.pyv",
Compressible: false,
},
"qam": {
ContentType: "application/vnd.epson.quickanime",
Compressible: false,
},
"qbo": {
ContentType: "application/vnd.intu.qbo",
Compressible: false,
},
"qfx": {
ContentType: "application/vnd.intu.qfx",
Compressible: false,
},
"qps": {
ContentType: "application/vnd.publishare-delta-tree",
Compressible: false,
},
"qt": {
ContentType: "video/quicktime",
Compressible: false,
},
"qwd": {
ContentType: "application/vnd.quark.quarkxpress",
Compressible: false,
},
"qwt": {
ContentType: "application/vnd.quark.quarkxpress",
Compressible: false,
},
"qxb": {
ContentType: "application/vnd.quark.quarkxpress",
Compressible: false,
},
"qxd": {
ContentType: "application/vnd.quark.quarkxpress",
Compressible: false,
},
"qxl": {
ContentType: "application/vnd.quark.quarkxpress",
Compressible: false,
},
"qxt": {
ContentType: "application/vnd.quark.quarkxpress",
Compressible: false,
},
"ra": {
ContentType: "audio/x-realaudio",
Compressible: false,
},
"ram": {
ContentType: "audio/x-pn-realaudio",
Compressible: false,
},
"raml": {
ContentType: "application/raml+yaml",
Compressible: false,
},
"rapd": {
ContentType: "application/route-apd+xml",
Compressible: false,
},
"rar": {
ContentType: "application/x-rar-compressed",
Compressible: false,
},
"ras": {
ContentType: "image/x-cmu-raster",
Compressible: false,
},
"rcprofile": {
ContentType: "application/vnd.ipunplugged.rcprofile",
Compressible: false,
},
"rdf": {
ContentType: "application/rdf+xml",
Compressible: false,
},
"rdz": {
ContentType: "application/vnd.data-vision.rdz",
Compressible: false,
},
"relo": {
ContentType: "application/p2p-overlay+xml",
Compressible: false,
},
"rep": {
ContentType: "application/vnd.businessobjects",
Compressible: false,
},
"res": {
ContentType: "application/x-dtbresource+xml",
Compressible: false,
},
"rgb": {
ContentType: "image/x-rgb",
Compressible: false,
},
"rif": {
ContentType: "application/reginfo+xml",
Compressible: false,
},
"rip": {
ContentType: "audio/vnd.rip",
Compressible: false,
},
"ris": {
ContentType: "application/x-research-info-systems",
Compressible: false,
},
"rl": {
ContentType: "application/resource-lists+xml",
Compressible: false,
},
"rlc": {
ContentType: "image/vnd.fujixerox.edmics-rlc",
Compressible: false,
},
"rld": {
ContentType: "application/resource-lists-diff+xml",
Compressible: false,
},
"rm": {
ContentType: "application/vnd.rn-realmedia",
Compressible: false,
},
"rmi": {
ContentType: "audio/midi",
Compressible: false,
},
"rmp": {
ContentType: "audio/x-pn-realaudio-plugin",
Compressible: false,
},
"rms": {
ContentType: "application/vnd.jcp.javame.midlet-rms",
Compressible: false,
},
"rmvb": {
ContentType: "application/vnd.rn-realmedia-vbr",
Compressible: false,
},
"rnc": {
ContentType: "application/relax-ng-compact-syntax",
Compressible: false,
},
"rng": {
ContentType: "application/xml",
Compressible: false,
},
"roa": {
ContentType: "application/rpki-roa",
Compressible: false,
},
"roff": {
ContentType: "text/troff",
Compressible: false,
},
"rp9": {
ContentType: "application/vnd.cloanto.rp9",
Compressible: false,
},
"rpm": {
ContentType: "application/x-redhat-package-manager",
Compressible: false,
},
"rpss": {
ContentType: "application/vnd.nokia.radio-presets",
Compressible: false,
},
"rpst": {
ContentType: "application/vnd.nokia.radio-preset",
Compressible: false,
},
"rq": {
ContentType: "application/sparql-query",
Compressible: false,
},
"rs": {
ContentType: "application/rls-services+xml",
Compressible: false,
},
"rsat": {
ContentType: "application/atsc-rsat+xml",
Compressible: false,
},
"rsd": {
ContentType: "application/rsd+xml",
Compressible: false,
},
"rsheet": {
ContentType: "application/urc-ressheet+xml",
Compressible: false,
},
"rss": {
ContentType: "application/rss+xml",
Compressible: false,
},
"rtf": {
ContentType: "text/rtf",
Compressible: false,
},
"rtx": {
ContentType: "text/richtext",
Compressible: false,
},
"run": {
ContentType: "application/x-makeself",
Compressible: false,
},
"rusd": {
ContentType: "application/route-usd+xml",
Compressible: false,
},
"s": {
ContentType: "text/x-asm",
Compressible: false,
},
"s3m": {
ContentType: "audio/s3m",
Compressible: false,
},
"saf": {
ContentType: "application/vnd.yamaha.smaf-audio",
Compressible: false,
},
"sass": {
ContentType: "text/x-sass",
Compressible: false,
},
"sbml": {
ContentType: "application/sbml+xml",
Compressible: false,
},
"sc": {
ContentType: "application/vnd.ibm.secure-container",
Compressible: false,
},
"scd": {
ContentType: "application/x-msschedule",
Compressible: false,
},
"scm": {
ContentType: "application/vnd.lotus-screencam",
Compressible: false,
},
"scq": {
ContentType: "application/scvp-cv-request",
Compressible: false,
},
"scs": {
ContentType: "application/scvp-cv-response",
Compressible: false,
},
"scss": {
ContentType: "text/x-scss",
Compressible: false,
},
"scurl": {
ContentType: "text/vnd.curl.scurl",
Compressible: false,
},
"sda": {
ContentType: "application/vnd.stardivision.draw",
Compressible: false,
},
"sdc": {
ContentType: "application/vnd.stardivision.calc",
Compressible: false,
},
"sdd": {
ContentType: "application/vnd.stardivision.impress",
Compressible: false,
},
"sdkd": {
ContentType: "application/vnd.solent.sdkm+xml",
Compressible: false,
},
"sdkm": {
ContentType: "application/vnd.solent.sdkm+xml",
Compressible: false,
},
"sdp": {
ContentType: "application/sdp",
Compressible: false,
},
"sdw": {
ContentType: "application/vnd.stardivision.writer",
Compressible: false,
},
"sea": {
ContentType: "application/x-sea",
Compressible: false,
},
"see": {
ContentType: "application/vnd.seemail",
Compressible: false,
},
"seed": {
ContentType: "application/vnd.fdsn.seed",
Compressible: false,
},
"sema": {
ContentType: "application/vnd.sema",
Compressible: false,
},
"semd": {
ContentType: "application/vnd.semd",
Compressible: false,
},
"semf": {
ContentType: "application/vnd.semf",
Compressible: false,
},
"senmlx": {
ContentType: "application/senml+xml",
Compressible: false,
},
"sensmlx": {
ContentType: "application/sensml+xml",
Compressible: false,
},
"ser": {
ContentType: "application/java-serialized-object",
Compressible: false,
},
"setpay": {
ContentType: "application/set-payment-initiation",
Compressible: false,
},
"setreg": {
ContentType: "application/set-registration-initiation",
Compressible: false,
},
"sfd-hdstx": {
ContentType: "application/vnd.hydrostatix.sof-data",
Compressible: false,
},
"sfs": {
ContentType: "application/vnd.spotfire.sfs",
Compressible: false,
},
"sfv": {
ContentType: "text/x-sfv",
Compressible: false,
},
"sgi": {
ContentType: "image/sgi",
Compressible: false,
},
"sgl": {
ContentType: "application/vnd.stardivision.writer-global",
Compressible: false,
},
"sgm": {
ContentType: "text/sgml",
Compressible: false,
},
"sgml": {
ContentType: "text/sgml",
Compressible: false,
},
"sh": {
ContentType: "application/x-sh",
Compressible: false,
},
"shar": {
ContentType: "application/x-shar",
Compressible: false,
},
"shex": {
ContentType: "text/shex",
Compressible: false,
},
"shf": {
ContentType: "application/shf+xml",
Compressible: false,
},
"shtml": {
ContentType: "text/html",
Compressible: false,
},
"sid": {
ContentType: "image/x-mrsid-image",
Compressible: false,
},
"sieve": {
ContentType: "application/sieve",
Compressible: false,
},
"sig": {
ContentType: "application/pgp-signature",
Compressible: false,
},
"sil": {
ContentType: "audio/silk",
Compressible: false,
},
"silo": {
ContentType: "model/mesh",
Compressible: false,
},
"sis": {
ContentType: "application/vnd.symbian.install",
Compressible: false,
},
"sisx": {
ContentType: "application/vnd.symbian.install",
Compressible: false,
},
"sit": {
ContentType: "application/x-stuffit",
Compressible: false,
},
"sitx": {
ContentType: "application/x-stuffitx",
Compressible: false,
},
"siv": {
ContentType: "application/sieve",
Compressible: false,
},
"skd": {
ContentType: "application/vnd.koan",
Compressible: false,
},
"skm": {
ContentType: "application/vnd.koan",
Compressible: false,
},
"skp": {
ContentType: "application/vnd.koan",
Compressible: false,
},
"skt": {
ContentType: "application/vnd.koan",
Compressible: false,
},
"sldm": {
ContentType: "application/vnd.ms-powerpoint.slide.macroenabled.12",
Compressible: false,
},
"sldx": {
ContentType: "application/vnd.openxmlformats-officedocument.presentationml.slide",
Compressible: false,
},
"slim": {
ContentType: "text/slim",
Compressible: false,
},
"slm": {
ContentType: "text/slim",
Compressible: false,
},
"sls": {
ContentType: "application/route-s-tsid+xml",
Compressible: false,
},
"slt": {
ContentType: "application/vnd.epson.salt",
Compressible: false,
},
"sm": {
ContentType: "application/vnd.stepmania.stepchart",
Compressible: false,
},
"smf": {
ContentType: "application/vnd.stardivision.math",
Compressible: false,
},
"smi": {
ContentType: "application/smil+xml",
Compressible: false,
},
"smil": {
ContentType: "application/smil+xml",
Compressible: false,
},
"smv": {
ContentType: "video/x-smv",
Compressible: false,
},
"smzip": {
ContentType: "application/vnd.stepmania.package",
Compressible: false,
},
"snd": {
ContentType: "audio/basic",
Compressible: false,
},
"snf": {
ContentType: "application/x-font-snf",
Compressible: false,
},
"so": {
ContentType: "application/octet-stream",
Compressible: false,
},
"spc": {
ContentType: "application/x-pkcs7-certificates",
Compressible: false,
},
"spdx": {
ContentType: "text/spdx",
Compressible: false,
},
"spf": {
ContentType: "application/vnd.yamaha.smaf-phrase",
Compressible: false,
},
"spl": {
ContentType: "application/x-futuresplash",
Compressible: false,
},
"spot": {
ContentType: "text/vnd.in3d.spot",
Compressible: false,
},
"spp": {
ContentType: "application/scvp-vp-response",
Compressible: false,
},
"spq": {
ContentType: "application/scvp-vp-request",
Compressible: false,
},
"spx": {
ContentType: "audio/ogg",
Compressible: false,
},
"sql": {
ContentType: "application/x-sql",
Compressible: false,
},
"src": {
ContentType: "application/x-wais-source",
Compressible: false,
},
"srt": {
ContentType: "application/x-subrip",
Compressible: false,
},
"sru": {
ContentType: "application/sru+xml",
Compressible: false,
},
"srx": {
ContentType: "application/sparql-results+xml",
Compressible: false,
},
"ssdl": {
ContentType: "application/ssdl+xml",
Compressible: false,
},
"sse": {
ContentType: "application/vnd.kodak-descriptor",
Compressible: false,
},
"ssf": {
ContentType: "application/vnd.epson.ssf",
Compressible: false,
},
"ssml": {
ContentType: "application/ssml+xml",
Compressible: false,
},
"st": {
ContentType: "application/vnd.sailingtracker.track",
Compressible: false,
},
"stc": {
ContentType: "application/vnd.sun.xml.calc.template",
Compressible: false,
},
"std": {
ContentType: "application/vnd.sun.xml.draw.template",
Compressible: false,
},
"stf": {
ContentType: "application/vnd.wt.stf",
Compressible: false,
},
"sti": {
ContentType: "application/vnd.sun.xml.impress.template",
Compressible: false,
},
"stk": {
ContentType: "application/hyperstudio",
Compressible: false,
},
"stl": {
ContentType: "model/stl",
Compressible: false,
},
"stpx": {
ContentType: "model/step+xml",
Compressible: false,
},
"stpxz": {
ContentType: "model/step-xml+zip",
Compressible: false,
},
"stpz": {
ContentType: "model/step+zip",
Compressible: false,
},
"str": {
ContentType: "application/vnd.pg.format",
Compressible: false,
},
"stw": {
ContentType: "application/vnd.sun.xml.writer.template",
Compressible: false,
},
"styl": {
ContentType: "text/stylus",
Compressible: false,
},
"stylus": {
ContentType: "text/stylus",
Compressible: false,
},
"sub": {
ContentType: "text/vnd.dvb.subtitle",
Compressible: false,
},
"sus": {
ContentType: "application/vnd.sus-calendar",
Compressible: false,
},
"susp": {
ContentType: "application/vnd.sus-calendar",
Compressible: false,
},
"sv4cpio": {
ContentType: "application/x-sv4cpio",
Compressible: false,
},
"sv4crc": {
ContentType: "application/x-sv4crc",
Compressible: false,
},
"svc": {
ContentType: "application/vnd.dvb.service",
Compressible: false,
},
"svd": {
ContentType: "application/vnd.svd",
Compressible: false,
},
"svg": {
ContentType: "image/svg+xml",
Compressible: false,
},
"svgz": {
ContentType: "image/svg+xml",
Compressible: false,
},
"swa": {
ContentType: "application/x-director",
Compressible: false,
},
"swf": {
ContentType: "application/x-shockwave-flash",
Compressible: false,
},
"swi": {
ContentType: "application/vnd.aristanetworks.swi",
Compressible: false,
},
"swidtag": {
ContentType: "application/swid+xml",
Compressible: false,
},
"sxc": {
ContentType: "application/vnd.sun.xml.calc",
Compressible: false,
},
"sxd": {
ContentType: "application/vnd.sun.xml.draw",
Compressible: false,
},
"sxg": {
ContentType: "application/vnd.sun.xml.writer.global",
Compressible: false,
},
"sxi": {
ContentType: "application/vnd.sun.xml.impress",
Compressible: false,
},
"sxm": {
ContentType: "application/vnd.sun.xml.math",
Compressible: false,
},
"sxw": {
ContentType: "application/vnd.sun.xml.writer",
Compressible: false,
},
"t": {
ContentType: "text/troff",
Compressible: false,
},
"t3": {
ContentType: "application/x-t3vm-image",
Compressible: false,
},
"t38": {
ContentType: "image/t38",
Compressible: false,
},
"taglet": {
ContentType: "application/vnd.mynfc",
Compressible: false,
},
"tao": {
ContentType: "application/vnd.tao.intent-module-archive",
Compressible: false,
},
"tap": {
ContentType: "image/vnd.tencent.tap",
Compressible: false,
},
"tar": {
ContentType: "application/x-tar",
Compressible: false,
},
"tcap": {
ContentType: "application/vnd.3gpp2.tcap",
Compressible: false,
},
"tcl": {
ContentType: "application/x-tcl",
Compressible: false,
},
"td": {
ContentType: "application/urc-targetdesc+xml",
Compressible: false,
},
"teacher": {
ContentType: "application/vnd.smart.teacher",
Compressible: false,
},
"tei": {
ContentType: "application/tei+xml",
Compressible: false,
},
"teicorpus": {
ContentType: "application/tei+xml",
Compressible: false,
},
"tex": {
ContentType: "application/x-tex",
Compressible: false,
},
"texi": {
ContentType: "application/x-texinfo",
Compressible: false,
},
"texinfo": {
ContentType: "application/x-texinfo",
Compressible: false,
},
"text": {
ContentType: "text/plain",
Compressible: false,
},
"tfi": {
ContentType: "application/thraud+xml",
Compressible: false,
},
"tfm": {
ContentType: "application/x-tex-tfm",
Compressible: false,
},
"tfx": {
ContentType: "image/tiff-fx",
Compressible: false,
},
"tga": {
ContentType: "image/x-tga",
Compressible: false,
},
"thmx": {
ContentType: "application/vnd.ms-officetheme",
Compressible: false,
},
"tif": {
ContentType: "image/tiff",
Compressible: false,
},
"tiff": {
ContentType: "image/tiff",
Compressible: false,
},
"tk": {
ContentType: "application/x-tcl",
Compressible: false,
},
"tmo": {
ContentType: "application/vnd.tmobile-livetv",
Compressible: false,
},
"toml": {
ContentType: "application/toml",
Compressible: false,
},
"torrent": {
ContentType: "application/x-bittorrent",
Compressible: false,
},
"tpl": {
ContentType: "application/vnd.groove-tool-template",
Compressible: false,
},
"tpt": {
ContentType: "application/vnd.trid.tpt",
Compressible: false,
},
"tr": {
ContentType: "text/troff",
Compressible: false,
},
"tra": {
ContentType: "application/vnd.trueapp",
Compressible: false,
},
"trig": {
ContentType: "application/trig",
Compressible: false,
},
"trm": {
ContentType: "application/x-msterminal",
Compressible: false,
},
"ts": {
ContentType: "video/mp2t",
Compressible: false,
},
"tsd": {
ContentType: "application/timestamped-data",
Compressible: false,
},
"tsv": {
ContentType: "text/tab-separated-values",
Compressible: false,
},
"ttc": {
ContentType: "font/collection",
Compressible: false,
},
"ttf": {
ContentType: "font/ttf",
Compressible: false,
},
"ttl": {
ContentType: "text/turtle",
Compressible: false,
},
"ttml": {
ContentType: "application/ttml+xml",
Compressible: false,
},
"twd": {
ContentType: "application/vnd.simtech-mindmapper",
Compressible: false,
},
"twds": {
ContentType: "application/vnd.simtech-mindmapper",
Compressible: false,
},
"txd": {
ContentType: "application/vnd.genomatix.tuxedo",
Compressible: false,
},
"txf": {
ContentType: "application/vnd.mobius.txf",
Compressible: false,
},
"txt": {
ContentType: "text/plain",
Compressible: false,
},
"u32": {
ContentType: "application/x-authorware-bin",
Compressible: false,
},
"u3d": {
ContentType: "model/u3d",
Compressible: false,
},
"u8dsn": {
ContentType: "message/global-delivery-status",
Compressible: false,
},
"u8hdr": {
ContentType: "message/global-headers",
Compressible: false,
},
"u8mdn": {
ContentType: "message/global-disposition-notification",
Compressible: false,
},
"u8msg": {
ContentType: "message/global",
Compressible: false,
},
"ubj": {
ContentType: "application/ubjson",
Compressible: false,
},
"udeb": {
ContentType: "application/x-debian-package",
Compressible: false,
},
"ufd": {
ContentType: "application/vnd.ufdl",
Compressible: false,
},
"ufdl": {
ContentType: "application/vnd.ufdl",
Compressible: false,
},
"ulx": {
ContentType: "application/x-glulx",
Compressible: false,
},
"umj": {
ContentType: "application/vnd.umajin",
Compressible: false,
},
"unityweb": {
ContentType: "application/vnd.unity",
Compressible: false,
},
"uo": {
ContentType: "application/vnd.uoml+xml",
Compressible: false,
},
"uoml": {
ContentType: "application/vnd.uoml+xml",
Compressible: false,
},
"uri": {
ContentType: "text/uri-list",
Compressible: false,
},
"uris": {
ContentType: "text/uri-list",
Compressible: false,
},
"urls": {
ContentType: "text/uri-list",
Compressible: false,
},
"usda": {
ContentType: "model/vnd.usda",
Compressible: false,
},
"usdz": {
ContentType: "model/vnd.usdz+zip",
Compressible: false,
},
"ustar": {
ContentType: "application/x-ustar",
Compressible: false,
},
"utz": {
ContentType: "application/vnd.uiq.theme",
Compressible: false,
},
"uu": {
ContentType: "text/x-uuencode",
Compressible: false,
},
"uva": {
ContentType: "audio/vnd.dece.audio",
Compressible: false,
},
"uvd": {
ContentType: "application/vnd.dece.data",
Compressible: false,
},
"uvf": {
ContentType: "application/vnd.dece.data",
Compressible: false,
},
"uvg": {
ContentType: "image/vnd.dece.graphic",
Compressible: false,
},
"uvh": {
ContentType: "video/vnd.dece.hd",
Compressible: false,
},
"uvi": {
ContentType: "image/vnd.dece.graphic",
Compressible: false,
},
"uvm": {
ContentType: "video/vnd.dece.mobile",
Compressible: false,
},
"uvp": {
ContentType: "video/vnd.dece.pd",
Compressible: false,
},
"uvs": {
ContentType: "video/vnd.dece.sd",
Compressible: false,
},
"uvt": {
ContentType: "application/vnd.dece.ttml+xml",
Compressible: false,
},
"uvu": {
ContentType: "video/vnd.uvvu.mp4",
Compressible: false,
},
"uvv": {
ContentType: "video/vnd.dece.video",
Compressible: false,
},
"uvva": {
ContentType: "audio/vnd.dece.audio",
Compressible: false,
},
"uvvd": {
ContentType: "application/vnd.dece.data",
Compressible: false,
},
"uvvf": {
ContentType: "application/vnd.dece.data",
Compressible: false,
},
"uvvg": {
ContentType: "image/vnd.dece.graphic",
Compressible: false,
},
"uvvh": {
ContentType: "video/vnd.dece.hd",
Compressible: false,
},
"uvvi": {
ContentType: "image/vnd.dece.graphic",
Compressible: false,
},
"uvvm": {
ContentType: "video/vnd.dece.mobile",
Compressible: false,
},
"uvvp": {
ContentType: "video/vnd.dece.pd",
Compressible: false,
},
"uvvs": {
ContentType: "video/vnd.dece.sd",
Compressible: false,
},
"uvvt": {
ContentType: "application/vnd.dece.ttml+xml",
Compressible: false,
},
"uvvu": {
ContentType: "video/vnd.uvvu.mp4",
Compressible: false,
},
"uvvv": {
ContentType: "video/vnd.dece.video",
Compressible: false,
},
"uvvx": {
ContentType: "application/vnd.dece.unspecified",
Compressible: false,
},
"uvvz": {
ContentType: "application/vnd.dece.zip",
Compressible: false,
},
"uvx": {
ContentType: "application/vnd.dece.unspecified",
Compressible: false,
},
"uvz": {
ContentType: "application/vnd.dece.zip",
Compressible: false,
},
"vbox": {
ContentType: "application/x-virtualbox-vbox",
Compressible: false,
},
"vbox-extpack": {
ContentType: "application/x-virtualbox-vbox-extpack",
Compressible: false,
},
"vcard": {
ContentType: "text/vcard",
Compressible: false,
},
"vcd": {
ContentType: "application/x-cdlink",
Compressible: false,
},
"vcf": {
ContentType: "text/x-vcard",
Compressible: false,
},
"vcg": {
ContentType: "application/vnd.groove-vcard",
Compressible: false,
},
"vcs": {
ContentType: "text/x-vcalendar",
Compressible: false,
},
"vcx": {
ContentType: "application/vnd.vcx",
Compressible: false,
},
"vdi": {
ContentType: "application/x-virtualbox-vdi",
Compressible: false,
},
"vds": {
ContentType: "model/vnd.sap.vds",
Compressible: false,
},
"vhd": {
ContentType: "application/x-virtualbox-vhd",
Compressible: false,
},
"vis": {
ContentType: "application/vnd.visionary",
Compressible: false,
},
"viv": {
ContentType: "video/vnd.vivo",
Compressible: false,
},
"vmdk": {
ContentType: "application/x-virtualbox-vmdk",
Compressible: false,
},
"vob": {
ContentType: "video/x-ms-vob",
Compressible: false,
},
"vor": {
ContentType: "application/vnd.stardivision.writer",
Compressible: false,
},
"vox": {
ContentType: "application/x-authorware-bin",
Compressible: false,
},
"vrml": {
ContentType: "model/vrml",
Compressible: false,
},
"vsd": {
ContentType: "application/vnd.visio",
Compressible: false,
},
"vsf": {
ContentType: "application/vnd.vsf",
Compressible: false,
},
"vss": {
ContentType: "application/vnd.visio",
Compressible: false,
},
"vst": {
ContentType: "application/vnd.visio",
Compressible: false,
},
"vsw": {
ContentType: "application/vnd.visio",
Compressible: false,
},
"vtf": {
ContentType: "image/vnd.valve.source.texture",
Compressible: false,
},
"vtt": {
ContentType: "text/vtt",
Compressible: false,
},
"vtu": {
ContentType: "model/vnd.vtu",
Compressible: false,
},
"vxml": {
ContentType: "application/voicexml+xml",
Compressible: false,
},
"w3d": {
ContentType: "application/x-director",
Compressible: false,
},
"wad": {
ContentType: "application/x-doom",
Compressible: false,
},
"wadl": {
ContentType: "application/vnd.sun.wadl+xml",
Compressible: false,
},
"war": {
ContentType: "application/java-archive",
Compressible: false,
},
"wasm": {
ContentType: "application/wasm",
Compressible: false,
},
"wav": {
ContentType: "audio/x-wav",
Compressible: false,
},
"wax": {
ContentType: "audio/x-ms-wax",
Compressible: false,
},
"wbmp": {
ContentType: "image/vnd.wap.wbmp",
Compressible: false,
},
"wbs": {
ContentType: "application/vnd.criticaltools.wbs+xml",
Compressible: false,
},
"wbxml": {
ContentType: "application/vnd.wap.wbxml",
Compressible: false,
},
"wcm": {
ContentType: "application/vnd.ms-works",
Compressible: false,
},
"wdb": {
ContentType: "application/vnd.ms-works",
Compressible: false,
},
"wdp": {
ContentType: "image/vnd.ms-photo",
Compressible: false,
},
"weba": {
ContentType: "audio/webm",
Compressible: false,
},
"webapp": {
ContentType: "application/x-web-app-manifest+json",
Compressible: false,
},
"webm": {
ContentType: "video/webm",
Compressible: false,
},
"webmanifest": {
ContentType: "application/manifest+json",
Compressible: false,
},
"webp": {
ContentType: "image/webp",
Compressible: false,
},
"wg": {
ContentType: "application/vnd.pmi.widget",
Compressible: false,
},
"wgsl": {
ContentType: "text/wgsl",
Compressible: false,
},
"wgt": {
ContentType: "application/widget",
Compressible: false,
},
"wif": {
ContentType: "application/watcherinfo+xml",
Compressible: false,
},
"wks": {
ContentType: "application/vnd.ms-works",
Compressible: false,
},
"wm": {
ContentType: "video/x-ms-wm",
Compressible: false,
},
"wma": {
ContentType: "audio/x-ms-wma",
Compressible: false,
},
"wmd": {
ContentType: "application/x-ms-wmd",
Compressible: false,
},
"wmf": {
ContentType: "image/wmf",
Compressible: false,
},
"wml": {
ContentType: "text/vnd.wap.wml",
Compressible: false,
},
"wmlc": {
ContentType: "application/vnd.wap.wmlc",
Compressible: false,
},
"wmls": {
ContentType: "text/vnd.wap.wmlscript",
Compressible: false,
},
"wmlsc": {
ContentType: "application/vnd.wap.wmlscriptc",
Compressible: false,
},
"wmv": {
ContentType: "video/x-ms-wmv",
Compressible: false,
},
"wmx": {
ContentType: "video/x-ms-wmx",
Compressible: false,
},
"wmz": {
ContentType: "application/x-msmetafile",
Compressible: false,
},
"woff": {
ContentType: "font/woff",
Compressible: false,
},
"woff2": {
ContentType: "font/woff2",
Compressible: false,
},
"wpd": {
ContentType: "application/vnd.wordperfect",
Compressible: false,
},
"wpl": {
ContentType: "application/vnd.ms-wpl",
Compressible: false,
},
"wps": {
ContentType: "application/vnd.ms-works",
Compressible: false,
},
"wqd": {
ContentType: "application/vnd.wqd",
Compressible: false,
},
"wri": {
ContentType: "application/x-mswrite",
Compressible: false,
},
"wrl": {
ContentType: "model/vrml",
Compressible: false,
},
"wsc": {
ContentType: "message/vnd.wfa.wsc",
Compressible: false,
},
"wsdl": {
ContentType: "application/wsdl+xml",
Compressible: false,
},
"wspolicy": {
ContentType: "application/wspolicy+xml",
Compressible: false,
},
"wtb": {
ContentType: "application/vnd.webturbo",
Compressible: false,
},
"wvx": {
ContentType: "video/x-ms-wvx",
Compressible: false,
},
"x32": {
ContentType: "application/x-authorware-bin",
Compressible: false,
},
"x3d": {
ContentType: "model/x3d+xml",
Compressible: false,
},
"x3db": {
ContentType: "model/x3d+fastinfoset",
Compressible: false,
},
"x3dbz": {
ContentType: "model/x3d+binary",
Compressible: false,
},
"x3dv": {
ContentType: "model/x3d-vrml",
Compressible: false,
},
"x3dvz": {
ContentType: "model/x3d+vrml",
Compressible: false,
},
"x3dz": {
ContentType: "model/x3d+xml",
Compressible: false,
},
"x_b": {
ContentType: "model/vnd.parasolid.transmit.binary",
Compressible: false,
},
"x_t": {
ContentType: "model/vnd.parasolid.transmit.text",
Compressible: false,
},
"xaml": {
ContentType: "application/xaml+xml",
Compressible: false,
},
"xap": {
ContentType: "application/x-silverlight-app",
Compressible: false,
},
"xar": {
ContentType: "application/vnd.xara",
Compressible: false,
},
"xav": {
ContentType: "application/xcap-att+xml",
Compressible: false,
},
"xbap": {
ContentType: "application/x-ms-xbap",
Compressible: false,
},
"xbd": {
ContentType: "application/vnd.fujixerox.docuworks.binder",
Compressible: false,
},
"xbm": {
ContentType: "image/x-xbitmap",
Compressible: false,
},
"xca": {
ContentType: "application/xcap-caps+xml",
Compressible: false,
},
"xcs": {
ContentType: "application/calendar+xml",
Compressible: false,
},
"xdf": {
ContentType: "application/xcap-diff+xml",
Compressible: false,
},
"xdm": {
ContentType: "application/vnd.syncml.dm+xml",
Compressible: false,
},
"xdp": {
ContentType: "application/vnd.adobe.xdp+xml",
Compressible: false,
},
"xdssc": {
ContentType: "application/dssc+xml",
Compressible: false,
},
"xdw": {
ContentType: "application/vnd.fujixerox.docuworks",
Compressible: false,
},
"xel": {
ContentType: "application/xcap-el+xml",
Compressible: false,
},
"xenc": {
ContentType: "application/xenc+xml",
Compressible: false,
},
"xer": {
ContentType: "application/patch-ops-error+xml",
Compressible: false,
},
"xfdf": {
ContentType: "application/xfdf",
Compressible: false,
},
"xfdl": {
ContentType: "application/vnd.xfdl",
Compressible: false,
},
"xht": {
ContentType: "application/xhtml+xml",
Compressible: false,
},
"xhtm": {
ContentType: "application/vnd.pwg-xhtml-print+xml",
Compressible: false,
},
"xhtml": {
ContentType: "application/xhtml+xml",
Compressible: false,
},
"xhvml": {
ContentType: "application/xv+xml",
Compressible: false,
},
"xif": {
ContentType: "image/vnd.xiff",
Compressible: false,
},
"xla": {
ContentType: "application/vnd.ms-excel",
Compressible: false,
},
"xlam": {
ContentType: "application/vnd.ms-excel.addin.macroenabled.12",
Compressible: false,
},
"xlc": {
ContentType: "application/vnd.ms-excel",
Compressible: false,
},
"xlf": {
ContentType: "application/xliff+xml",
Compressible: false,
},
"xlm": {
ContentType: "application/vnd.ms-excel",
Compressible: false,
},
"xls": {
ContentType: "application/vnd.ms-excel",
Compressible: false,
},
"xlsb": {
ContentType: "application/vnd.ms-excel.sheet.binary.macroenabled.12",
Compressible: false,
},
"xlsm": {
ContentType: "application/vnd.ms-excel.sheet.macroenabled.12",
Compressible: false,
},
"xlsx": {
ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
Compressible: false,
},
"xlt": {
ContentType: "application/vnd.ms-excel",
Compressible: false,
},
"xltm": {
ContentType: "application/vnd.ms-excel.template.macroenabled.12",
Compressible: false,
},
"xltx": {
ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
Compressible: false,
},
"xlw": {
ContentType: "application/vnd.ms-excel",
Compressible: false,
},
"xm": {
ContentType: "audio/xm",
Compressible: false,
},
"xml": {
ContentType: "text/xml",
Compressible: false,
},
"xns": {
ContentType: "application/xcap-ns+xml",
Compressible: false,
},
"xo": {
ContentType: "application/vnd.olpc-sugar",
Compressible: false,
},
"xop": {
ContentType: "application/xop+xml",
Compressible: false,
},
"xpi": {
ContentType: "application/x-xpinstall",
Compressible: false,
},
"xpl": {
ContentType: "application/xproc+xml",
Compressible: false,
},
"xpm": {
ContentType: "image/x-xpixmap",
Compressible: false,
},
"xpr": {
ContentType: "application/vnd.is-xpr",
Compressible: false,
},
"xps": {
ContentType: "application/vnd.ms-xpsdocument",
Compressible: false,
},
"xpw": {
ContentType: "application/vnd.intercon.formnet",
Compressible: false,
},
"xpx": {
ContentType: "application/vnd.intercon.formnet",
Compressible: false,
},
"xsd": {
ContentType: "application/xml",
Compressible: false,
},
"xsf": {
ContentType: "application/prs.xsf+xml",
Compressible: false,
},
"xsl": {
ContentType: "application/xslt+xml",
Compressible: false,
},
"xslt": {
ContentType: "application/xslt+xml",
Compressible: false,
},
"xsm": {
ContentType: "application/vnd.syncml+xml",
Compressible: false,
},
"xspf": {
ContentType: "application/xspf+xml",
Compressible: false,
},
"xul": {
ContentType: "application/vnd.mozilla.xul+xml",
Compressible: false,
},
"xvm": {
ContentType: "application/xv+xml",
Compressible: false,
},
"xvml": {
ContentType: "application/xv+xml",
Compressible: false,
},
"xwd": {
ContentType: "image/x-xwindowdump",
Compressible: false,
},
"xyz": {
ContentType: "chemical/x-xyz",
Compressible: false,
},
"xz": {
ContentType: "application/x-xz",
Compressible: false,
},
"yaml": {
ContentType: "text/yaml",
Compressible: false,
},
"yang": {
ContentType: "application/yang",
Compressible: false,
},
"yin": {
ContentType: "application/yin+xml",
Compressible: false,
},
"yml": {
ContentType: "text/yaml",
Compressible: false,
},
"ymp": {
ContentType: "text/x-suse-ymp",
Compressible: false,
},
"z1": {
ContentType: "application/x-zmachine",
Compressible: false,
},
"z2": {
ContentType: "application/x-zmachine",
Compressible: false,
},
"z3": {
ContentType: "application/x-zmachine",
Compressible: false,
},
"z4": {
ContentType: "application/x-zmachine",
Compressible: false,
},
"z5": {
ContentType: "application/x-zmachine",
Compressible: false,
},
"z6": {
ContentType: "application/x-zmachine",
Compressible: false,
},
"z7": {
ContentType: "application/x-zmachine",
Compressible: false,
},
"z8": {
ContentType: "application/x-zmachine",
Compressible: false,
},
"zaz": {
ContentType: "application/vnd.zzazz.deck+xml",
Compressible: false,
},
"zip": {
ContentType: "application/zip",
Compressible: false,
},
"zir": {
ContentType: "application/vnd.zul",
Compressible: false,
},
"zirz": {
ContentType: "application/vnd.zul",
Compressible: false,
},
"zmm": {
ContentType: "application/vnd.handheld-entertainment+xml",
Compressible: false,
},
}
golang-github-minio-pkg-3.0.10/mimedb/db_test.go 0000664 0000000 0000000 00000003164 14655770405 0021443 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package mimedb
import "testing"
func TestMimeLookup(t *testing.T) {
// Test mimeLookup.
contentType := DB["txt"].ContentType
if contentType != "text/plain" {
t.Fatalf("Invalid content type are found expected \"application/x-msdownload\", got %s", contentType)
}
compressible := DB["txt"].Compressible
if compressible {
t.Fatalf("Invalid content type are found expected \"false\", got %t", compressible)
}
}
func TestTypeByExtension(t *testing.T) {
// Test TypeByExtension.
contentType := TypeByExtension(".txt")
if contentType != "text/plain" {
t.Fatalf("Invalid content type are found expected \"text/plain\", got %s", contentType)
}
// Test non-existent type resolution
contentType = TypeByExtension(".abc")
if contentType != "application/octet-stream" {
t.Fatalf("Invalid content type are found expected \"application/octet-stream\", got %s", contentType)
}
}
golang-github-minio-pkg-3.0.10/mimedb/resolve-db.go 0000664 0000000 0000000 00000002216 14655770405 0022056 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package mimedb
import (
"strings"
)
// TypeByExtension resolves the extension to its respective content-type.
func TypeByExtension(ext string) string {
// Set default to "application/octet-stream".
contentType := "application/octet-stream"
if ext != "" {
if content, ok := DB[strings.ToLower(strings.TrimPrefix(ext, "."))]; ok {
contentType = content.ContentType
}
}
return contentType
}
golang-github-minio-pkg-3.0.10/mimedb/util/ 0000775 0000000 0000000 00000000000 14655770405 0020441 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/mimedb/util/gen-db.go 0000664 0000000 0000000 00000010325 14655770405 0022125 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
// Package mimedb is a database of file extension to mime content-type.
// Definitions are imported from NodeJS mime-db project under MIT license.
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"
"text/template"
)
const progTempl = `// DO NOT EDIT THIS FILE. IT IS AUTO-GENERATED BY "gen-db.go".
// Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
// Package mimedb is a database of file extension to mime content-type.
// Definitions are imported from NodeJS mime-db project under MIT license.
package mimedb
// DB - Mime is a collection of mime types with extension as key and content-type as value.
var DB = map[string]struct {
ContentType string
Compressible bool
}{
{{range $extension, $entry := . }} "{{$extension}}": {
ContentType: "{{$entry.ContentType}}",
Compressible: {{$entry.Compressible}},
},
{{end}}}
`
type mimeEntry struct {
ContentType string `json:"contentType"`
Compressible bool `json:"compresible"`
}
type mimeDB map[string]mimeEntry
// JSON data from gobindata and parse them into extDB.
func convertDB(jsonFile string) (mimeDB, error) {
// Structure of JSON data from mime-db project.
type dbEntry struct {
Source string `json:"source"`
Compressible bool `json:"compresible"`
Extensions []string `json:"extensions"`
}
// Access embedded "db.json" inside go-bindata.
jsonDB, err := ioutil.ReadFile(jsonFile)
if err != nil {
return nil, err
}
// Convert db.json into go's typed structure.
db := make(map[string]dbEntry)
if err := json.Unmarshal(jsonDB, &db); err != nil {
return nil, err
}
mDB := make(mimeDB)
// Generate a new database from mime-db.
for key, val := range db {
if len(val.Extensions) > 0 {
/* Denormalize - each extension has its own
unique content-type now. Looks will be fast. */
for _, ext := range val.Extensions {
/* Single extension type may map to
multiple content-types. In that case,
simply prefer the longest content-type
to maintain some level of
consistency. Only guarantee is,
whatever content type is assigned, it
is appropriate and valid type. */
if strings.Compare(mDB[ext].ContentType, key) < 0 {
mDB[ext] = mimeEntry{
ContentType: key,
Compressible: val.Compressible,
}
}
}
}
}
return mDB, nil
}
func main() {
// Take input json file from command-line".
if len(os.Args) != 2 {
fmt.Print("Syntax:\n\tgen-db /path/to/db.json\n")
os.Exit(1)
}
// Load and convert db.json into new database with extension
// as key.
mDB, err := convertDB(os.Args[1])
if err != nil {
panic(err)
}
// Generate db embedded go program.
tmpl := template.New("mimedb")
mimeTmpl, err := tmpl.Parse(progTempl)
if err != nil {
panic(err)
}
err = mimeTmpl.Execute(os.Stdout, mDB)
if err != nil {
panic(err)
}
}
golang-github-minio-pkg-3.0.10/net/ 0000775 0000000 0000000 00000000000 14655770405 0017015 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/net/health.go 0000664 0000000 0000000 00000006622 14655770405 0020617 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package net
import (
"github.com/minio/madmin-go/v3"
"github.com/montanaflynn/stats"
)
// ComputePerfStats takes arrays of Latency & Throughput to compute Statistics
func ComputePerfStats(latencies, throughputs []float64) (madmin.NetLatency, madmin.NetThroughput, error) {
var avgLatency float64
var percentile50Latency float64
var percentile90Latency float64
var percentile99Latency float64
var minLatency float64
var maxLatency float64
var avgThroughput float64
var percentile50Throughput float64
var percentile90Throughput float64
var percentile99Throughput float64
var minThroughput float64
var maxThroughput float64
var err error
if avgLatency, err = stats.Mean(latencies); err != nil {
return madmin.NetLatency{}, madmin.NetThroughput{}, err
}
if percentile50Latency, err = stats.Percentile(latencies, 50); err != nil {
return madmin.NetLatency{}, madmin.NetThroughput{}, err
}
if percentile90Latency, err = stats.Percentile(latencies, 90); err != nil {
return madmin.NetLatency{}, madmin.NetThroughput{}, err
}
if percentile99Latency, err = stats.Percentile(latencies, 99); err != nil {
return madmin.NetLatency{}, madmin.NetThroughput{}, err
}
if maxLatency, err = stats.Max(latencies); err != nil {
return madmin.NetLatency{}, madmin.NetThroughput{}, err
}
if minLatency, err = stats.Min(latencies); err != nil {
return madmin.NetLatency{}, madmin.NetThroughput{}, err
}
l := madmin.NetLatency{
Avg: avgLatency,
Percentile50: percentile50Latency,
Percentile90: percentile90Latency,
Percentile99: percentile99Latency,
Min: minLatency,
Max: maxLatency,
}
if avgThroughput, err = stats.Mean(throughputs); err != nil {
return madmin.NetLatency{}, madmin.NetThroughput{}, err
}
if percentile50Throughput, err = stats.Percentile(throughputs, 50); err != nil {
return madmin.NetLatency{}, madmin.NetThroughput{}, err
}
if percentile90Throughput, err = stats.Percentile(throughputs, 90); err != nil {
return madmin.NetLatency{}, madmin.NetThroughput{}, err
}
if percentile99Throughput, err = stats.Percentile(throughputs, 99); err != nil {
return madmin.NetLatency{}, madmin.NetThroughput{}, err
}
if maxThroughput, err = stats.Max(throughputs); err != nil {
return madmin.NetLatency{}, madmin.NetThroughput{}, err
}
if minThroughput, err = stats.Min(throughputs); err != nil {
return madmin.NetLatency{}, madmin.NetThroughput{}, err
}
t := madmin.NetThroughput{
Avg: avgThroughput,
Percentile50: percentile50Throughput,
Percentile90: percentile90Throughput,
Percentile99: percentile99Throughput,
Min: minThroughput,
Max: maxThroughput,
}
return l, t, nil
}
golang-github-minio-pkg-3.0.10/net/host.go 0000664 0000000 0000000 00000010010 14655770405 0020311 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package net
import (
"encoding/json"
"errors"
"net"
"regexp"
"strings"
)
var hostLabelRegexp = regexp.MustCompile("^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$")
// Host - holds network host IP/name and its port.
type Host struct {
Name string
Port Port
IsPortSet bool
}
// IsEmpty - returns whether Host is empty or not
func (host Host) IsEmpty() bool {
return host.Name == ""
}
// String - returns string representation of Host.
func (host Host) String() string {
if !host.IsPortSet {
return host.Name
}
return net.JoinHostPort(host.Name, host.Port.String())
}
// Equal - checks whether given host is equal or not.
func (host Host) Equal(compHost Host) bool {
return host.String() == compHost.String()
}
// MarshalJSON - converts Host into JSON data
func (host Host) MarshalJSON() ([]byte, error) {
return json.Marshal(host.String())
}
// UnmarshalJSON - parses data into Host.
func (host *Host) UnmarshalJSON(data []byte) (err error) {
var s string
if err = json.Unmarshal(data, &s); err != nil {
return err
}
// Allow empty string
if s == "" {
*host = Host{}
return nil
}
var h *Host
if h, err = ParseHost(s); err != nil {
return err
}
*host = *h
return nil
}
// ParseHost - parses string into Host
func ParseHost(s string) (*Host, error) {
if s == "" {
return nil, errors.New("invalid argument")
}
isValidHost := func(host string) bool {
if host == "" {
return true
}
if ip := net.ParseIP(host); ip != nil {
return true
}
// host is not a valid IPv4 or IPv6 address
// host may be a hostname
// refer https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names
// why checks are done like below
if len(host) < 1 || len(host) > 253 {
return false
}
labels := strings.Split(host, ".")
for i, label := range labels {
if i+1 == len(labels) && label == "" {
// Allow only `.` at the end of the FQDN
// such as 'test.example.com.' should be
// treated similar to 'test.example.com'
continue
}
if len(label) < 1 || len(label) > 63 {
return false
}
if !hostLabelRegexp.MatchString(label) {
return false
}
}
return true
}
var port Port
var isPortSet bool
host, portStr, err := net.SplitHostPort(s)
if err != nil {
if !strings.Contains(err.Error(), "missing port in address") {
return nil, err
}
host = s
} else {
if port, err = ParsePort(portStr); err != nil {
return nil, err
}
isPortSet = true
}
if host != "" {
host, err = trimIPv6(host)
if err != nil {
return nil, err
}
}
// IPv6 requires a link-local address on every network interface.
// `%interface` should be preserved.
trimmedHost := host
if i := strings.LastIndex(trimmedHost, "%"); i > -1 {
// `%interface` can be skipped for validity check though.
trimmedHost = trimmedHost[:i]
}
if !isValidHost(trimmedHost) {
return nil, errors.New("invalid hostname")
}
return &Host{
Name: host,
Port: port,
IsPortSet: isPortSet,
}, nil
}
// IPv6 can be embedded with square brackets.
func trimIPv6(host string) (string, error) {
// `missing ']' in host` error is already handled in `SplitHostPort`
if host[len(host)-1] == ']' {
if host[0] != '[' {
return "", errors.New("missing '[' in host")
}
return host[1:][:len(host)-2], nil
}
return host, nil
}
golang-github-minio-pkg-3.0.10/net/host_test.go 0000664 0000000 0000000 00000020434 14655770405 0021363 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package net
import (
"reflect"
"testing"
)
func TestHostIsEmpty(t *testing.T) {
testCases := []struct {
host Host
expectedResult bool
}{
{Host{"", 0, false}, true},
{Host{"", 0, true}, true},
{Host{"play", 9000, false}, false},
{Host{"play", 9000, true}, false},
}
for i, testCase := range testCases {
result := testCase.host.IsEmpty()
if result != testCase.expectedResult {
t.Fatalf("test %v: result: expected: %v, got: %v", i+1, testCase.expectedResult, result)
}
}
}
func TestHostString(t *testing.T) {
testCases := []struct {
host Host
expectedStr string
}{
{Host{"", 0, false}, ""},
{Host{"", 0, true}, ":0"},
{Host{"play", 9000, false}, "play"},
{Host{"play", 9000, true}, "play:9000"},
}
for i, testCase := range testCases {
str := testCase.host.String()
if str != testCase.expectedStr {
t.Fatalf("test %v: string: expected: %v, got: %v", i+1, testCase.expectedStr, str)
}
}
}
func TestHostEqual(t *testing.T) {
testCases := []struct {
host Host
compHost Host
expectedResult bool
}{
{Host{"", 0, false}, Host{"", 0, true}, false},
{Host{"play", 9000, true}, Host{"play", 9000, false}, false},
{Host{"", 0, true}, Host{"", 0, true}, true},
{Host{"play", 9000, false}, Host{"play", 9000, false}, true},
{Host{"play", 9000, true}, Host{"play", 9000, true}, true},
}
for i, testCase := range testCases {
result := testCase.host.Equal(testCase.compHost)
if result != testCase.expectedResult {
t.Fatalf("test %v: string: expected: %v, got: %v", i+1, testCase.expectedResult, result)
}
}
}
func TestHostMarshalJSON(t *testing.T) {
testCases := []struct {
host Host
expectedData []byte
expectErr bool
}{
{Host{}, []byte(`""`), false},
{Host{"play", 0, false}, []byte(`"play"`), false},
{Host{"play", 0, true}, []byte(`"play:0"`), false},
{Host{"play", 9000, true}, []byte(`"play:9000"`), false},
{Host{"play.min.io", 0, false}, []byte(`"play.min.io"`), false},
{Host{"play.min.io", 9000, true}, []byte(`"play.min.io:9000"`), false},
{Host{"147.75.201.93", 0, false}, []byte(`"147.75.201.93"`), false},
{Host{"147.75.201.93", 9000, true}, []byte(`"147.75.201.93:9000"`), false},
{Host{"play12", 0, false}, []byte(`"play12"`), false},
{Host{"12play", 0, false}, []byte(`"12play"`), false},
{Host{"play-minio-io", 0, false}, []byte(`"play-minio-io"`), false},
{Host{"play--min.io", 0, false}, []byte(`"play--min.io"`), false},
}
for i, testCase := range testCases {
data, err := testCase.host.MarshalJSON()
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("test %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if !reflect.DeepEqual(data, testCase.expectedData) {
t.Fatalf("test %v: data: expected: %v, got: %v", i+1, string(testCase.expectedData), string(data))
}
}
}
}
func TestHostUnmarshalJSON(t *testing.T) {
testCases := []struct {
data []byte
expectedHost *Host
expectErr bool
}{
{[]byte(`""`), &Host{}, false},
{[]byte(`"play"`), &Host{"play", 0, false}, false},
{[]byte(`"play:0"`), &Host{"play", 0, true}, false},
{[]byte(`"play:9000"`), &Host{"play", 9000, true}, false},
{[]byte(`"play.min.io"`), &Host{"play.min.io", 0, false}, false},
{[]byte(`"play.min.io:9000"`), &Host{"play.min.io", 9000, true}, false},
{[]byte(`"147.75.201.93"`), &Host{"147.75.201.93", 0, false}, false},
{[]byte(`"147.75.201.93:9000"`), &Host{"147.75.201.93", 9000, true}, false},
{[]byte(`"play12"`), &Host{"play12", 0, false}, false},
{[]byte(`"12play"`), &Host{"12play", 0, false}, false},
{[]byte(`"play-minio-io"`), &Host{"play-minio-io", 0, false}, false},
{[]byte(`"play--min.io"`), &Host{"play--min.io", 0, false}, false},
{[]byte(`":9000"`), &Host{"", 9000, true}, false},
{[]byte(`"[fe80::8097:76eb:b397:e067%wlp2s0]"`), &Host{"fe80::8097:76eb:b397:e067%wlp2s0", 0, false}, false},
{[]byte(`"[fe80::8097:76eb:b397:e067]:9000"`), &Host{"fe80::8097:76eb:b397:e067", 9000, true}, false},
{[]byte(`"fe80::8097:76eb:b397:e067%wlp2s0"`), nil, true},
{[]byte(`"fe80::8097:76eb:b397:e067%wlp2s0]"`), nil, true},
{[]byte(`"[fe80::8097:76eb:b397:e067%wlp2s0"`), nil, true},
{[]byte(`"[[fe80::8097:76eb:b397:e067%wlp2s0]]"`), nil, true},
{[]byte(`"[[fe80::8097:76eb:b397:e067%wlp2s0"`), nil, true},
{[]byte(`"play:"`), nil, true},
{[]byte(`"play::"`), nil, true},
{[]byte(`"play:90000"`), nil, true},
{[]byte(`"play:-10"`), nil, true},
{[]byte(`"play-"`), nil, true},
{[]byte(`"play.minio..io"`), nil, true},
{[]byte(`":"`), nil, true},
}
for _, testCase := range testCases {
testCase := testCase
t.Run("", func(t *testing.T) {
var host Host
err := host.UnmarshalJSON(testCase.data)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Errorf("error: expected: %v, got: %v", testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if !reflect.DeepEqual(&host, testCase.expectedHost) {
t.Errorf("host: expected: %#v, got: %#v", testCase.expectedHost, host)
}
}
})
}
}
func TestParseHost(t *testing.T) {
testCases := []struct {
s string
expectedHost *Host
expectErr bool
}{
{"play", &Host{"play", 0, false}, false},
{"play:0", &Host{"play", 0, true}, false},
{"play:9000", &Host{"play", 9000, true}, false},
{"play.min.io", &Host{"play.min.io", 0, false}, false},
{"play.min.io:9000", &Host{"play.min.io", 9000, true}, false},
{"play.min.io.:9000", &Host{"play.min.io.", 9000, true}, false},
{"147.75.201.93", &Host{"147.75.201.93", 0, false}, false},
{"147.75.201.93:9000", &Host{"147.75.201.93", 9000, true}, false},
{"play12", &Host{"play12", 0, false}, false},
{"12play", &Host{"12play", 0, false}, false},
{"play-minio-io", &Host{"play-minio-io", 0, false}, false},
{"play--min.io", &Host{"play--min.io", 0, false}, false},
{"play--min.io.", &Host{"play--min.io.", 0, false}, false},
{":9000", &Host{"", 9000, true}, false},
{".play.min.io.:9000", nil, true},
{".play.min.io:9000", nil, true},
{"play-.-min.io", nil, true},
{"play:", nil, true},
{"play::", nil, true},
{"play:90000", nil, true},
{"play:-10", nil, true},
{"play-", nil, true},
{"play.minio..io", nil, true},
{":", nil, true},
{"", nil, true},
}
for _, testCase := range testCases {
testCase := testCase
t.Run("", func(t *testing.T) {
host, err := ParseHost(testCase.s)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Errorf("error: expected: %v, got: %v", testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if !reflect.DeepEqual(host, testCase.expectedHost) {
t.Errorf("host: expected: %#v, got: %#v", testCase.expectedHost, host)
}
}
})
}
}
func TestTrimIPv6(t *testing.T) {
testCases := []struct {
IP string
expectedIP string
expectErr bool
}{
{"[fe80::8097:76eb:b397:e067%wlp2s0]", "fe80::8097:76eb:b397:e067%wlp2s0", false},
{"fe80::8097:76eb:b397:e067%wlp2s0]", "fe80::8097:76eb:b397:e067%wlp2s0", true},
{"[fe80::8097:76eb:b397:e067%wlp2s0]]", "fe80::8097:76eb:b397:e067%wlp2s0]", false},
{"[[fe80::8097:76eb:b397:e067%wlp2s0]]", "[fe80::8097:76eb:b397:e067%wlp2s0]", false},
}
for i, testCase := range testCases {
ip, err := trimIPv6(testCase.IP)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("test %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if ip != testCase.expectedIP {
t.Fatalf("test %v: IP: expected: %#v, got: %#v", i+1, testCase.expectedIP, ip)
}
}
}
}
golang-github-minio-pkg-3.0.10/net/port.go 0000664 0000000 0000000 00000003256 14655770405 0020336 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package net
import (
"errors"
"net"
"strconv"
)
// Port - network port
type Port uint16
// String - returns string representation of port.
func (p Port) String() string {
return strconv.Itoa(int(p))
}
// GetFreePort asks the kernel for a free open port that is ready to use.
func GetFreePort() (Port, error) {
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
if err != nil {
return 0, err
}
l, err := net.ListenTCP("tcp", addr)
if err != nil {
return 0, err
}
defer l.Close()
return Port(l.Addr().(*net.TCPAddr).Port), nil
}
// ParsePort - parses string into Port
func ParsePort(s string) (p Port, err error) {
if s == "https" {
return Port(443), nil
} else if s == "http" {
return Port(80), nil
}
var i int
if i, err = strconv.Atoi(s); err != nil {
return p, errors.New("invalid port number")
}
if i < 0 || i > 65535 {
return p, errors.New("port must be between 0 to 65535")
}
return Port(i), nil
}
golang-github-minio-pkg-3.0.10/net/port_test.go 0000664 0000000 0000000 00000003675 14655770405 0021402 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package net
import (
"testing"
)
func TestPortString(t *testing.T) {
testCases := []struct {
port Port
expectedStr string
}{
{Port(0), "0"},
{Port(9000), "9000"},
{Port(65535), "65535"},
{Port(1024), "1024"},
}
for i, testCase := range testCases {
str := testCase.port.String()
if str != testCase.expectedStr {
t.Fatalf("test %v: error: port: %v, got: %v", i+1, testCase.expectedStr, str)
}
}
}
func TestParsePort(t *testing.T) {
testCases := []struct {
s string
expectedPort Port
expectErr bool
}{
{"0", Port(0), false},
{"9000", Port(9000), false},
{"65535", Port(65535), false},
{"http", Port(80), false},
{"https", Port(443), false},
{"90000", Port(0), true},
{"-10", Port(0), true},
{"", Port(0), true},
{" 1024", Port(0), true},
}
for i, testCase := range testCases {
port, err := ParsePort(testCase.s)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("test %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if port != testCase.expectedPort {
t.Fatalf("test %v: error: port: %v, got: %v", i+1, testCase.expectedPort, port)
}
}
}
}
golang-github-minio-pkg-3.0.10/net/url.go 0000664 0000000 0000000 00000015636 14655770405 0020161 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package net
import (
"context"
"encoding/json"
"errors"
"fmt"
"net"
"net/url"
"os"
"path"
"strings"
"syscall"
)
// URL - improved JSON friendly url.URL.
type URL url.URL
// IsEmpty - checks URL is empty or not.
func (u URL) IsEmpty() bool {
return u.String() == ""
}
// validOptionalPort reports whether port is either an empty string
// or matches /^:\d*$/
func validOptionalPort(port string) bool {
if port == "" {
return true
}
if port[0] != ':' {
return false
}
for _, b := range port[1:] {
if b < '0' || b > '9' {
return false
}
}
return true
}
// splitHostPort separates host and port. If the port is not valid, it returns
// the entire input as host, and it doesn't check the validity of the host.
// Unlike net.SplitHostPort, but per RFC 3986, it requires ports to be numeric.
func splitHostPort(hostPort string) (host, port string) {
host = hostPort
colon := strings.LastIndexByte(host, ':')
if colon != -1 && validOptionalPort(host[colon:]) {
host, port = host[:colon], host[colon+1:]
}
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
host = host[1 : len(host)-1]
}
return
}
// Hostname returns u.Host, stripping any valid port number if present
//
// If the result is enclosed in square brackets, as literal IPv6 addresses are,
// the square brackets are removed from the result.
func (u URL) Hostname() string {
host, _ := splitHostPort(u.Host)
return host
}
// Port returns the port part of u.Host, without the leading colon.
//
// If u.Host doesn't contain a valid numeric port, Port returns an empty string.
func (u URL) Port() string {
_, port := splitHostPort(u.Host)
return port
}
// String - returns string representation of URL.
func (u URL) String() string {
// if port number 80 and 443, remove for http and https scheme respectively
if u.Host != "" {
host, err := ParseHost(u.Host)
if err != nil {
panic(err)
}
switch {
case u.Scheme == "http" && host.Port == 80:
fallthrough
case u.Scheme == "https" && host.Port == 443:
u.Host = host.Name
}
}
uu := url.URL(u)
return uu.String()
}
// MarshalJSON - converts to JSON string data.
func (u URL) MarshalJSON() ([]byte, error) {
return json.Marshal(u.String())
}
// UnmarshalJSON - parses given data into URL.
func (u *URL) UnmarshalJSON(data []byte) (err error) {
var s string
if err = json.Unmarshal(data, &s); err != nil {
return err
}
// Allow empty string
if s == "" {
*u = URL{}
return nil
}
var ru *URL
if ru, err = ParseURL(s); err != nil {
return err
}
*u = *ru
return nil
}
// ParseHTTPURL - parses a string into HTTP URL, string is
// expected to be of form http:// or https://
func ParseHTTPURL(s string) (u *URL, err error) {
u, err = ParseURL(s)
if err != nil {
return nil, err
}
switch u.Scheme {
default:
return nil, fmt.Errorf("unexpected scheme found %s", u.Scheme)
case "http", "https":
return u, nil
}
}
// ParseURL - parses string into URL.
func ParseURL(s string) (u *URL, err error) {
var uu *url.URL
if uu, err = url.Parse(s); err != nil {
return nil, err
}
if uu.Hostname() == "" {
if uu.Scheme != "" {
return nil, errors.New("scheme appears with empty host")
}
} else {
portStr := uu.Port()
if portStr == "" {
switch uu.Scheme {
case "http":
portStr = "80"
case "https":
portStr = "443"
}
}
if _, err = ParseHost(net.JoinHostPort(uu.Hostname(), portStr)); err != nil {
return nil, err
}
}
// Clean path in the URL.
// Note: path.Clean() is used on purpose because in MS Windows filepath.Clean() converts
// `/` into `\` ie `/foo` becomes `\foo`
if uu.Path != "" {
uu.Path = path.Clean(uu.Path)
}
// path.Clean removes the trailing '/' and converts '//' to '/'.
if strings.HasSuffix(s, "/") && !strings.HasSuffix(uu.Path, "/") {
uu.Path += "/"
}
v := URL(*uu)
u = &v
return u, nil
}
// IsNetworkOrHostDown - if there was a network error or if the host is down.
// expectTimeouts indicates that *context* timeouts are expected and does not
// indicate a downed host. Other timeouts still returns down.
func IsNetworkOrHostDown(err error, expectTimeouts bool) bool {
if err == nil {
return false
}
if errors.Is(err, context.Canceled) {
return false
}
if expectTimeouts && errors.Is(err, context.DeadlineExceeded) {
return false
}
// We need to figure if the error either a timeout
// or a non-temporary error.
urlErr := &url.Error{}
if errors.As(err, &urlErr) {
switch urlErr.Err.(type) {
case *net.DNSError, *net.OpError, net.UnknownNetworkError:
return true
}
}
var e net.Error
if errors.As(err, &e) {
if e.Timeout() {
return true
}
}
// If write to an closed connection, It will make this error
opErr := &net.OpError{}
if errors.As(err, &opErr) {
if opErr.Op == "write" && opErr.Net == "tcp" {
if es, ok := opErr.Err.(*os.SyscallError); ok && es.Syscall == "wsasend" {
return true
}
}
}
// Fallback to other mechanisms.
switch {
case strings.Contains(err.Error(), "Connection closed by foreign host"):
return true
case strings.Contains(err.Error(), "TLS handshake timeout"):
// If error is - tlsHandshakeTimeoutError.
return true
case strings.Contains(err.Error(), "i/o timeout"):
// If error is - tcp timeoutError.
return true
case strings.Contains(err.Error(), "connection timed out"):
// If err is a net.Dial timeout.
return true
case strings.Contains(err.Error(), "connection reset by peer"):
// IF err is a peer reset on a socket.
return true
case strings.Contains(err.Error(), "broken pipe"):
// IF err is a broken pipe on a socket.
return true
case strings.Contains(strings.ToLower(err.Error()), "503 service unavailable"):
// Denial errors
return true
}
return false
}
// IsConnResetErr - Checks for "connection reset" errors.
func IsConnResetErr(err error) bool {
if strings.Contains(err.Error(), "connection reset by peer") {
return true
}
// incase if error message is wrapped.
return errors.Is(err, syscall.ECONNRESET)
}
// IsConnRefusedErr - Checks for "connection refused" errors.
func IsConnRefusedErr(err error) bool {
if strings.Contains(err.Error(), "connection refused") {
return true
}
// incase if error message is wrapped.
return errors.Is(err, syscall.ECONNREFUSED)
}
golang-github-minio-pkg-3.0.10/net/url_test.go 0000664 0000000 0000000 00000022220 14655770405 0021203 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package net
import (
"reflect"
"testing"
)
func TestURLHostnameAndPort(t *testing.T) {
tests := []struct {
in string // URL.Host field
host string
port string
}{
{"foo.com:80", "foo.com", "80"},
{"foo.com", "foo.com", ""},
{"foo.com:", "foo.com", ""},
{"FOO.COM", "FOO.COM", ""}, // no canonicalization
{"1.2.3.4", "1.2.3.4", ""},
{"1.2.3.4:80", "1.2.3.4", "80"},
{"[1:2:3:4]", "1:2:3:4", ""},
{"[1:2:3:4]:80", "1:2:3:4", "80"},
{"[::1]:80", "::1", "80"},
{"[::1]", "::1", ""},
{"[::1]:", "::1", ""},
{"localhost", "localhost", ""},
{"localhost:443", "localhost", "443"},
{"some.super.long.domain.example.org:8080", "some.super.long.domain.example.org", "8080"},
{"[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:17000", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", "17000"},
{"[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", ""},
// Ensure that even when not valid, Host is one of "Hostname",
// "Hostname:Port", "[Hostname]" or "[Hostname]:Port".
// See https://golang.org/issue/29098.
{"[minio.io]:80", "minio.io", "80"},
{"minio.io]:80", "minio.io]", "80"},
{"minio.io:80_invalid_port", "minio.io:80_invalid_port", ""},
{"[::1]extra]:80", "::1]extra", "80"},
{"minio.io]extra:extra", "minio.io]extra:extra", ""},
}
for _, tt := range tests {
u := URL{Host: tt.in}
host, port := u.Hostname(), u.Port()
if host != tt.host {
t.Errorf("Hostname for Host %q = %q; want %q", tt.in, host, tt.host)
}
if port != tt.port {
t.Errorf("Port for Host %q = %q; want %q", tt.in, port, tt.port)
}
}
}
func TestURLIsEmpty(t *testing.T) {
testCases := []struct {
url URL
expectedResult bool
}{
{URL{}, true},
{URL{Scheme: "http", Host: "play"}, false},
{URL{Path: "path/to/play"}, false},
}
for i, testCase := range testCases {
result := testCase.url.IsEmpty()
if result != testCase.expectedResult {
t.Fatalf("test %v: result: expected: %v, got: %v", i+1, testCase.expectedResult, result)
}
}
}
func TestURLString(t *testing.T) {
testCases := []struct {
url URL
expectedStr string
}{
{URL{}, ""},
{URL{Scheme: "http", Host: "play"}, "http://play"},
{URL{Scheme: "https", Host: "play:443"}, "https://play"},
{URL{Scheme: "https", Host: "play.min.io:80"}, "https://play.min.io:80"},
{URL{Scheme: "https", Host: "147.75.201.93:9000", Path: "/"}, "https://147.75.201.93:9000/"},
{URL{Scheme: "https", Host: "s3.amazonaws.com", Path: "/", RawQuery: "location"}, "https://s3.amazonaws.com/?location"},
{URL{Scheme: "http", Host: "myminio:10000", Path: "/mybucket/myobject"}, "http://myminio:10000/mybucket/myobject"},
{URL{Scheme: "ftp", Host: "myftp.server:10000", Path: "/myuser"}, "ftp://myftp.server:10000/myuser"},
{URL{Path: "path/to/play"}, "path/to/play"},
}
for i, testCase := range testCases {
str := testCase.url.String()
if str != testCase.expectedStr {
t.Fatalf("test %v: string: expected: %v, got: %v", i+1, testCase.expectedStr, str)
}
}
}
func TestURLMarshalJSON(t *testing.T) {
testCases := []struct {
url URL
expectedData []byte
expectErr bool
}{
{URL{}, []byte(`""`), false},
{URL{Scheme: "http", Host: "play"}, []byte(`"http://play"`), false},
{URL{Scheme: "https", Host: "play.min.io:0"}, []byte(`"https://play.min.io:0"`), false},
{URL{Scheme: "https", Host: "147.75.201.93:9000", Path: "/"}, []byte(`"https://147.75.201.93:9000/"`), false},
{URL{Scheme: "https", Host: "s3.amazonaws.com", Path: "/", RawQuery: "location"}, []byte(`"https://s3.amazonaws.com/?location"`), false},
{URL{Scheme: "http", Host: "myminio:10000", Path: "/mybucket/myobject"}, []byte(`"http://myminio:10000/mybucket/myobject"`), false},
{URL{Scheme: "ftp", Host: "myftp.server:10000", Path: "/myuser"}, []byte(`"ftp://myftp.server:10000/myuser"`), false},
}
for i, testCase := range testCases {
data, err := testCase.url.MarshalJSON()
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("test %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if !reflect.DeepEqual(data, testCase.expectedData) {
t.Fatalf("test %v: data: expected: %v, got: %v", i+1, string(testCase.expectedData), string(data))
}
}
}
}
func TestURLUnmarshalJSON(t *testing.T) {
testCases := []struct {
data []byte
expectedURL *URL
expectErr bool
}{
{[]byte(`""`), &URL{}, false},
{[]byte(`"http://play"`), &URL{Scheme: "http", Host: "play"}, false},
{[]byte(`"https://play.min.io:0"`), &URL{Scheme: "https", Host: "play.min.io:0"}, false},
{[]byte(`"https://147.75.201.93:9000/"`), &URL{Scheme: "https", Host: "147.75.201.93:9000", Path: "/"}, false},
{[]byte(`"https://s3.amazonaws.com/?location"`), &URL{Scheme: "https", Host: "s3.amazonaws.com", Path: "/", RawQuery: "location"}, false},
{[]byte(`"http://myminio:10000/mybucket/myobject//"`), &URL{Scheme: "http", Host: "myminio:10000", Path: "/mybucket/myobject/"}, false},
{[]byte(`"ftp://myftp.server:10000/myuser"`), &URL{Scheme: "ftp", Host: "myftp.server:10000", Path: "/myuser"}, false},
{[]byte(`"http://webhook.server:10000/mywebhook/"`), &URL{Scheme: "http", Host: "webhook.server:10000", Path: "/mywebhook/"}, false},
{[]byte(`"myserver:1000"`), nil, true},
{[]byte(`"http://:1000/mybucket"`), nil, true},
{[]byte(`"https://147.75.201.93:90000/"`), nil, true},
{[]byte(`"http:/play"`), nil, true},
}
for i, testCase := range testCases {
var url URL
err := url.UnmarshalJSON(testCase.data)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("test %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if !reflect.DeepEqual(&url, testCase.expectedURL) {
t.Fatalf("test %v: host: expected: %#v, got: %#v", i+1, testCase.expectedURL, url)
}
}
}
}
func TestParseHTTPURL(t *testing.T) {
testCases := []struct {
s string
expectedURL *URL
expectErr bool
}{
{"http://play", &URL{Scheme: "http", Host: "play"}, false},
{"https://play.min.io:0", &URL{Scheme: "https", Host: "play.min.io:0"}, false},
{"https://147.75.201.93:9000/", &URL{Scheme: "https", Host: "147.75.201.93:9000", Path: "/"}, false},
{"https://s3.amazonaws.com/?location", &URL{Scheme: "https", Host: "s3.amazonaws.com", Path: "/", RawQuery: "location"}, false},
{"http://myminio:10000/mybucket//myobject/", &URL{Scheme: "http", Host: "myminio:10000", Path: "/mybucket/myobject/"}, false},
{"ftp://myftp.server:10000/myuser", nil, true},
{"https://my.server:10000000/myuser", nil, true},
{"myserver:1000", nil, true},
{"http://:1000/mybucket", nil, true},
{"https://147.75.201.93:90000/", nil, true},
{"http:/play", nil, true},
}
for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.s, func(t *testing.T) {
url, err := ParseHTTPURL(testCase.s)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("error: expected: %v, got: %v", testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if !reflect.DeepEqual(url, testCase.expectedURL) {
t.Fatalf("host: expected: %#v, got: %#v", testCase.expectedURL, url)
}
}
})
}
}
func TestParseURL(t *testing.T) {
testCases := []struct {
s string
expectedURL *URL
expectErr bool
}{
{"http://play", &URL{Scheme: "http", Host: "play"}, false},
{"https://play.min.io:0", &URL{Scheme: "https", Host: "play.min.io:0"}, false},
{"https://147.75.201.93:9000/", &URL{Scheme: "https", Host: "147.75.201.93:9000", Path: "/"}, false},
{"https://s3.amazonaws.com/?location", &URL{Scheme: "https", Host: "s3.amazonaws.com", Path: "/", RawQuery: "location"}, false},
{"http://myminio:10000/mybucket//myobject/", &URL{Scheme: "http", Host: "myminio:10000", Path: "/mybucket/myobject/"}, false},
{"ftp://myftp.server:10000/myuser", &URL{Scheme: "ftp", Host: "myftp.server:10000", Path: "/myuser"}, false},
{"myserver:1000", nil, true},
{"http://:1000/mybucket", nil, true},
{"https://147.75.201.93:90000/", nil, true},
{"http:/play", nil, true},
}
for i, testCase := range testCases {
url, err := ParseURL(testCase.s)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("test %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if !reflect.DeepEqual(url, testCase.expectedURL) {
t.Fatalf("test %v: host: expected: %#v, got: %#v", i+1, testCase.expectedURL, url)
}
}
}
}
golang-github-minio-pkg-3.0.10/policy/ 0000775 0000000 0000000 00000000000 14655770405 0017526 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/policy/action.go 0000664 0000000 0000000 00000052342 14655770405 0021340 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
import (
"github.com/minio/pkg/v3/policy/condition"
"github.com/minio/pkg/v3/wildcard"
)
// Action - policy action.
// Refer https://docs.aws.amazon.com/IAM/latest/UserGuide/list_amazons3.html
// for more information about available actions.
type Action string
const (
// AbortMultipartUploadAction - AbortMultipartUpload Rest API action.
AbortMultipartUploadAction Action = "s3:AbortMultipartUpload"
// CreateBucketAction - CreateBucket Rest API action.
CreateBucketAction = "s3:CreateBucket"
// DeleteBucketAction - DeleteBucket Rest API action.
DeleteBucketAction = "s3:DeleteBucket"
// ForceDeleteBucketAction - DeleteBucket Rest API action when x-minio-force-delete flag
// is specified.
ForceDeleteBucketAction = "s3:ForceDeleteBucket"
// DeleteBucketPolicyAction - DeleteBucketPolicy Rest API action.
DeleteBucketPolicyAction = "s3:DeleteBucketPolicy"
// DeleteBucketCorsAction - DeleteBucketCors Rest API action.
DeleteBucketCorsAction = "s3:DeleteBucketCors"
// DeleteObjectAction - DeleteObject Rest API action.
DeleteObjectAction = "s3:DeleteObject"
// GetBucketLocationAction - GetBucketLocation Rest API action.
GetBucketLocationAction = "s3:GetBucketLocation"
// GetBucketNotificationAction - GetBucketNotification Rest API action.
GetBucketNotificationAction = "s3:GetBucketNotification"
// GetBucketPolicyAction - GetBucketPolicy Rest API action.
GetBucketPolicyAction = "s3:GetBucketPolicy"
// GetBucketCorsAction - GetBucketCors Rest API action.
GetBucketCorsAction = "s3:GetBucketCors"
// GetObjectAction - GetObject Rest API action.
GetObjectAction = "s3:GetObject"
// GetObjectAttributesAction - GetObjectVersionAttributes Rest API action.
GetObjectAttributesAction = "s3:GetObjectAttributes"
// HeadBucketAction - HeadBucket Rest API action. This action is unused in minio.
HeadBucketAction = "s3:HeadBucket"
// ListAllMyBucketsAction - ListAllMyBuckets (List buckets) Rest API action.
ListAllMyBucketsAction = "s3:ListAllMyBuckets"
// ListBucketAction - ListBucket Rest API action.
ListBucketAction = "s3:ListBucket"
// GetBucketPolicyStatusAction - Retrieves the policy status for a bucket.
GetBucketPolicyStatusAction = "s3:GetBucketPolicyStatus"
// ListBucketVersionsAction - ListBucketVersions Rest API action.
ListBucketVersionsAction = "s3:ListBucketVersions"
// ListBucketMultipartUploadsAction - ListMultipartUploads Rest API action.
ListBucketMultipartUploadsAction = "s3:ListBucketMultipartUploads"
// ListenNotificationAction - ListenNotification Rest API action.
// This is MinIO extension.
ListenNotificationAction = "s3:ListenNotification"
// ListenBucketNotificationAction - ListenBucketNotification Rest API action.
// This is MinIO extension.
ListenBucketNotificationAction = "s3:ListenBucketNotification"
// ListMultipartUploadPartsAction - ListParts Rest API action.
ListMultipartUploadPartsAction = "s3:ListMultipartUploadParts"
// PutBucketLifecycleAction - PutBucketLifecycle Rest API action.
PutBucketLifecycleAction = "s3:PutLifecycleConfiguration"
// GetBucketLifecycleAction - GetBucketLifecycle Rest API action.
GetBucketLifecycleAction = "s3:GetLifecycleConfiguration"
// PutBucketNotificationAction - PutObjectNotification Rest API action.
PutBucketNotificationAction = "s3:PutBucketNotification"
// PutBucketPolicyAction - PutBucketPolicy Rest API action.
PutBucketPolicyAction = "s3:PutBucketPolicy"
// PutBucketCorsAction - PutBucketCors Rest API action.
PutBucketCorsAction = "s3:PutBucketCors"
// PutObjectAction - PutObject Rest API action.
PutObjectAction = "s3:PutObject"
// DeleteObjectVersionAction - DeleteObjectVersion Rest API action.
DeleteObjectVersionAction = "s3:DeleteObjectVersion"
// DeleteObjectVersionTaggingAction - DeleteObjectVersionTagging Rest API action.
DeleteObjectVersionTaggingAction = "s3:DeleteObjectVersionTagging"
// GetObjectVersionAction - GetObjectVersionAction Rest API action.
GetObjectVersionAction = "s3:GetObjectVersion"
// GetObjectVersionAttributesAction - GetObjectVersionAttributes Rest API action.
GetObjectVersionAttributesAction = "s3:GetObjectVersionAttributes"
// GetObjectVersionTaggingAction - GetObjectVersionTagging Rest API action.
GetObjectVersionTaggingAction = "s3:GetObjectVersionTagging"
// PutObjectVersionTaggingAction - PutObjectVersionTagging Rest API action.
PutObjectVersionTaggingAction = "s3:PutObjectVersionTagging"
// BypassGovernanceRetentionAction - bypass governance retention for PutObjectRetention, PutObject and DeleteObject Rest API action.
BypassGovernanceRetentionAction = "s3:BypassGovernanceRetention"
// PutObjectRetentionAction - PutObjectRetention Rest API action.
PutObjectRetentionAction = "s3:PutObjectRetention"
// GetObjectRetentionAction - GetObjectRetention, GetObject, HeadObject Rest API action.
GetObjectRetentionAction = "s3:GetObjectRetention"
// GetObjectLegalHoldAction - GetObjectLegalHold, GetObject Rest API action.
GetObjectLegalHoldAction = "s3:GetObjectLegalHold"
// PutObjectLegalHoldAction - PutObjectLegalHold, PutObject Rest API action.
PutObjectLegalHoldAction = "s3:PutObjectLegalHold"
// GetBucketObjectLockConfigurationAction - GetBucketObjectLockConfiguration Rest API action
GetBucketObjectLockConfigurationAction = "s3:GetBucketObjectLockConfiguration"
// PutBucketObjectLockConfigurationAction - PutBucketObjectLockConfiguration Rest API action
PutBucketObjectLockConfigurationAction = "s3:PutBucketObjectLockConfiguration"
// GetBucketTaggingAction - GetBucketTagging Rest API action
GetBucketTaggingAction = "s3:GetBucketTagging"
// PutBucketTaggingAction - PutBucketTagging Rest API action
PutBucketTaggingAction = "s3:PutBucketTagging"
// GetObjectTaggingAction - Get Object Tags API action
GetObjectTaggingAction = "s3:GetObjectTagging"
// PutObjectTaggingAction - Put Object Tags API action
PutObjectTaggingAction = "s3:PutObjectTagging"
// DeleteObjectTaggingAction - Delete Object Tags API action
DeleteObjectTaggingAction = "s3:DeleteObjectTagging"
// PutBucketEncryptionAction - PutBucketEncryption REST API action
PutBucketEncryptionAction = "s3:PutEncryptionConfiguration"
// GetBucketEncryptionAction - GetBucketEncryption REST API action
GetBucketEncryptionAction = "s3:GetEncryptionConfiguration"
// PutBucketVersioningAction - PutBucketVersioning REST API action
PutBucketVersioningAction = "s3:PutBucketVersioning"
// GetBucketVersioningAction - GetBucketVersioning REST API action
GetBucketVersioningAction = "s3:GetBucketVersioning"
// GetReplicationConfigurationAction - GetReplicationConfiguration REST API action
GetReplicationConfigurationAction = "s3:GetReplicationConfiguration"
// PutReplicationConfigurationAction - PutReplicationConfiguration REST API action
PutReplicationConfigurationAction = "s3:PutReplicationConfiguration"
// ReplicateObjectAction - ReplicateObject REST API action
ReplicateObjectAction = "s3:ReplicateObject"
// ReplicateDeleteAction - ReplicateDelete REST API action
ReplicateDeleteAction = "s3:ReplicateDelete"
// ReplicateTagsAction - ReplicateTags REST API action
ReplicateTagsAction = "s3:ReplicateTags"
// GetObjectVersionForReplicationAction - GetObjectVersionForReplication REST API action
GetObjectVersionForReplicationAction = "s3:GetObjectVersionForReplication"
// RestoreObjectAction - RestoreObject REST API action
RestoreObjectAction = "s3:RestoreObject"
// ResetBucketReplicationStateAction - MinIO extension API ResetBucketReplicationState to reset replication state
// on a bucket
ResetBucketReplicationStateAction = "s3:ResetBucketReplicationState"
// PutObjectFanOutAction - PutObject like API action but allows PostUpload() fan-out.
PutObjectFanOutAction = "s3:PutObjectFanOut"
// AllActions - all API actions
AllActions = "s3:*"
)
// List of all supported actions.
var supportedActions = map[Action]struct{}{
AbortMultipartUploadAction: {},
CreateBucketAction: {},
DeleteBucketAction: {},
ForceDeleteBucketAction: {},
DeleteBucketPolicyAction: {},
DeleteObjectAction: {},
GetBucketLocationAction: {},
GetBucketNotificationAction: {},
GetBucketPolicyAction: {},
GetObjectAction: {},
HeadBucketAction: {},
ListAllMyBucketsAction: {},
ListBucketAction: {},
GetBucketPolicyStatusAction: {},
ListBucketVersionsAction: {},
ListBucketMultipartUploadsAction: {},
ListenNotificationAction: {},
ListenBucketNotificationAction: {},
ListMultipartUploadPartsAction: {},
PutBucketLifecycleAction: {},
GetBucketLifecycleAction: {},
PutBucketNotificationAction: {},
PutBucketPolicyAction: {},
PutObjectAction: {},
BypassGovernanceRetentionAction: {},
PutObjectRetentionAction: {},
GetObjectRetentionAction: {},
GetObjectLegalHoldAction: {},
PutObjectLegalHoldAction: {},
GetBucketObjectLockConfigurationAction: {},
PutBucketObjectLockConfigurationAction: {},
GetBucketTaggingAction: {},
PutBucketTaggingAction: {},
GetObjectVersionAction: {},
GetObjectAttributesAction: {},
GetObjectVersionAttributesAction: {},
GetObjectVersionTaggingAction: {},
DeleteObjectVersionAction: {},
DeleteObjectVersionTaggingAction: {},
PutObjectVersionTaggingAction: {},
GetObjectTaggingAction: {},
PutObjectTaggingAction: {},
DeleteObjectTaggingAction: {},
PutBucketEncryptionAction: {},
GetBucketEncryptionAction: {},
PutBucketVersioningAction: {},
GetBucketVersioningAction: {},
GetReplicationConfigurationAction: {},
PutReplicationConfigurationAction: {},
ReplicateObjectAction: {},
ReplicateDeleteAction: {},
ReplicateTagsAction: {},
GetObjectVersionForReplicationAction: {},
RestoreObjectAction: {},
ResetBucketReplicationStateAction: {},
PutObjectFanOutAction: {},
AllActions: {},
}
// List of all supported object actions.
var supportedObjectActions = map[Action]struct{}{
AllActions: {},
AbortMultipartUploadAction: {},
DeleteObjectAction: {},
GetObjectAction: {},
ListMultipartUploadPartsAction: {},
PutObjectAction: {},
BypassGovernanceRetentionAction: {},
PutObjectRetentionAction: {},
GetObjectRetentionAction: {},
PutObjectLegalHoldAction: {},
GetObjectLegalHoldAction: {},
GetObjectTaggingAction: {},
PutObjectTaggingAction: {},
DeleteObjectTaggingAction: {},
GetObjectVersionAction: {},
GetObjectVersionTaggingAction: {},
DeleteObjectVersionAction: {},
DeleteObjectVersionTaggingAction: {},
PutObjectVersionTaggingAction: {},
ReplicateObjectAction: {},
ReplicateDeleteAction: {},
ReplicateTagsAction: {},
GetObjectVersionForReplicationAction: {},
RestoreObjectAction: {},
ResetBucketReplicationStateAction: {},
PutObjectFanOutAction: {},
GetObjectAttributesAction: {},
GetObjectVersionAttributesAction: {},
}
// IsObjectAction - returns whether action is object type or not.
func (action Action) IsObjectAction() bool {
for supAction := range supportedObjectActions {
if action.Match(supAction) {
return true
}
}
return false
}
// Match - matches action name with action patter.
func (action Action) Match(a Action) bool {
return wildcard.Match(string(action), string(a))
}
// IsValid - checks if action is valid or not.
func (action Action) IsValid() bool {
for supAction := range supportedActions {
if action.Match(supAction) {
return true
}
}
return false
}
// ActionConditionKeyMap is alias for the map type used here.
type ActionConditionKeyMap map[Action]condition.KeySet
// Lookup - looks up the action in the condition key map.
func (a ActionConditionKeyMap) Lookup(action Action) condition.KeySet {
commonKeys := []condition.Key{}
for _, keyName := range condition.CommonKeys {
commonKeys = append(commonKeys, keyName.ToKey())
}
ckeysMerged := condition.NewKeySet(commonKeys...)
for act, ckey := range a {
if action.Match(act) {
ckeysMerged.Merge(ckey)
}
}
return ckeysMerged
}
// IAMActionConditionKeyMap - holds mapping of supported condition key for an action.
var IAMActionConditionKeyMap = createActionConditionKeyMap()
func createActionConditionKeyMap() ActionConditionKeyMap {
commonKeys := []condition.Key{}
for _, keyName := range condition.CommonKeys {
commonKeys = append(commonKeys, keyName.ToKey())
}
allSupportedKeys := []condition.Key{}
for _, keyName := range condition.AllSupportedKeys {
allSupportedKeys = append(allSupportedKeys, keyName.ToKey())
}
return ActionConditionKeyMap{
AllActions: condition.NewKeySet(allSupportedKeys...),
AbortMultipartUploadAction: condition.NewKeySet(commonKeys...),
CreateBucketAction: condition.NewKeySet(commonKeys...),
DeleteObjectAction: condition.NewKeySet(
append([]condition.Key{
condition.S3VersionID.ToKey(),
}, commonKeys...)...),
GetBucketLocationAction: condition.NewKeySet(commonKeys...),
GetBucketPolicyStatusAction: condition.NewKeySet(commonKeys...),
GetObjectAction: condition.NewKeySet(
append([]condition.Key{
condition.S3XAmzServerSideEncryption.ToKey(),
condition.S3XAmzServerSideEncryptionCustomerAlgorithm.ToKey(),
condition.S3XAmzServerSideEncryptionAwsKmsKeyID.ToKey(),
condition.S3VersionID.ToKey(),
condition.ExistingObjectTag.ToKey(),
}, commonKeys...)...),
HeadBucketAction: condition.NewKeySet(commonKeys...),
GetObjectAttributesAction: condition.NewKeySet(
append([]condition.Key{
condition.ExistingObjectTag.ToKey(),
}, commonKeys...)...),
GetObjectVersionAttributesAction: condition.NewKeySet(
append([]condition.Key{
condition.S3VersionID.ToKey(),
condition.ExistingObjectTag.ToKey(),
}, commonKeys...)...),
ListAllMyBucketsAction: condition.NewKeySet(commonKeys...),
ListBucketAction: condition.NewKeySet(
append([]condition.Key{
condition.S3Prefix.ToKey(),
condition.S3Delimiter.ToKey(),
condition.S3MaxKeys.ToKey(),
}, commonKeys...)...),
ListBucketVersionsAction: condition.NewKeySet(
append([]condition.Key{
condition.S3Prefix.ToKey(),
condition.S3Delimiter.ToKey(),
condition.S3MaxKeys.ToKey(),
}, commonKeys...)...),
ListBucketMultipartUploadsAction: condition.NewKeySet(commonKeys...),
ListenNotificationAction: condition.NewKeySet(commonKeys...),
ListenBucketNotificationAction: condition.NewKeySet(commonKeys...),
ListMultipartUploadPartsAction: condition.NewKeySet(commonKeys...),
PutObjectAction: condition.NewKeySet(
append([]condition.Key{
condition.S3XAmzCopySource.ToKey(),
condition.S3XAmzServerSideEncryption.ToKey(),
condition.S3XAmzServerSideEncryptionCustomerAlgorithm.ToKey(),
condition.S3XAmzServerSideEncryptionAwsKmsKeyID.ToKey(),
condition.S3XAmzMetadataDirective.ToKey(),
condition.S3XAmzStorageClass.ToKey(),
condition.S3VersionID.ToKey(),
condition.S3ObjectLockRetainUntilDate.ToKey(),
condition.S3ObjectLockMode.ToKey(),
condition.S3ObjectLockLegalHold.ToKey(),
condition.RequestObjectTagKeys.ToKey(),
condition.RequestObjectTag.ToKey(),
}, commonKeys...)...),
// https://docs.aws.amazon.com/AmazonS3/latest/dev/list_amazons3.html
// LockLegalHold is not supported with PutObjectRetentionAction
PutObjectRetentionAction: condition.NewKeySet(
append([]condition.Key{
condition.S3XAmzServerSideEncryption.ToKey(),
condition.S3XAmzServerSideEncryptionCustomerAlgorithm.ToKey(),
condition.S3XAmzServerSideEncryptionAwsKmsKeyID.ToKey(),
condition.S3ObjectLockRemainingRetentionDays.ToKey(),
condition.S3ObjectLockRetainUntilDate.ToKey(),
condition.S3ObjectLockMode.ToKey(),
condition.S3VersionID.ToKey(),
}, commonKeys...)...),
GetObjectRetentionAction: condition.NewKeySet(
append([]condition.Key{
condition.S3XAmzServerSideEncryption.ToKey(),
condition.S3XAmzServerSideEncryptionCustomerAlgorithm.ToKey(),
condition.S3XAmzServerSideEncryptionAwsKmsKeyID.ToKey(),
condition.S3VersionID.ToKey(),
}, commonKeys...)...),
PutObjectLegalHoldAction: condition.NewKeySet(
append([]condition.Key{
condition.S3XAmzServerSideEncryption.ToKey(),
condition.S3XAmzServerSideEncryptionCustomerAlgorithm.ToKey(),
condition.S3XAmzServerSideEncryptionAwsKmsKeyID.ToKey(),
condition.S3ObjectLockLegalHold.ToKey(),
condition.S3VersionID.ToKey(),
}, commonKeys...)...),
GetObjectLegalHoldAction: condition.NewKeySet(commonKeys...),
// https://docs.aws.amazon.com/AmazonS3/latest/dev/list_amazons3.html
BypassGovernanceRetentionAction: condition.NewKeySet(
append([]condition.Key{
condition.S3VersionID.ToKey(),
condition.S3ObjectLockRemainingRetentionDays.ToKey(),
condition.S3ObjectLockRetainUntilDate.ToKey(),
condition.S3ObjectLockMode.ToKey(),
condition.S3ObjectLockLegalHold.ToKey(),
condition.RequestObjectTagKeys.ToKey(),
condition.RequestObjectTag.ToKey(),
}, commonKeys...)...),
GetBucketObjectLockConfigurationAction: condition.NewKeySet(commonKeys...),
PutBucketObjectLockConfigurationAction: condition.NewKeySet(commonKeys...),
GetBucketTaggingAction: condition.NewKeySet(commonKeys...),
PutBucketTaggingAction: condition.NewKeySet(
append([]condition.Key{
condition.RequestObjectTagKeys.ToKey(),
condition.RequestObjectTag.ToKey(),
}, commonKeys...)...),
PutObjectTaggingAction: condition.NewKeySet(
append([]condition.Key{
condition.S3VersionID.ToKey(),
condition.ExistingObjectTag.ToKey(),
condition.RequestObjectTagKeys.ToKey(),
condition.RequestObjectTag.ToKey(),
}, commonKeys...)...),
GetObjectTaggingAction: condition.NewKeySet(
append([]condition.Key{
condition.S3VersionID.ToKey(),
condition.ExistingObjectTag.ToKey(),
}, commonKeys...)...),
DeleteObjectTaggingAction: condition.NewKeySet(
append([]condition.Key{
condition.S3VersionID.ToKey(),
condition.ExistingObjectTag.ToKey(),
}, commonKeys...)...),
PutObjectVersionTaggingAction: condition.NewKeySet(
append([]condition.Key{
condition.S3VersionID.ToKey(),
condition.ExistingObjectTag.ToKey(),
condition.RequestObjectTagKeys.ToKey(),
condition.RequestObjectTag.ToKey(),
}, commonKeys...)...),
GetObjectVersionAction: condition.NewKeySet(
append([]condition.Key{
condition.S3VersionID.ToKey(),
condition.ExistingObjectTag.ToKey(),
}, commonKeys...)...),
GetObjectVersionTaggingAction: condition.NewKeySet(
append([]condition.Key{
condition.S3VersionID.ToKey(),
condition.ExistingObjectTag.ToKey(),
}, commonKeys...)...),
DeleteObjectVersionAction: condition.NewKeySet(
append([]condition.Key{
condition.S3VersionID.ToKey(),
}, commonKeys...)...),
DeleteObjectVersionTaggingAction: condition.NewKeySet(
append([]condition.Key{
condition.S3VersionID.ToKey(),
condition.ExistingObjectTag.ToKey(),
}, commonKeys...)...),
GetReplicationConfigurationAction: condition.NewKeySet(commonKeys...),
PutReplicationConfigurationAction: condition.NewKeySet(commonKeys...),
ReplicateObjectAction: condition.NewKeySet(
append([]condition.Key{
condition.S3VersionID.ToKey(),
condition.ExistingObjectTag.ToKey(),
}, commonKeys...)...),
ReplicateDeleteAction: condition.NewKeySet(
append([]condition.Key{
condition.S3VersionID.ToKey(),
condition.ExistingObjectTag.ToKey(),
}, commonKeys...)...),
ReplicateTagsAction: condition.NewKeySet(
append([]condition.Key{
condition.S3VersionID.ToKey(),
condition.ExistingObjectTag.ToKey(),
}, commonKeys...)...),
GetObjectVersionForReplicationAction: condition.NewKeySet(
append([]condition.Key{
condition.S3VersionID.ToKey(),
condition.ExistingObjectTag.ToKey(),
}, commonKeys...)...),
RestoreObjectAction: condition.NewKeySet(commonKeys...),
ResetBucketReplicationStateAction: condition.NewKeySet(commonKeys...),
PutObjectFanOutAction: condition.NewKeySet(commonKeys...),
}
}
golang-github-minio-pkg-3.0.10/policy/action_test.go 0000664 0000000 0000000 00000003341 14655770405 0022372 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
import (
"testing"
)
func TestActionIsObjectAction(t *testing.T) {
testCases := []struct {
action Action
expectedResult bool
}{
{AbortMultipartUploadAction, true},
{DeleteObjectAction, true},
{GetObjectAction, true},
{ListMultipartUploadPartsAction, true},
{PutObjectAction, true},
{CreateBucketAction, false},
}
for i, testCase := range testCases {
result := testCase.action.IsObjectAction()
if testCase.expectedResult != result {
t.Fatalf("case %v: expected: %v, got: %v", i+1, testCase.expectedResult, result)
}
}
}
func TestActionIsValid(t *testing.T) {
testCases := []struct {
action Action
expectedResult bool
}{
{PutObjectAction, true},
{AbortMultipartUploadAction, true},
{Action("foo"), false},
}
for i, testCase := range testCases {
result := testCase.action.IsValid()
if testCase.expectedResult != result {
t.Fatalf("case %v: expected: %v, got: %v", i+1, testCase.expectedResult, result)
}
}
}
golang-github-minio-pkg-3.0.10/policy/actionset.go 0000664 0000000 0000000 00000013013 14655770405 0022044 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
import (
"encoding/json"
"fmt"
"sort"
"github.com/minio/minio-go/v7/pkg/set"
)
// ActionSet - set of actions.
type ActionSet map[Action]struct{}
// Clone clones ActionSet structure
func (actionSet ActionSet) Clone() ActionSet {
return NewActionSet(actionSet.ToSlice()...)
}
// Add - add action to the set.
func (actionSet ActionSet) Add(action Action) {
actionSet[action] = struct{}{}
}
// Contains - checks given action exists in the action set.
func (actionSet ActionSet) Contains(action Action) bool {
_, found := actionSet[action]
return found
}
// IsEmpty - returns if the current action set is empty
func (actionSet ActionSet) IsEmpty() bool {
return len(actionSet) == 0
}
// Match - matches object name with anyone of action pattern in action set.
func (actionSet ActionSet) Match(action Action) bool {
for r := range actionSet {
if r.Match(action) {
return true
}
// This is a special case where GetObjectVersion
// means GetObject is enabled implicitly.
switch r {
case GetObjectVersionAction:
if action == GetObjectAction {
return true
}
}
}
return false
}
// Equals - checks whether given action set is equal to current action set or not.
func (actionSet ActionSet) Equals(sactionSet ActionSet) bool {
// If length of set is not equal to length of given set, the
// set is not equal to given set.
if len(actionSet) != len(sactionSet) {
return false
}
// As both sets are equal in length, check each elements are equal.
for k := range actionSet {
if _, ok := sactionSet[k]; !ok {
return false
}
}
return true
}
// Intersection - returns actions available in both ActionSet.
func (actionSet ActionSet) Intersection(sset ActionSet) ActionSet {
nset := NewActionSet()
for k := range actionSet {
if _, ok := sset[k]; ok {
nset.Add(k)
}
}
return nset
}
// MarshalJSON - encodes ActionSet to JSON data.
func (actionSet ActionSet) MarshalJSON() ([]byte, error) {
if len(actionSet) == 0 {
return nil, Errorf("empty actions not allowed")
}
return json.Marshal(actionSet.ToSlice())
}
func (actionSet ActionSet) String() string {
actions := []string{}
for action := range actionSet {
actions = append(actions, string(action))
}
sort.Strings(actions)
return fmt.Sprintf("%v", actions)
}
// ToSlice - returns slice of actions from the action set.
func (actionSet ActionSet) ToSlice() []Action {
actions := []Action{}
for action := range actionSet {
actions = append(actions, action)
}
return actions
}
// ToAdminSlice - returns slice of admin actions from the action set.
func (actionSet ActionSet) ToAdminSlice() []AdminAction {
actions := []AdminAction{}
for action := range actionSet {
actions = append(actions, AdminAction(action))
}
return actions
}
// ToSTSSlice - returns slice of STS actions from the action set.
func (actionSet ActionSet) ToSTSSlice() []STSAction {
actions := []STSAction{}
for action := range actionSet {
actions = append(actions, STSAction(action))
}
return actions
}
// ToKMSSlice - returns slice of kms actions from the action set.
func (actionSet ActionSet) ToKMSSlice() (actions []KMSAction) {
for action := range actionSet {
actions = append(actions, KMSAction(action))
}
return actions
}
// UnmarshalJSON - decodes JSON data to ActionSet.
func (actionSet *ActionSet) UnmarshalJSON(data []byte) error {
var sset set.StringSet
if err := json.Unmarshal(data, &sset); err != nil {
return err
}
if sset.IsEmpty() {
return Errorf("empty actions not allowed")
}
*actionSet = make(ActionSet)
for _, s := range sset.ToSlice() {
actionSet.Add(Action(s))
}
return nil
}
// ValidateAdmin checks if all actions are valid Admin actions
func (actionSet ActionSet) ValidateAdmin() error {
for _, action := range actionSet.ToAdminSlice() {
if !action.IsValid() {
return Errorf("unsupported admin action '%v'", action)
}
}
return nil
}
// ValidateSTS checks if all actions are valid STS actions
func (actionSet ActionSet) ValidateSTS() error {
for _, action := range actionSet.ToSTSSlice() {
if !action.IsValid() {
return Errorf("unsupported STS action '%v'", action)
}
}
return nil
}
// ValidateKMS checks if all actions are valid KMS actions
func (actionSet ActionSet) ValidateKMS() error {
for _, action := range actionSet.ToKMSSlice() {
if !action.IsValid() {
return Errorf("unsupported KMS action '%v'", action)
}
}
return nil
}
// Validate checks if all actions are valid
func (actionSet ActionSet) Validate() error {
for _, action := range actionSet.ToSlice() {
if !action.IsValid() {
return Errorf("unsupported action '%v'", action)
}
}
return nil
}
// NewActionSet - creates new action set.
func NewActionSet(actions ...Action) ActionSet {
actionSet := make(ActionSet)
for _, action := range actions {
actionSet.Add(action)
}
return actionSet
}
golang-github-minio-pkg-3.0.10/policy/actionset_test.go 0000664 0000000 0000000 00000012523 14655770405 0023110 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
import (
"encoding/json"
"reflect"
"testing"
)
func TestActionSetAdd(t *testing.T) {
testCases := []struct {
set ActionSet
action Action
expectedResult ActionSet
}{
{NewActionSet(), PutObjectAction, NewActionSet(PutObjectAction)},
{NewActionSet(PutObjectAction), PutObjectAction, NewActionSet(PutObjectAction)},
}
for i, testCase := range testCases {
testCase.set.Add(testCase.action)
if !reflect.DeepEqual(testCase.expectedResult, testCase.set) {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, testCase.set)
}
}
}
func TestActionSetMatches(t *testing.T) {
testCases := []struct {
set ActionSet
action Action
expectedResult bool
}{
{NewActionSet(AllActions), AbortMultipartUploadAction, true},
{NewActionSet(PutObjectAction), PutObjectAction, true},
{NewActionSet(PutObjectAction, GetObjectAction), PutObjectAction, true},
{NewActionSet(PutObjectAction, GetObjectAction), AbortMultipartUploadAction, false},
}
for i, testCase := range testCases {
result := testCase.set.Match(testCase.action)
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestActionSetIntersection(t *testing.T) {
testCases := []struct {
set ActionSet
setToIntersect ActionSet
expectedResult ActionSet
}{
{NewActionSet(), NewActionSet(PutObjectAction), NewActionSet()},
{NewActionSet(PutObjectAction), NewActionSet(), NewActionSet()},
{NewActionSet(PutObjectAction), NewActionSet(PutObjectAction, GetObjectAction), NewActionSet(PutObjectAction)},
}
for i, testCase := range testCases {
result := testCase.set.Intersection(testCase.setToIntersect)
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, testCase.set)
}
}
}
func TestActionSetMarshalJSON(t *testing.T) {
testCases := []struct {
actionSet ActionSet
expectedResult []byte
expectErr bool
}{
{NewActionSet(PutObjectAction), []byte(`["s3:PutObject"]`), false},
{NewActionSet(), nil, true},
}
for i, testCase := range testCases {
result, err := json.Marshal(testCase.actionSet)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, string(testCase.expectedResult), string(result))
}
}
}
}
func TestActionSetToSlice(t *testing.T) {
testCases := []struct {
actionSet ActionSet
expectedResult []Action
}{
{NewActionSet(PutObjectAction), []Action{PutObjectAction}},
{NewActionSet(), []Action{}},
}
for i, testCase := range testCases {
result := testCase.actionSet.ToSlice()
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestActionSetUnmarshalJSON(t *testing.T) {
testCases := []struct {
data []byte
expectedResult ActionSet
expectUnmarshalErr bool
expectValidateErr bool
}{
{[]byte(`"s3:PutObject"`), NewActionSet(PutObjectAction), false, false},
{[]byte(`["s3:PutObject"]`), NewActionSet(PutObjectAction), false, false},
{[]byte(`["s3:PutObject", "s3:GetObject"]`), NewActionSet(PutObjectAction, GetObjectAction), false, false},
{[]byte(`["s3:PutObject", "s3:GetObject", "s3:PutObject"]`), NewActionSet(PutObjectAction, GetObjectAction), false, false},
{[]byte(`[]`), NewActionSet(), true, false}, // Empty array.
{[]byte(`"foo"`), nil, false, true}, // Invalid action.
{[]byte(`["s3:PutObject", "foo"]`), nil, false, true}, // Invalid action.
}
for i, testCase := range testCases {
result := make(ActionSet)
err := json.Unmarshal(testCase.data, &result)
expectErr := (err != nil)
if expectErr != testCase.expectUnmarshalErr {
t.Fatalf("case %v: error during unmarshal: expected: %v, got: %v\n", i+1, testCase.expectUnmarshalErr, expectErr)
}
err = result.Validate()
expectErr = (err != nil)
if expectErr != testCase.expectValidateErr {
t.Fatalf("case %v: error during validation: expected: %v, got: %v\n", i+1, testCase.expectValidateErr, expectErr)
}
if !testCase.expectUnmarshalErr && !testCase.expectValidateErr {
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
}
golang-github-minio-pkg-3.0.10/policy/admin-action.go 0000664 0000000 0000000 00000027371 14655770405 0022432 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
import (
"github.com/minio/pkg/v3/policy/condition"
)
// AdminAction - admin policy action.
type AdminAction string
const (
// HealAdminAction - allows heal command
HealAdminAction = "admin:Heal"
// DecommissionAdminAction - allows decomissioning of pools
DecommissionAdminAction = "admin:Decommission"
// RebalanceAdminAction - allows rebalancing of pools
RebalanceAdminAction = "admin:Rebalance"
// Service Actions
// StorageInfoAdminAction - allow listing server info
StorageInfoAdminAction = "admin:StorageInfo"
// PrometheusAdminAction - prometheus info action
PrometheusAdminAction = "admin:Prometheus"
// DataUsageInfoAdminAction - allow listing data usage info
DataUsageInfoAdminAction = "admin:DataUsageInfo"
// ForceUnlockAdminAction - allow force unlocking locks
ForceUnlockAdminAction = "admin:ForceUnlock"
// TopLocksAdminAction - allow listing top locks
TopLocksAdminAction = "admin:TopLocksInfo"
// ProfilingAdminAction - allow profiling
ProfilingAdminAction = "admin:Profiling"
// TraceAdminAction - allow listing server trace
TraceAdminAction = "admin:ServerTrace"
// ConsoleLogAdminAction - allow listing console logs on terminal
ConsoleLogAdminAction = "admin:ConsoleLog"
// KMSCreateKeyAdminAction - allow creating a new KMS master key
KMSCreateKeyAdminAction = "admin:KMSCreateKey"
// KMSKeyStatusAdminAction - allow getting KMS key status
KMSKeyStatusAdminAction = "admin:KMSKeyStatus"
// ServerInfoAdminAction - allow listing server info
ServerInfoAdminAction = "admin:ServerInfo"
// HealthInfoAdminAction - allow obtaining cluster health information
HealthInfoAdminAction = "admin:OBDInfo"
// BandwidthMonitorAction - allow monitoring bandwidth usage
BandwidthMonitorAction = "admin:BandwidthMonitor"
// InspectDataAction - allows downloading raw files from backend
InspectDataAction = "admin:InspectData"
// ServerUpdateAdminAction - allow MinIO binary update
ServerUpdateAdminAction = "admin:ServerUpdate"
// ServiceRestartAdminAction - allow restart of MinIO service.
ServiceRestartAdminAction = "admin:ServiceRestart"
// ServiceStopAdminAction - allow stopping MinIO service.
ServiceStopAdminAction = "admin:ServiceStop"
// ServiceFreezeAdminAction - allow freeze/unfreeze MinIO service.
ServiceFreezeAdminAction = "admin:ServiceFreeze"
// ConfigUpdateAdminAction - allow MinIO config management
ConfigUpdateAdminAction = "admin:ConfigUpdate"
// CreateUserAdminAction - allow creating MinIO user
CreateUserAdminAction = "admin:CreateUser"
// DeleteUserAdminAction - allow deleting MinIO user
DeleteUserAdminAction = "admin:DeleteUser"
// ListUsersAdminAction - allow list users permission
ListUsersAdminAction = "admin:ListUsers"
// EnableUserAdminAction - allow enable user permission
EnableUserAdminAction = "admin:EnableUser"
// DisableUserAdminAction - allow disable user permission
DisableUserAdminAction = "admin:DisableUser"
// GetUserAdminAction - allows GET permission on user info
GetUserAdminAction = "admin:GetUser"
// Cluster Replicate Actions
// SiteReplicationAddAction - allow adding clusters for site-level replication
SiteReplicationAddAction = "admin:SiteReplicationAdd"
// SiteReplicationDisableAction - allow disabling a cluster from replication
SiteReplicationDisableAction = "admin:SiteReplicationDisable"
// SiteReplicationRemoveAction - allow removing a cluster from replication
SiteReplicationRemoveAction = "admin:SiteReplicationRemove"
// SiteReplicationResyncAction - allow resyncing cluster data to another site
SiteReplicationResyncAction = "admin:SiteReplicationResync"
// SiteReplicationInfoAction - allow getting site replication info
SiteReplicationInfoAction = "admin:SiteReplicationInfo"
// SiteReplicationOperationAction - allow performing site replication
// create/update/delete operations to peers
SiteReplicationOperationAction = "admin:SiteReplicationOperation"
// Service account Actions
// CreateServiceAccountAdminAction - allow create a service account for a user
CreateServiceAccountAdminAction = "admin:CreateServiceAccount"
// UpdateServiceAccountAdminAction - allow updating a service account
UpdateServiceAccountAdminAction = "admin:UpdateServiceAccount"
// RemoveServiceAccountAdminAction - allow removing a service account
RemoveServiceAccountAdminAction = "admin:RemoveServiceAccount"
// ListServiceAccountsAdminAction - allow listing service accounts
ListServiceAccountsAdminAction = "admin:ListServiceAccounts"
// ListTemporaryAccountsAdminAction - allow listing of temporary accounts
ListTemporaryAccountsAdminAction = "admin:ListTemporaryAccounts"
// Group Actions
// AddUserToGroupAdminAction - allow adding user to group permission
AddUserToGroupAdminAction = "admin:AddUserToGroup"
// RemoveUserFromGroupAdminAction - allow removing user to group permission
RemoveUserFromGroupAdminAction = "admin:RemoveUserFromGroup"
// GetGroupAdminAction - allow getting group info
GetGroupAdminAction = "admin:GetGroup"
// ListGroupsAdminAction - allow list groups permission
ListGroupsAdminAction = "admin:ListGroups"
// EnableGroupAdminAction - allow enable group permission
EnableGroupAdminAction = "admin:EnableGroup"
// DisableGroupAdminAction - allow disable group permission
DisableGroupAdminAction = "admin:DisableGroup"
// Policy Actions
// CreatePolicyAdminAction - allow create policy permission
CreatePolicyAdminAction = "admin:CreatePolicy"
// DeletePolicyAdminAction - allow delete policy permission
DeletePolicyAdminAction = "admin:DeletePolicy"
// GetPolicyAdminAction - allow get policy permission
GetPolicyAdminAction = "admin:GetPolicy"
// AttachPolicyAdminAction - allows attaching a policy to a user/group
AttachPolicyAdminAction = "admin:AttachUserOrGroupPolicy"
// UpdatePolicyAssociationAction - allows to add/remove policy association
// on a user or group.
UpdatePolicyAssociationAction = "admin:UpdatePolicyAssociation"
// ListUserPoliciesAdminAction - allows listing user policies
ListUserPoliciesAdminAction = "admin:ListUserPolicies"
// Bucket quota Actions
// SetBucketQuotaAdminAction - allow setting bucket quota
SetBucketQuotaAdminAction = "admin:SetBucketQuota"
// GetBucketQuotaAdminAction - allow getting bucket quota
GetBucketQuotaAdminAction = "admin:GetBucketQuota"
// Bucket Target admin Actions
// SetBucketTargetAction - allow setting bucket target
SetBucketTargetAction = "admin:SetBucketTarget"
// GetBucketTargetAction - allow getting bucket targets
GetBucketTargetAction = "admin:GetBucketTarget"
// ReplicationDiff - allow computing the unreplicated objects in a bucket
ReplicationDiff = "admin:ReplicationDiff"
// Bucket import/export admin Actions
// ImportBucketMetadataAction - allow importing bucket metadata
ImportBucketMetadataAction = "admin:ImportBucketMetadata"
// ExportBucketMetadataAction - allow exporting bucket metadata
ExportBucketMetadataAction = "admin:ExportBucketMetadata"
// Remote Tier admin Actions
// SetTierAction - allow adding/editing a remote tier
SetTierAction = "admin:SetTier"
// ListTierAction - allow listing remote tiers
ListTierAction = "admin:ListTier"
// Migrate IAM admin Actions
// ExportIAMAction - allow exporting of all IAM info
ExportIAMAction = "admin:ExportIAM"
// ImportIAMAction - allow importing IAM info to MinIO
ImportIAMAction = "admin:ImportIAM"
// Batch Job APIs
// ListBatchJobsAction allow listing current active jobs
ListBatchJobsAction = "admin:ListBatchJobs"
// DescribeBatchJobAction allow getting batch job YAML
DescribeBatchJobAction = "admin:DescribeBatchJob"
// StartBatchJobAction allow submitting a batch job
StartBatchJobAction = "admin:StartBatchJob"
// CancelBatchJobAction allow canceling a batch job
CancelBatchJobAction = "admin:CancelBatchJob"
// AllAdminActions - provides all admin permissions
AllAdminActions = "admin:*"
)
// List of all supported admin actions.
var supportedAdminActions = map[AdminAction]struct{}{
HealAdminAction: {},
StorageInfoAdminAction: {},
DataUsageInfoAdminAction: {},
TopLocksAdminAction: {},
ProfilingAdminAction: {},
PrometheusAdminAction: {},
TraceAdminAction: {},
ConsoleLogAdminAction: {},
KMSCreateKeyAdminAction: {},
KMSKeyStatusAdminAction: {},
ServerInfoAdminAction: {},
HealthInfoAdminAction: {},
BandwidthMonitorAction: {},
ServerUpdateAdminAction: {},
ServiceRestartAdminAction: {},
ServiceStopAdminAction: {},
ServiceFreezeAdminAction: {},
ConfigUpdateAdminAction: {},
CreateUserAdminAction: {},
DeleteUserAdminAction: {},
ListUsersAdminAction: {},
EnableUserAdminAction: {},
DisableUserAdminAction: {},
GetUserAdminAction: {},
AddUserToGroupAdminAction: {},
RemoveUserFromGroupAdminAction: {},
GetGroupAdminAction: {},
ListGroupsAdminAction: {},
EnableGroupAdminAction: {},
DisableGroupAdminAction: {},
CreateServiceAccountAdminAction: {},
UpdateServiceAccountAdminAction: {},
RemoveServiceAccountAdminAction: {},
ListServiceAccountsAdminAction: {},
ListTemporaryAccountsAdminAction: {},
CreatePolicyAdminAction: {},
DeletePolicyAdminAction: {},
GetPolicyAdminAction: {},
AttachPolicyAdminAction: {},
UpdatePolicyAssociationAction: {},
ListUserPoliciesAdminAction: {},
SetBucketQuotaAdminAction: {},
GetBucketQuotaAdminAction: {},
SetBucketTargetAction: {},
GetBucketTargetAction: {},
ReplicationDiff: {},
SetTierAction: {},
ListTierAction: {},
DecommissionAdminAction: {},
RebalanceAdminAction: {},
SiteReplicationAddAction: {},
SiteReplicationDisableAction: {},
SiteReplicationInfoAction: {},
SiteReplicationOperationAction: {},
SiteReplicationRemoveAction: {},
SiteReplicationResyncAction: {},
ImportBucketMetadataAction: {},
ExportBucketMetadataAction: {},
ExportIAMAction: {},
ImportIAMAction: {},
ListBatchJobsAction: {},
DescribeBatchJobAction: {},
StartBatchJobAction: {},
CancelBatchJobAction: {},
AllAdminActions: {},
}
// IsValid - checks if action is valid or not.
func (action AdminAction) IsValid() bool {
_, ok := supportedAdminActions[action]
return ok
}
func createAdminActionConditionKeyMap() map[Action]condition.KeySet {
allSupportedAdminKeys := []condition.Key{}
for _, keyName := range condition.AllSupportedAdminKeys {
allSupportedAdminKeys = append(allSupportedAdminKeys, keyName.ToKey())
}
adminActionConditionKeyMap := map[Action]condition.KeySet{}
for act := range supportedAdminActions {
adminActionConditionKeyMap[Action(act)] = condition.NewKeySet(allSupportedAdminKeys...)
}
return adminActionConditionKeyMap
}
// adminActionConditionKeyMap - holds mapping of supported condition key for an action.
var adminActionConditionKeyMap = createAdminActionConditionKeyMap()
golang-github-minio-pkg-3.0.10/policy/bucket-policy-statement.go 0000664 0000000 0000000 00000011752 14655770405 0024637 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
import (
"strings"
"github.com/minio/pkg/v3/policy/condition"
)
// BPStatement - policy statement.
type BPStatement struct {
SID ID `json:"Sid,omitempty"`
Effect Effect `json:"Effect"`
Principal Principal `json:"Principal"`
Actions ActionSet `json:"Action"`
NotActions ActionSet `json:"NotAction,omitempty"`
Resources ResourceSet `json:"Resource"`
Conditions condition.Functions `json:"Condition,omitempty"`
}
// IsAllowed - checks given policy args is allowed to continue the Rest API.
func (statement BPStatement) IsAllowed(args BucketPolicyArgs) bool {
check := func() bool {
if !statement.Principal.Match(args.AccountName) {
return false
}
if (!statement.Actions.Match(args.Action) && !statement.Actions.IsEmpty()) ||
statement.NotActions.Match(args.Action) {
return false
}
resource := args.BucketName
if args.ObjectName != "" {
if !strings.HasPrefix(args.ObjectName, "/") {
resource += "/"
}
resource += args.ObjectName
}
if !statement.Resources.Match(resource, args.ConditionValues) {
return false
}
return statement.Conditions.Evaluate(args.ConditionValues)
}
return statement.Effect.IsAllowed(check())
}
// isValid - checks whether statement is valid or not.
func (statement BPStatement) isValid() error {
if !statement.Effect.IsValid() {
return Errorf("invalid Effect %v", statement.Effect)
}
if !statement.Principal.IsValid() {
return Errorf("invalid Principal %v", statement.Principal)
}
if len(statement.Actions) == 0 && len(statement.NotActions) == 0 {
return Errorf("Action must not be empty")
}
if len(statement.Resources) == 0 {
return Errorf("Resource must not be empty")
}
for action := range statement.Actions {
if action.IsObjectAction() {
if !statement.Resources.ObjectResourceExists() {
return Errorf("unsupported Resource found %v for action %v", statement.Resources, action)
}
} else {
if !statement.Resources.BucketResourceExists() {
return Errorf("unsupported Resource found %v for action %v", statement.Resources, action)
}
}
keys := statement.Conditions.Keys()
keyDiff := keys.Difference(IAMActionConditionKeyMap.Lookup(action))
if !keyDiff.IsEmpty() {
return Errorf("unsupported condition keys '%v' used for action '%v'", keyDiff, action)
}
}
return nil
}
// Validate - validates Statement is for given bucket or not.
func (statement BPStatement) Validate(bucketName string) error {
if err := statement.isValid(); err != nil {
return err
}
return statement.Resources.ValidateBucket(bucketName)
}
// Equals checks if two statements are equal
func (statement BPStatement) Equals(st BPStatement) bool {
if statement.Effect != st.Effect {
return false
}
if !statement.Principal.Equals(st.Principal) {
return false
}
if !statement.Actions.Equals(st.Actions) {
return false
}
if !statement.NotActions.Equals(st.NotActions) {
return false
}
if !statement.Resources.Equals(st.Resources) {
return false
}
if !statement.Conditions.Equals(st.Conditions) {
return false
}
return true
}
// Clone clones Statement structure
func (statement BPStatement) Clone() BPStatement {
return BPStatement{
SID: statement.SID,
Effect: statement.Effect,
Principal: statement.Principal.Clone(),
Actions: statement.Actions.Clone(),
NotActions: statement.NotActions.Clone(),
Resources: statement.Resources.Clone(),
Conditions: statement.Conditions.Clone(),
}
}
// NewBPStatement - creates new statement.
func NewBPStatement(sid ID, effect Effect, principal Principal, actionSet ActionSet, resourceSet ResourceSet, conditions condition.Functions) BPStatement {
return BPStatement{
SID: sid,
Effect: effect,
Principal: principal,
Actions: actionSet,
Resources: resourceSet,
Conditions: conditions,
}
}
// NewBPStatementWithNotAction - creates new statement with NotAction.
func NewBPStatementWithNotAction(sid ID, effect Effect, principal Principal, notActions ActionSet, resources ResourceSet, conditions condition.Functions) BPStatement {
return BPStatement{
SID: sid,
Effect: effect,
Principal: principal,
NotActions: notActions,
Resources: resources,
Conditions: conditions,
}
}
golang-github-minio-pkg-3.0.10/policy/bucket-policy-statement_test.go 0000664 0000000 0000000 00000036032 14655770405 0025674 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
import (
"encoding/json"
"net"
"reflect"
"testing"
"github.com/minio/pkg/v3/policy/condition"
)
func TestBPStatementIsAllowed(t *testing.T) {
case1Statement := NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(GetBucketLocationAction, PutObjectAction),
NewResourceSet(NewResource("*")),
condition.NewFunctions(),
)
case2Statement := NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(GetObjectAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
)
_, IPNet1, err := net.ParseCIDR("192.168.1.0/24")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func1, err := condition.NewIPAddressFunc(
condition.AWSSourceIP.ToKey(),
IPNet1,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case3Statement := NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(GetObjectAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1),
)
case4Statement := NewBPStatement("",
Deny,
NewPrincipal("*"),
NewActionSet(GetObjectAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1),
)
case5Statement := NewBPStatementWithNotAction(
"",
Allow,
NewPrincipal("*"),
NewActionSet(GetObjectAction, CreateBucketAction),
NewResourceSet(NewResource("mybucket/myobject*"), NewResource("mybucket")),
condition.NewFunctions(),
)
case6Statement := NewBPStatementWithNotAction(
"",
Deny,
NewPrincipal("*"),
NewActionSet(GetObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1),
)
anonGetBucketLocationArgs := BucketPolicyArgs{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: GetBucketLocationAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{},
}
anonPutObjectActionArgs := BucketPolicyArgs{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: PutObjectAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{
"x-amz-copy-source": {"mybucket/myobject"},
"SourceIp": {"192.168.1.10"},
},
ObjectName: "myobject",
}
anonGetObjectActionArgs := BucketPolicyArgs{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: GetObjectAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{},
ObjectName: "myobject",
}
getBucketLocationArgs := BucketPolicyArgs{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: GetBucketLocationAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{},
IsOwner: true,
}
putObjectActionArgs := BucketPolicyArgs{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: PutObjectAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{
"x-amz-copy-source": {"mybucket/myobject"},
"SourceIp": {"192.168.1.10"},
},
IsOwner: true,
ObjectName: "myobject",
}
getObjectActionArgs := BucketPolicyArgs{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: GetObjectAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{},
IsOwner: true,
ObjectName: "myobject",
}
testCases := []struct {
statement BPStatement
args BucketPolicyArgs
expectedResult bool
}{
{case1Statement, anonGetBucketLocationArgs, true},
{case1Statement, anonPutObjectActionArgs, true},
{case1Statement, anonGetObjectActionArgs, false},
{case1Statement, getBucketLocationArgs, true},
{case1Statement, putObjectActionArgs, true},
{case1Statement, getObjectActionArgs, false},
{case2Statement, anonGetBucketLocationArgs, false},
{case2Statement, anonPutObjectActionArgs, true},
{case2Statement, anonGetObjectActionArgs, true},
{case2Statement, getBucketLocationArgs, false},
{case2Statement, putObjectActionArgs, true},
{case2Statement, getObjectActionArgs, true},
{case3Statement, anonGetBucketLocationArgs, false},
{case3Statement, anonPutObjectActionArgs, true},
{case3Statement, anonGetObjectActionArgs, false},
{case3Statement, getBucketLocationArgs, false},
{case3Statement, putObjectActionArgs, true},
{case3Statement, getObjectActionArgs, false},
{case4Statement, anonGetBucketLocationArgs, true},
{case4Statement, anonPutObjectActionArgs, false},
{case4Statement, anonGetObjectActionArgs, true},
{case4Statement, getBucketLocationArgs, true},
{case4Statement, putObjectActionArgs, false},
{case4Statement, getObjectActionArgs, true},
{case5Statement, anonGetBucketLocationArgs, true},
{case5Statement, anonPutObjectActionArgs, true},
{case5Statement, anonGetObjectActionArgs, false},
{case5Statement, getBucketLocationArgs, true},
{case5Statement, getObjectActionArgs, false},
{case5Statement, putObjectActionArgs, true},
{case6Statement, anonGetBucketLocationArgs, true},
{case6Statement, anonPutObjectActionArgs, false},
{case6Statement, anonGetObjectActionArgs, true},
{case6Statement, getBucketLocationArgs, true},
{case6Statement, putObjectActionArgs, false},
{case6Statement, getObjectActionArgs, true},
}
for i, testCase := range testCases {
result := testCase.statement.IsAllowed(testCase.args)
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestBPStatementIsValid(t *testing.T) {
_, IPNet1, err := net.ParseCIDR("192.168.1.0/24")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func1, err := condition.NewIPAddressFunc(
condition.AWSSourceIP.ToKey(),
IPNet1,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func2, err := condition.NewStringEqualsFunc(
"",
condition.S3XAmzCopySource.ToKey(),
"mybucket/myobject",
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
statement BPStatement
expectErr bool
}{
// Invalid effect error.
{NewBPStatement("",
Effect("foo"),
NewPrincipal("*"),
NewActionSet(GetBucketLocationAction, PutObjectAction),
NewResourceSet(NewResource("*")),
condition.NewFunctions(),
), true},
// Invalid principal error.
{NewBPStatement("",
Allow,
NewPrincipal(),
NewActionSet(GetBucketLocationAction, PutObjectAction),
NewResourceSet(NewResource("*")),
condition.NewFunctions(),
), true},
// Empty actions error.
{NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(),
NewResourceSet(NewResource("*")),
condition.NewFunctions(),
), true},
// Empty resources error.
{NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(GetBucketLocationAction, PutObjectAction),
NewResourceSet(),
condition.NewFunctions(),
), true},
// Unsupported resource found for object action.
{NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(GetBucketLocationAction, PutObjectAction),
NewResourceSet(NewResource("mybucket")),
condition.NewFunctions(),
), true},
// Unsupported resource found for bucket action.
{NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(GetBucketLocationAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
), true},
// Unsupported condition key for action.
{NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(GetObjectAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1, func2),
), true},
{NewBPStatement("",
Deny,
NewPrincipal("*"),
NewActionSet(GetObjectAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1),
), false},
{BPStatement{
SID: "",
Effect: Allow,
Principal: NewPrincipal("*"),
NotActions: NewActionSet(GetObjectAction),
Resources: NewResourceSet(NewResource("mybucket/myobject*")),
Conditions: condition.NewFunctions(),
}, false},
}
for i, testCase := range testCases {
err := testCase.statement.isValid()
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
}
}
func TestBPStatementUnmarshalJSONAndValidate(t *testing.T) {
case1Data := []byte(`{
"Sid": "SomeId1",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
}`)
case1Statement := NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
)
case1Statement.SID = "SomeId1"
case2Data := []byte(`{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*",
"Condition": {
"Null": {
"s3:x-amz-copy-source": true
}
}
}`)
func1, err := condition.NewNullFunc(
condition.S3XAmzCopySource.ToKey(),
true,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Statement := NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1),
)
case3Data := []byte(`{
"Effect": "Deny",
"Principal": {
"AWS": "*"
},
"Action": [
"s3:PutObject",
"s3:GetObject"
],
"Resource": "arn:aws:s3:::mybucket/myobject*",
"Condition": {
"Null": {
"s3:x-amz-server-side-encryption": "false"
}
}
}`)
func2, err := condition.NewNullFunc(
condition.S3XAmzServerSideEncryption.ToKey(),
false,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case3Statement := NewBPStatement("",
Deny,
NewPrincipal("*"),
NewActionSet(PutObjectAction, GetObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func2),
)
case4Data := []byte(`{
"Effect": "Allow",
"Principal": "Q3AM3UQ867SPQQA43P2F",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
}`)
case5Data := []byte(`{
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
}`)
case6Data := []byte(`{
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
}`)
case7Data := []byte(`{
"Effect": "Allow",
"Principal": "*",
"Resource": "arn:aws:s3:::mybucket/myobject*"
}`)
case8Data := []byte(`{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:PutObject"
}`)
case9Data := []byte(`{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*",
"Condition": {
}
}`)
case10Data := []byte(`{
"Effect": "Deny",
"Principal": {
"AWS": "*"
},
"Action": [
"s3:PutObject",
"s3:GetObject"
],
"Resource": "arn:aws:s3:::mybucket/myobject*",
"Condition": {
"StringEquals": {
"s3:x-amz-copy-source": "yourbucket/myobject*"
}
}
}`)
case11Data := []byte(`{
"Effect": "Deny",
"Principal": "*",
"NotAction": [
"s3:PutObject",
"s3:GetObject"
],
"Resource": "arn:aws:s3:::mybucket/myobject*"
}`)
case11Statement := BPStatement{
Effect: Deny,
Principal: NewPrincipal("*"),
NotActions: NewActionSet(GetObjectAction, PutObjectAction),
Resources: NewResourceSet(NewResource("mybucket/myobject*")),
Conditions: condition.NewFunctions(),
}
testCases := []struct {
data []byte
expectedResult BPStatement
expectUnmarshalErr bool
bucket string
expectValidationErr bool
}{
{case1Data, case1Statement, false, "mybucket", false},
{case2Data, case2Statement, false, "mybucket", false},
{case3Data, case3Statement, false, "mybucket", false},
// JSON unmarshaling error.
{case4Data, BPStatement{}, true, "mybucket", true},
// Invalid effect error.
{case5Data, BPStatement{}, false, "mybucket", true},
// empty principal error.
{case6Data, BPStatement{}, false, "mybucket", true},
// Empty action error.
{case7Data, BPStatement{}, false, "mybucket", true},
// Empty resource error.
{case8Data, BPStatement{}, false, "mybucket", true},
// Empty condition error.
{case9Data, BPStatement{}, true, "mybucket", false},
// Unsupported condition key error.
{case10Data, BPStatement{}, false, "mybucket", true},
{case11Data, case11Statement, false, "mybucket", false},
}
for i, testCase := range testCases {
var result BPStatement
expectErr := (json.Unmarshal(testCase.data, &result) != nil)
if expectErr != testCase.expectUnmarshalErr {
t.Errorf("case %v: error during unmarshal: expected: %v, got: %v", i+1, testCase.expectUnmarshalErr, expectErr)
}
expectErr = (result.Validate(testCase.bucket) != nil)
if expectErr != testCase.expectValidationErr {
t.Errorf("case %v: error during validation: expected: %v, got: %v", i+1, testCase.expectValidationErr, expectErr)
}
if !testCase.expectUnmarshalErr && !testCase.expectValidationErr {
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v", i+1, testCase.expectedResult, result)
}
}
}
}
func TestBPStatementValidate(t *testing.T) {
case1Statement := NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
)
func1, err := condition.NewNullFunc(
condition.S3XAmzCopySource.ToKey(),
true,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func2, err := condition.NewNullFunc(
condition.S3XAmzServerSideEncryption.ToKey(),
false,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Statement := NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(GetObjectAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1, func2),
)
testCases := []struct {
statement BPStatement
bucketName string
expectErr bool
}{
{case1Statement, "mybucket", false},
{case2Statement, "mybucket", true},
{case1Statement, "yourbucket", true},
}
for i, testCase := range testCases {
err := testCase.statement.Validate(testCase.bucketName)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
}
}
golang-github-minio-pkg-3.0.10/policy/bucket-policy.go 0000664 0000000 0000000 00000012014 14655770405 0022625 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
import (
"encoding/json"
"io"
)
// BucketPolicyArgs - arguments to policy to check whether it is allowed
type BucketPolicyArgs struct {
AccountName string `json:"account"`
Groups []string `json:"groups"`
Action Action `json:"action"`
BucketName string `json:"bucket"`
ConditionValues map[string][]string `json:"conditions"`
IsOwner bool `json:"owner"`
ObjectName string `json:"object"`
}
// BucketPolicy - bucket policy.
type BucketPolicy struct {
ID ID `json:"ID,omitempty"`
Version string
Statements []BPStatement `json:"Statement"`
}
// IsAllowed - checks given policy args is allowed to continue the Rest API.
func (policy BucketPolicy) IsAllowed(args BucketPolicyArgs) bool {
// Check all deny statements. If any one statement denies, return false.
for _, statement := range policy.Statements {
if statement.Effect == Deny {
if !statement.IsAllowed(args) {
return false
}
}
}
// For owner, its allowed by default.
if args.IsOwner {
return true
}
// Check all allow statements. If any one statement allows, return true.
for _, statement := range policy.Statements {
if statement.Effect == Allow {
if statement.IsAllowed(args) {
return true
}
}
}
return false
}
// IsEmpty - returns whether policy is empty or not.
func (policy BucketPolicy) IsEmpty() bool {
return len(policy.Statements) == 0
}
// isValid - checks if Policy is valid or not.
func (policy BucketPolicy) isValid() error {
if policy.Version != DefaultVersion && policy.Version != "" {
return Errorf("invalid version '%v'", policy.Version)
}
for _, statement := range policy.Statements {
if err := statement.isValid(); err != nil {
return err
}
}
return nil
}
// MarshalJSON - encodes Policy to JSON data.
func (policy BucketPolicy) MarshalJSON() ([]byte, error) {
if err := policy.isValid(); err != nil {
return nil, err
}
// subtype to avoid recursive call to MarshalJSON()
type subPolicy BucketPolicy
return json.Marshal(subPolicy(policy))
}
func (policy *BucketPolicy) dropDuplicateStatements() {
dups := make(map[int]struct{})
for i := range policy.Statements {
if _, ok := dups[i]; ok {
// i is already a duplicate of some statement, so we do not need to
// compare with it.
continue
}
for j := i + 1; j < len(policy.Statements); j++ {
if !policy.Statements[i].Equals(policy.Statements[j]) {
continue
}
// save duplicate statement index for removal.
dups[j] = struct{}{}
}
}
// remove duplicate items from the slice.
var c int
for i := range policy.Statements {
if _, ok := dups[i]; ok {
continue
}
policy.Statements[c] = policy.Statements[i]
c++
}
policy.Statements = policy.Statements[:c]
}
// UnmarshalJSON - decodes JSON data to Policy.
func (policy *BucketPolicy) UnmarshalJSON(data []byte) error {
// subtype to avoid recursive call to UnmarshalJSON()
type subPolicy BucketPolicy
var sp subPolicy
if err := json.Unmarshal(data, &sp); err != nil {
return err
}
p := BucketPolicy(sp)
if err := p.isValid(); err != nil {
return err
}
p.dropDuplicateStatements()
*policy = p
return nil
}
// Validate - validates all statements are for given bucket or not.
func (policy BucketPolicy) Validate(bucketName string) error {
if err := policy.isValid(); err != nil {
return err
}
for _, statement := range policy.Statements {
if err := statement.Validate(bucketName); err != nil {
return err
}
}
return nil
}
// ParseBucketPolicyConfig - parses data in given reader to Policy.
func ParseBucketPolicyConfig(reader io.Reader, bucketName string) (*BucketPolicy, error) {
var policy BucketPolicy
decoder := json.NewDecoder(reader)
decoder.DisallowUnknownFields()
if err := decoder.Decode(&policy); err != nil {
return nil, Errorf("%w", err)
}
err := policy.Validate(bucketName)
return &policy, err
}
// Equals returns true if the two policies are identical
func (policy *BucketPolicy) Equals(p BucketPolicy) bool {
if policy.ID != p.ID || policy.Version != p.Version {
return false
}
if len(policy.Statements) != len(p.Statements) {
return false
}
for i, st := range policy.Statements {
if !p.Statements[i].Equals(st) {
return false
}
}
return true
}
golang-github-minio-pkg-3.0.10/policy/bucket-policy_test.go 0000664 0000000 0000000 00000074032 14655770405 0023674 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
import (
"encoding/json"
"net"
"reflect"
"testing"
"github.com/minio/pkg/v3/policy/condition"
)
func TestBucketPolicyIsAllowed(t *testing.T) {
case1Policy := BucketPolicy{
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(GetBucketLocationAction, PutObjectAction),
NewResourceSet(NewResource("*")),
condition.NewFunctions(),
),
},
}
case2Policy := BucketPolicy{
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(GetObjectAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
_, IPNet, err := net.ParseCIDR("192.168.1.0/24")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func1, err := condition.NewIPAddressFunc(
condition.AWSSourceIP.ToKey(),
IPNet,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case3Policy := BucketPolicy{
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(GetObjectAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1),
),
},
}
case4Policy := BucketPolicy{
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Deny,
NewPrincipal("*"),
NewActionSet(GetObjectAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1),
),
},
}
anonGetBucketLocationArgs := BucketPolicyArgs{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: GetBucketLocationAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{},
}
anonPutObjectActionArgs := BucketPolicyArgs{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: PutObjectAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{
"x-amz-copy-source": {"mybucket/myobject"},
"SourceIp": {"192.168.1.10"},
},
ObjectName: "myobject",
}
anonGetObjectActionArgs := BucketPolicyArgs{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: GetObjectAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{},
ObjectName: "myobject",
}
getBucketLocationArgs := BucketPolicyArgs{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: GetBucketLocationAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{},
IsOwner: true,
}
putObjectActionArgs := BucketPolicyArgs{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: PutObjectAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{
"x-amz-copy-source": {"mybucket/myobject"},
"SourceIp": {"192.168.1.10"},
},
IsOwner: true,
ObjectName: "myobject",
}
getObjectActionArgs := BucketPolicyArgs{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: GetObjectAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{},
IsOwner: true,
ObjectName: "myobject",
}
testCases := []struct {
policy BucketPolicy
args BucketPolicyArgs
expectedResult bool
}{
{case1Policy, anonGetBucketLocationArgs, true},
{case1Policy, anonPutObjectActionArgs, true},
{case1Policy, anonGetObjectActionArgs, false},
{case1Policy, getBucketLocationArgs, true},
{case1Policy, putObjectActionArgs, true},
{case1Policy, getObjectActionArgs, true},
{case2Policy, anonGetBucketLocationArgs, false},
{case2Policy, anonPutObjectActionArgs, true},
{case2Policy, anonGetObjectActionArgs, true},
{case2Policy, getBucketLocationArgs, true},
{case2Policy, putObjectActionArgs, true},
{case2Policy, getObjectActionArgs, true},
{case3Policy, anonGetBucketLocationArgs, false},
{case3Policy, anonPutObjectActionArgs, true},
{case3Policy, anonGetObjectActionArgs, false},
{case3Policy, getBucketLocationArgs, true},
{case3Policy, putObjectActionArgs, true},
{case3Policy, getObjectActionArgs, true},
{case4Policy, anonGetBucketLocationArgs, false},
{case4Policy, anonPutObjectActionArgs, false},
{case4Policy, anonGetObjectActionArgs, false},
{case4Policy, getBucketLocationArgs, true},
{case4Policy, putObjectActionArgs, false},
{case4Policy, getObjectActionArgs, true},
}
for i, testCase := range testCases {
result := testCase.policy.IsAllowed(testCase.args)
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestBucketPolicyIsEmpty(t *testing.T) {
case1Policy := BucketPolicy{
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
case2Policy := BucketPolicy{
ID: "MyPolicyForMyBucket",
Version: DefaultVersion,
}
testCases := []struct {
policy BucketPolicy
expectedResult bool
}{
{case1Policy, false},
{case2Policy, true},
}
for i, testCase := range testCases {
result := testCase.policy.IsEmpty()
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestBucketPolicyIsValid(t *testing.T) {
case1Policy := BucketPolicy{
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
case2Policy := BucketPolicy{
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
NewBPStatement("",
Deny,
NewPrincipal("*"),
NewActionSet(GetObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
case3Policy := BucketPolicy{
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
NewBPStatement("",
Deny,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/yourobject*")),
condition.NewFunctions(),
),
},
}
func1, err := condition.NewNullFunc(
condition.S3XAmzCopySource.ToKey(),
true,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func2, err := condition.NewNullFunc(
condition.S3XAmzServerSideEncryption.ToKey(),
false,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case4Policy := BucketPolicy{
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1),
),
NewBPStatement("",
Deny,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func2),
),
},
}
case5Policy := BucketPolicy{
Version: "17-10-2012",
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
case6Policy := BucketPolicy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(GetObjectAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1, func2),
),
},
}
case7Policy := BucketPolicy{
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
NewBPStatement("",
Deny,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
case8Policy := BucketPolicy{
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
testCases := []struct {
policy BucketPolicy
expectErr bool
}{
{case1Policy, false},
// allowed duplicate principal.
{case2Policy, false},
// allowed duplicate principal and action.
{case3Policy, false},
// allowed duplicate principal, action and resource.
{case4Policy, false},
// Invalid version error.
{case5Policy, true},
// Invalid statement error.
{case6Policy, true},
// Duplicate statement success different effects.
{case7Policy, false},
// Duplicate statement success, duplicate statement dropped.
{case8Policy, false},
}
for i, testCase := range testCases {
err := testCase.policy.isValid()
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
}
}
func TestBucketPolicyMarshalJSON(t *testing.T) {
case1Policy := BucketPolicy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
case1Policy.Statements[0].SID = "SomeId1"
case1Data := []byte(`{"ID":"MyPolicyForMyBucket1","Version":"2012-10-17","Statement":[{"Sid":"SomeId1","Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:PutObject"],"Resource":["arn:aws:s3:::mybucket/myobject*"]}]}`)
_, IPNet1, err := net.ParseCIDR("192.168.1.0/24")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func1, err := condition.NewIPAddressFunc(
condition.AWSSourceIP.ToKey(),
IPNet1,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Policy := BucketPolicy{
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
NewBPStatement("",
Deny,
NewPrincipal("*"),
NewActionSet(GetObjectAction),
NewResourceSet(NewResource("mybucket/yourobject*")),
condition.NewFunctions(func1),
),
},
}
case2Data := []byte(`{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:PutObject"],"Resource":["arn:aws:s3:::mybucket/myobject*"]},{"Effect":"Deny","Principal":{"AWS":["*"]},"Action":["s3:GetObject"],"Resource":["arn:aws:s3:::mybucket/yourobject*"],"Condition":{"IpAddress":{"aws:SourceIp":["192.168.1.0/24"]}}}]}`)
case3Policy := BucketPolicy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("Q3AM3UQ867SPQQA43P2F"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
case3Data := []byte(`{"ID":"MyPolicyForMyBucket1","Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["Q3AM3UQ867SPQQA43P2F"]},"Action":["s3:PutObject"],"Resource":["arn:aws:s3:::mybucket/myobject*"]},{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:PutObject"],"Resource":["arn:aws:s3:::mybucket/myobject*"]}]}`)
case4Policy := BucketPolicy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(GetObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
case4Data := []byte(`{"ID":"MyPolicyForMyBucket1","Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:PutObject"],"Resource":["arn:aws:s3:::mybucket/myobject*"]},{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:GetObject"],"Resource":["arn:aws:s3:::mybucket/myobject*"]}]}`)
case5Policy := BucketPolicy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/yourobject*")),
condition.NewFunctions(),
),
},
}
case5Data := []byte(`{"ID":"MyPolicyForMyBucket1","Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:PutObject"],"Resource":["arn:aws:s3:::mybucket/myobject*"]},{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:PutObject"],"Resource":["arn:aws:s3:::mybucket/yourobject*"]}]}`)
_, IPNet2, err := net.ParseCIDR("192.168.2.0/24")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func2, err := condition.NewIPAddressFunc(
condition.AWSSourceIP.ToKey(),
IPNet2,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case6Policy := BucketPolicy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1),
),
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func2),
),
},
}
case6Data := []byte(`{"ID":"MyPolicyForMyBucket1","Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:PutObject"],"Resource":["arn:aws:s3:::mybucket/myobject*"],"Condition":{"IpAddress":{"aws:SourceIp":["192.168.1.0/24"]}}},{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:PutObject"],"Resource":["arn:aws:s3:::mybucket/myobject*"],"Condition":{"IpAddress":{"aws:SourceIp":["192.168.2.0/24"]}}}]}`)
case7Policy := BucketPolicy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(GetBucketLocationAction),
NewResourceSet(NewResource("mybucket")),
condition.NewFunctions(),
),
},
}
case7Data := []byte(`{"ID":"MyPolicyForMyBucket1","Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:GetBucketLocation"],"Resource":["arn:aws:s3:::mybucket"]}]}`)
case8Policy := BucketPolicy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(GetBucketLocationAction),
NewResourceSet(NewResource("*")),
condition.NewFunctions(),
),
},
}
case8Data := []byte(`{"ID":"MyPolicyForMyBucket1","Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"AWS":["*"]},"Action":["s3:GetBucketLocation"],"Resource":["arn:aws:s3:::*"]}]}`)
func3, err := condition.NewNullFunc(
condition.S3XAmzCopySource.ToKey(),
true,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case9Policy := BucketPolicy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(GetObjectAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1, func2, func3),
),
},
}
testCases := []struct {
policy BucketPolicy
expectedResult []byte
expectErr bool
}{
{case1Policy, case1Data, false},
{case2Policy, case2Data, false},
{case3Policy, case3Data, false},
{case4Policy, case4Data, false},
{case5Policy, case5Data, false},
{case6Policy, case6Data, false},
{case7Policy, case7Data, false},
{case8Policy, case8Data, false},
{case9Policy, nil, true},
}
for i, testCase := range testCases {
result, err := json.Marshal(testCase.policy)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v", i+1, string(testCase.expectedResult), string(result))
}
}
}
}
func TestBucketPolicyUnmarshalJSON(t *testing.T) {
case1Data := []byte(`{
"ID": "MyPolicyForMyBucket1",
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SomeId1",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
}
]
}`)
case1Policy := BucketPolicy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
case1Policy.Statements[0].SID = "SomeId1"
case2Data := []byte(`{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
},
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::mybucket/yourobject*",
"Condition": {
"IpAddress": {
"aws:SourceIp": "192.168.1.0/24"
}
}
}
]
}`)
_, IPNet1, err := net.ParseCIDR("192.168.1.0/24")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func1, err := condition.NewIPAddressFunc(
condition.AWSSourceIP.ToKey(),
IPNet1,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Policy := BucketPolicy{
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
NewBPStatement("",
Deny,
NewPrincipal("*"),
NewActionSet(GetObjectAction),
NewResourceSet(NewResource("mybucket/yourobject*")),
condition.NewFunctions(func1),
),
},
}
case3Data := []byte(`{
"ID": "MyPolicyForMyBucket1",
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": [
"Q3AM3UQ867SPQQA43P2F"
]
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
},
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
}
]
}`)
case3Policy := BucketPolicy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("Q3AM3UQ867SPQQA43P2F"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
case4Data := []byte(`{
"ID": "MyPolicyForMyBucket1",
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
},
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
}
]
}`)
case4Policy := BucketPolicy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(GetObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
case5Data := []byte(`{
"ID": "MyPolicyForMyBucket1",
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
},
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/yourobject*"
}
]
}`)
case5Policy := BucketPolicy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/yourobject*")),
condition.NewFunctions(),
),
},
}
case6Data := []byte(`{
"ID": "MyPolicyForMyBucket1",
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*",
"Condition": {
"IpAddress": {
"aws:SourceIp": "192.168.1.0/24"
}
}
},
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*",
"Condition": {
"IpAddress": {
"aws:SourceIp": "192.168.2.0/24"
}
}
}
]
}`)
_, IPNet2, err := net.ParseCIDR("192.168.2.0/24")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func2, err := condition.NewIPAddressFunc(
condition.AWSSourceIP.ToKey(),
IPNet2,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case6Policy := BucketPolicy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1),
),
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func2),
),
},
}
case7Data := []byte(`{
"ID": "MyPolicyForMyBucket1",
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetBucketLocation",
"Resource": "arn:aws:s3:::mybucket"
}
]
}`)
case7Policy := BucketPolicy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(GetBucketLocationAction),
NewResourceSet(NewResource("mybucket")),
condition.NewFunctions(),
),
},
}
case8Data := []byte(`{
"ID": "MyPolicyForMyBucket1",
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetBucketLocation",
"Resource": "arn:aws:s3:::*"
}
]
}`)
case8Policy := BucketPolicy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(GetBucketLocationAction),
NewResourceSet(NewResource("*")),
condition.NewFunctions(),
),
},
}
case9Data := []byte(`{
"ID": "MyPolicyForMyBucket1",
"Version": "17-10-2012",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
}
]
}`)
case10Data := []byte(`{
"ID": "MyPolicyForMyBucket1",
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
},
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
}
]
}`)
case10Policy := BucketPolicy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
case11Data := []byte(`{
"ID": "MyPolicyForMyBucket1",
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
},
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
}
]
}`)
case11Policy := BucketPolicy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
NewBPStatement("",
Deny,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
testCases := []struct {
data []byte
expectedResult BucketPolicy
expectErr bool
}{
{case1Data, case1Policy, false},
{case2Data, case2Policy, false},
{case3Data, case3Policy, false},
{case4Data, case4Policy, false},
{case5Data, case5Policy, false},
{case6Data, case6Policy, false},
{case7Data, case7Policy, false},
{case8Data, case8Policy, false},
// Invalid version error.
{case9Data, BucketPolicy{}, true},
// Duplicate statement success, duplicate statement removed.
{case10Data, case10Policy, false},
// Duplicate statement success (Effect differs).
{case11Data, case11Policy, false},
}
for i, testCase := range testCases {
var result BucketPolicy
err := json.Unmarshal(testCase.data, &result)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Errorf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Errorf("case %v: result: expected: %v, got: %v", i+1, testCase.expectedResult, result)
}
}
}
}
func TestBucketPolicyValidate(t *testing.T) {
case1Policy := BucketPolicy{
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
func1, err := condition.NewNullFunc(
condition.S3XAmzCopySource.ToKey(),
true,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func2, err := condition.NewNullFunc(
condition.S3XAmzServerSideEncryption.ToKey(),
false,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Policy := BucketPolicy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []BPStatement{
NewBPStatement("",
Allow,
NewPrincipal("*"),
NewActionSet(GetObjectAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1, func2),
),
},
}
testCases := []struct {
policy BucketPolicy
bucketName string
expectErr bool
}{
{case1Policy, "mybucket", false},
{case2Policy, "yourbucket", true},
{case1Policy, "yourbucket", true},
}
for i, testCase := range testCases {
err := testCase.policy.Validate(testCase.bucketName)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
}
}
golang-github-minio-pkg-3.0.10/policy/condition/ 0000775 0000000 0000000 00000000000 14655770405 0021514 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/policy/condition/boolfunc.go 0000664 0000000 0000000 00000006030 14655770405 0023651 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package condition
import (
"fmt"
"reflect"
"strconv"
)
// booleanFunc - Bool condition function. It checks whether Key is true or false.
// https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html#Conditions_Boolean
type booleanFunc struct {
k Key
value string
}
// evaluate() - evaluates to check whether Key is present in given values or not.
// Depending on condition boolean value, this function returns true or false.
func (f booleanFunc) evaluate(values map[string][]string) bool {
rvalues := getValuesByKey(values, f.k)
if len(rvalues) == 0 {
return false
}
return f.value == rvalues[0]
}
// key() - returns condition key which is used by this condition function.
func (f booleanFunc) key() Key {
return f.k
}
// name() - returns "Bool" condition name.
func (f booleanFunc) name() name {
return name{name: boolean}
}
func (f booleanFunc) String() string {
return fmt.Sprintf("%v:%v:%v", boolean, f.k, f.value)
}
// toMap - returns map representation of this function.
func (f booleanFunc) toMap() map[Key]ValueSet {
if !f.k.IsValid() {
return nil
}
return map[Key]ValueSet{
f.k: NewValueSet(NewStringValue(f.value)),
}
}
func (f booleanFunc) clone() Function {
return &booleanFunc{
k: f.k,
value: f.value,
}
}
func newBooleanFunc(key Key, values ValueSet, _ string) (Function, error) {
if !key.Is(AWSSecureTransport) {
return nil, fmt.Errorf("only %v key is allowed for %v condition", AWSSecureTransport, boolean)
}
if len(values) != 1 {
return nil, fmt.Errorf("only one value is allowed for boolean condition")
}
var value Value
for v := range values {
value = v
switch v.GetType() {
case reflect.Bool:
if _, err := v.GetBool(); err != nil {
return nil, err
}
case reflect.String:
s, err := v.GetString()
if err != nil {
return nil, err
}
if _, err = strconv.ParseBool(s); err != nil {
return nil, fmt.Errorf("value must be a boolean string for boolean condition")
}
default:
return nil, fmt.Errorf("value must be a boolean for boolean condition")
}
}
return &booleanFunc{key, value.String()}, nil
}
// NewBoolFunc - returns new Bool function.
func NewBoolFunc(key Key, value bool) (Function, error) {
return newBooleanFunc(key, NewValueSet(NewBoolValue(value)), "")
}
golang-github-minio-pkg-3.0.10/policy/condition/boolfunc_test.go 0000664 0000000 0000000 00000014220 14655770405 0024710 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package condition
import (
"reflect"
"testing"
)
func TestBooleanFuncEvaluate(t *testing.T) {
case1Function, err := newBooleanFunc(AWSSecureTransport.ToKey(), NewValueSet(NewBoolValue(true)), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Function, err := newBooleanFunc(AWSSecureTransport.ToKey(), NewValueSet(NewBoolValue(false)), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
function Function
values map[string][]string
expectedResult bool
}{
{case1Function, map[string][]string{"SecureTransport": {"true"}}, true},
{case2Function, map[string][]string{"SecureTransport": {"false"}}, true},
}
for i, testCase := range testCases {
result := testCase.function.evaluate(testCase.values)
if result != testCase.expectedResult {
t.Errorf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
if _, err := newBooleanFunc(S3Prefix.ToKey(), NewValueSet(NewBoolValue(true)), ""); err == nil {
t.Errorf("error expected")
}
}
func TestBooleanFuncKey(t *testing.T) {
case1Function, err := newBooleanFunc(AWSSecureTransport.ToKey(), NewValueSet(NewBoolValue(true)), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
function Function
expectedResult Key
}{
{case1Function, AWSSecureTransport.ToKey()},
}
for i, testCase := range testCases {
result := testCase.function.key()
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestBooleanFuncName(t *testing.T) {
case1Function, err := newBooleanFunc(AWSSecureTransport.ToKey(), NewValueSet(NewBoolValue(true)), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
function Function
expectedResult name
}{
{case1Function, name{name: boolean}},
}
for i, testCase := range testCases {
result := testCase.function.name()
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestBooleanFuncToMap(t *testing.T) {
case1Function, err := newBooleanFunc(AWSSecureTransport.ToKey(), NewValueSet(NewBoolValue(true)), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case1Result := map[Key]ValueSet{
AWSSecureTransport.ToKey(): NewValueSet(NewStringValue("true")),
}
case2Function, err := newBooleanFunc(AWSSecureTransport.ToKey(), NewValueSet(NewBoolValue(false)), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Result := map[Key]ValueSet{
AWSSecureTransport.ToKey(): NewValueSet(NewStringValue("false")),
}
testCases := []struct {
f Function
expectedResult map[Key]ValueSet
}{
{case1Function, case1Result},
{case2Function, case2Result},
}
for i, testCase := range testCases {
result := testCase.f.toMap()
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestBooleanFuncClone(t *testing.T) {
case1Function, err := newBooleanFunc(AWSSecureTransport.ToKey(), NewValueSet(NewBoolValue(true)), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case1Result := &booleanFunc{k: AWSSecureTransport.ToKey(), value: "true"}
case2Function, err := newBooleanFunc(AWSSecureTransport.ToKey(), NewValueSet(NewBoolValue(false)), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Result := &booleanFunc{k: AWSSecureTransport.ToKey(), value: "false"}
testCases := []struct {
f Function
expectedResult Function
}{
{case1Function, case1Result},
{case2Function, case2Result},
}
for i, testCase := range testCases {
result := testCase.f.clone()
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestNewBooleanFunc(t *testing.T) {
case1Function, err := newBooleanFunc(AWSSecureTransport.ToKey(), NewValueSet(NewBoolValue(true)), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Function, err := newBooleanFunc(AWSSecureTransport.ToKey(), NewValueSet(NewBoolValue(false)), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
key Key
values ValueSet
expectedResult Function
expectErr bool
}{
{AWSSecureTransport.ToKey(), NewValueSet(NewBoolValue(true)), case1Function, false},
{AWSSecureTransport.ToKey(), NewValueSet(NewStringValue("false")), case2Function, false},
// Multiple values error.
{AWSSecureTransport.ToKey(), NewValueSet(NewStringValue("true"), NewStringValue("false")), nil, true},
// Invalid boolean string error.
{AWSSecureTransport.ToKey(), NewValueSet(NewStringValue("foo")), nil, true},
// Invalid value error.
{AWSSecureTransport.ToKey(), NewValueSet(NewIntValue(7)), nil, true},
}
for i, testCase := range testCases {
result, err := newBooleanFunc(testCase.key, testCase.values, "")
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
}
golang-github-minio-pkg-3.0.10/policy/condition/datefunc.go 0000664 0000000 0000000 00000012724 14655770405 0023642 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package condition
import (
"fmt"
"reflect"
"time"
)
type dateFunc struct {
n name
k Key
value time.Time
c condition
}
func (f dateFunc) evaluate(values map[string][]string) bool {
rvalues := getValuesByKey(values, f.k)
if len(rvalues) == 0 {
return false
}
t, err := time.Parse(time.RFC3339, rvalues[0])
if err != nil {
return false
}
switch f.c {
case equals:
return f.value.Equal(t)
case notEquals:
return !f.value.Equal(t)
case greaterThan:
return t.After(f.value)
case greaterThanEquals:
return t.After(f.value) || t.Equal(f.value)
case lessThan:
return t.Before(f.value)
case lessThanEquals:
return t.Before(f.value) || t.Equal(f.value)
}
// This never happens.
return false
}
func (f dateFunc) key() Key {
return f.k
}
func (f dateFunc) name() name {
return f.n
}
func (f dateFunc) String() string {
return fmt.Sprintf("%v:%v:%v", f.n, f.k, f.value.Format(time.RFC3339))
}
func (f dateFunc) toMap() map[Key]ValueSet {
if !f.k.IsValid() {
return nil
}
values := NewValueSet()
values.Add(NewStringValue(f.value.Format(time.RFC3339)))
return map[Key]ValueSet{
f.k: values,
}
}
func (f dateFunc) clone() Function {
return &dateFunc{
n: f.n,
k: f.k,
value: f.value,
c: f.c,
}
}
func valueToTime(n string, values ValueSet) (v time.Time, err error) {
if len(values) != 1 {
return v, fmt.Errorf("only one value is allowed for %s condition", n)
}
for vs := range values {
switch vs.GetType() {
case reflect.String:
s, err := vs.GetString()
if err != nil {
return v, err
}
if v, err = time.Parse(time.RFC3339, s); err != nil {
return v, fmt.Errorf("value %s must be a time.Time string for %s condition: %w", vs, n, err)
}
default:
return v, fmt.Errorf("value %s must be a time.Time for %s condition", vs, n)
}
}
return v, nil
}
func newDateFunc(n string, key Key, values ValueSet, cond condition) (Function, error) {
v, err := valueToTime(n, values)
if err != nil {
return nil, err
}
return &dateFunc{
n: name{name: n},
k: key,
value: v,
c: cond,
}, nil
}
// newDateEqualsFunc - returns new DateEquals function.
func newDateEqualsFunc(key Key, values ValueSet, _ string) (Function, error) {
return newDateFunc(dateEquals, key, values, equals)
}
// NewDateEqualsFunc - returns new DateEquals function.
func NewDateEqualsFunc(key Key, value time.Time) (Function, error) {
return &dateFunc{n: name{name: dateEquals}, k: key, value: value, c: equals}, nil
}
// newDateNotEqualsFunc - returns new DateNotEquals function.
func newDateNotEqualsFunc(key Key, values ValueSet, _ string) (Function, error) {
return newDateFunc(dateNotEquals, key, values, notEquals)
}
// NewDateNotEqualsFunc - returns new DateNotEquals function.
func NewDateNotEqualsFunc(key Key, value time.Time) (Function, error) {
return &dateFunc{n: name{name: dateNotEquals}, k: key, value: value, c: notEquals}, nil
}
// newDateGreaterThanFunc - returns new DateGreaterThan function.
func newDateGreaterThanFunc(key Key, values ValueSet, _ string) (Function, error) {
return newDateFunc(dateGreaterThan, key, values, greaterThan)
}
// NewDateGreaterThanFunc - returns new DateGreaterThan function.
func NewDateGreaterThanFunc(key Key, value time.Time) (Function, error) {
return &dateFunc{n: name{name: dateGreaterThan}, k: key, value: value, c: greaterThan}, nil
}
// newDateGreaterThanEqualsFunc - returns new DateGreaterThanEquals function.
func newDateGreaterThanEqualsFunc(key Key, values ValueSet, _ string) (Function, error) {
return newDateFunc(dateGreaterThanEquals, key, values, greaterThanEquals)
}
// NewDateGreaterThanEqualsFunc - returns new DateGreaterThanEquals function.
func NewDateGreaterThanEqualsFunc(key Key, value time.Time) (Function, error) {
return &dateFunc{n: name{name: dateGreaterThanEquals}, k: key, value: value, c: greaterThanEquals}, nil
}
// newDateLessThanFunc - returns new DateLessThan function.
func newDateLessThanFunc(key Key, values ValueSet, _ string) (Function, error) {
return newDateFunc(dateLessThan, key, values, lessThan)
}
// NewDateLessThanFunc - returns new DateLessThan function.
func NewDateLessThanFunc(key Key, value time.Time) (Function, error) {
return &dateFunc{n: name{name: dateLessThan}, k: key, value: value, c: lessThan}, nil
}
// newDateLessThanEqualsFunc - returns new DateLessThanEquals function.
func newDateLessThanEqualsFunc(key Key, values ValueSet, _ string) (Function, error) {
return newDateFunc(dateLessThanEquals, key, values, lessThanEquals)
}
// NewDateLessThanEqualsFunc - returns new DateLessThanEquals function.
func NewDateLessThanEqualsFunc(key Key, value time.Time) (Function, error) {
return &dateFunc{n: name{name: dateLessThanEquals}, k: key, value: value, c: lessThanEquals}, nil
}
golang-github-minio-pkg-3.0.10/policy/condition/datefunc_test.go 0000664 0000000 0000000 00000025645 14655770405 0024707 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package condition
import (
"reflect"
"testing"
"time"
)
func testDateFuncEvaluate(t *testing.T, funcs ...Function) {
testCases := []struct {
function Function
values map[string][]string
expectedResult bool
}{
{funcs[0], map[string][]string{"object-lock-retain-until-date": {"2009-11-10T15:00:00Z"}}, true},
{funcs[0], map[string][]string{"object-lock-retain-until-date": {"2009-12-10T15:00:00Z"}}, false},
{funcs[1], map[string][]string{"object-lock-retain-until-date": {"2009-11-10T15:00:00Z"}}, false},
{funcs[1], map[string][]string{"object-lock-retain-until-date": {"2009-12-10T15:00:00Z"}}, true},
{funcs[2], map[string][]string{"object-lock-retain-until-date": {"2009-11-10T15:00:00Z"}}, false},
{funcs[2], map[string][]string{"object-lock-retain-until-date": {"2008-11-10T15:00:00Z"}}, false},
{funcs[2], map[string][]string{"object-lock-retain-until-date": {"2009-12-10T15:00:00Z"}}, true},
{funcs[3], map[string][]string{"object-lock-retain-until-date": {"2009-11-10T15:00:00Z"}}, true},
{funcs[3], map[string][]string{"object-lock-retain-until-date": {"2008-11-10T15:00:00Z"}}, false},
{funcs[3], map[string][]string{"object-lock-retain-until-date": {"2009-12-10T15:00:00Z"}}, true},
{funcs[4], map[string][]string{"object-lock-retain-until-date": {"2009-11-10T15:00:00Z"}}, false},
{funcs[4], map[string][]string{"object-lock-retain-until-date": {"2008-11-10T15:00:00Z"}}, true},
{funcs[4], map[string][]string{"object-lock-retain-until-date": {"2009-12-10T15:00:00Z"}}, false},
{funcs[5], map[string][]string{"object-lock-retain-until-date": {"2009-11-10T15:00:00Z"}}, true},
{funcs[5], map[string][]string{"object-lock-retain-until-date": {"2008-11-10T15:00:00Z"}}, true},
{funcs[5], map[string][]string{"object-lock-retain-until-date": {"2009-12-10T15:00:00Z"}}, false},
}
for i, testCase := range testCases {
result := testCase.function.evaluate(testCase.values)
if result != testCase.expectedResult {
t.Errorf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestDateFuncEvaluate(t *testing.T) {
valueSet := NewValueSet(NewStringValue("2009-11-10T15:00:00Z"))
case1Function, err := newDateEqualsFunc(S3ObjectLockRetainUntilDate.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Function, err := newDateNotEqualsFunc(S3ObjectLockRetainUntilDate.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case3Function, err := newDateGreaterThanFunc(S3ObjectLockRetainUntilDate.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case4Function, err := newDateGreaterThanEqualsFunc(S3ObjectLockRetainUntilDate.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case5Function, err := newDateLessThanFunc(S3ObjectLockRetainUntilDate.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case6Function, err := newDateLessThanEqualsFunc(S3ObjectLockRetainUntilDate.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testDateFuncEvaluate(t, case1Function, case2Function, case3Function, case4Function, case5Function, case6Function)
if _, err := newDateEqualsFunc(S3ObjectLockRetainUntilDate.ToKey(), NewValueSet(NewIntValue(20091110), NewStringValue("2009-11-10T15:00:00Z")), ""); err == nil {
t.Fatalf("error expected")
}
if _, err := newDateEqualsFunc(S3ObjectLockRetainUntilDate.ToKey(), NewValueSet(NewStringValue("Mon, 02 Jan 2006 15:04:05 MST")), ""); err == nil {
t.Fatalf("error expected")
}
if _, err := newDateEqualsFunc(S3ObjectLockRetainUntilDate.ToKey(), NewValueSet(NewIntValue(20091110)), ""); err == nil {
t.Fatalf("error expected")
}
}
func TestNewDateFuncEvaluate(t *testing.T) {
dateValue := time.Date(2009, time.November, 10, 15, 0, 0, 0, time.UTC)
case1Function, err := NewDateEqualsFunc(S3ObjectLockRetainUntilDate.ToKey(), dateValue)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Function, err := NewDateNotEqualsFunc(S3ObjectLockRetainUntilDate.ToKey(), dateValue)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case3Function, err := NewDateGreaterThanFunc(S3ObjectLockRetainUntilDate.ToKey(), dateValue)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case4Function, err := NewDateGreaterThanEqualsFunc(S3ObjectLockRetainUntilDate.ToKey(), dateValue)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case5Function, err := NewDateLessThanFunc(S3ObjectLockRetainUntilDate.ToKey(), dateValue)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case6Function, err := NewDateLessThanEqualsFunc(S3ObjectLockRetainUntilDate.ToKey(), dateValue)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testDateFuncEvaluate(t, case1Function, case2Function, case3Function, case4Function, case5Function, case6Function)
}
func TestDateFuncKey(t *testing.T) {
case1Function, err := newDateEqualsFunc(S3ObjectLockRetainUntilDate.ToKey(), NewValueSet(NewStringValue("2009-11-10T15:00:00Z")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
function Function
expectedResult Key
}{
{case1Function, S3ObjectLockRetainUntilDate.ToKey()},
}
for i, testCase := range testCases {
result := testCase.function.key()
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestDateFuncName(t *testing.T) {
valueSet := NewValueSet(NewStringValue("2009-11-10T15:00:00Z"))
case1Function, err := newDateEqualsFunc(S3ObjectLockRetainUntilDate.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Function, err := newDateNotEqualsFunc(S3ObjectLockRetainUntilDate.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case3Function, err := newDateGreaterThanFunc(S3ObjectLockRetainUntilDate.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case4Function, err := newDateGreaterThanEqualsFunc(S3ObjectLockRetainUntilDate.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case5Function, err := newDateLessThanFunc(S3ObjectLockRetainUntilDate.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case6Function, err := newDateLessThanEqualsFunc(S3ObjectLockRetainUntilDate.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
function Function
expectedResult name
}{
{case1Function, name{name: dateEquals}},
{case2Function, name{name: dateNotEquals}},
{case3Function, name{name: dateGreaterThan}},
{case4Function, name{name: dateGreaterThanEquals}},
{case5Function, name{name: dateLessThan}},
{case6Function, name{name: dateLessThanEquals}},
}
for i, testCase := range testCases {
result := testCase.function.name()
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestDateFuncToMap(t *testing.T) {
valueSet := NewValueSet(NewStringValue("2009-11-10T15:00:00Z"))
case1Function, err := newDateEqualsFunc(S3ObjectLockRetainUntilDate.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case1Result := map[Key]ValueSet{S3ObjectLockRetainUntilDate.ToKey(): valueSet}
testCases := []struct {
f Function
expectedResult map[Key]ValueSet
}{
{case1Function, case1Result},
}
for i, testCase := range testCases {
result := testCase.f.toMap()
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestDateFuncClone(t *testing.T) {
dateValue := time.Date(2009, time.November, 10, 15, 0, 0, 0, time.UTC)
case1Function, err := NewDateEqualsFunc(S3ObjectLockRetainUntilDate.ToKey(), dateValue)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case1Result := &dateFunc{
n: name{name: dateEquals},
k: S3ObjectLockRetainUntilDate.ToKey(),
value: dateValue,
c: equals,
}
case2Function, err := NewDateNotEqualsFunc(S3ObjectLockRetainUntilDate.ToKey(), dateValue)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Result := &dateFunc{
n: name{name: dateNotEquals},
k: S3ObjectLockRetainUntilDate.ToKey(),
value: dateValue,
c: notEquals,
}
case3Function, err := NewDateGreaterThanFunc(S3ObjectLockRetainUntilDate.ToKey(), dateValue)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case3Result := &dateFunc{
n: name{name: dateGreaterThan},
k: S3ObjectLockRetainUntilDate.ToKey(),
value: dateValue,
c: greaterThan,
}
case4Function, err := NewDateGreaterThanEqualsFunc(S3ObjectLockRetainUntilDate.ToKey(), dateValue)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case4Result := &dateFunc{
n: name{name: dateGreaterThanEquals},
k: S3ObjectLockRetainUntilDate.ToKey(),
value: dateValue,
c: greaterThanEquals,
}
case5Function, err := NewDateLessThanFunc(S3ObjectLockRetainUntilDate.ToKey(), dateValue)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case5Result := &dateFunc{
n: name{name: dateLessThan},
k: S3ObjectLockRetainUntilDate.ToKey(),
value: dateValue,
c: lessThan,
}
case6Function, err := NewDateLessThanEqualsFunc(S3ObjectLockRetainUntilDate.ToKey(), dateValue)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case6Result := &dateFunc{
n: name{name: dateLessThanEquals},
k: S3ObjectLockRetainUntilDate.ToKey(),
value: dateValue,
c: lessThanEquals,
}
testCases := []struct {
function Function
expectedResult Function
}{
{case1Function, case1Result},
{case2Function, case2Result},
{case3Function, case3Result},
{case4Function, case4Result},
{case5Function, case5Result},
{case6Function, case6Result},
}
for i, testCase := range testCases {
result := testCase.function.clone()
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
golang-github-minio-pkg-3.0.10/policy/condition/func.go 0000664 0000000 0000000 00000014265 14655770405 0023006 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package condition
import (
"encoding/json"
"fmt"
"sort"
)
type condition int
const (
equals condition = iota + 1
notEquals
greaterThan
greaterThanEquals
lessThan
lessThanEquals
)
// Function - condition function interface.
type Function interface {
// evaluate() - evaluates this condition function with given values.
evaluate(values map[string][]string) bool
// key() - returns condition key used in this function.
key() Key
// name() - returns condition name of this function.
name() name
// String() - returns string representation of function.
String() string
// toMap - returns map representation of this function.
toMap() map[Key]ValueSet
// clone - returns copy of this function.
clone() Function
}
// Functions - list of functions.
type Functions []Function
// Evaluate - evaluates all functions with given values map. Each function is evaluated
// sequencely and next function is called only if current function succeeds.
func (functions Functions) Evaluate(values map[string][]string) bool {
for _, f := range functions {
if !f.evaluate(values) {
return false
}
}
return true
}
// Keys - returns list of keys used in all functions.
func (functions Functions) Keys() KeySet {
keySet := NewKeySet()
for _, f := range functions {
keySet.Add(f.key())
}
return keySet
}
// Clone clones Functions structure
func (functions Functions) Clone() Functions {
funcs := []Function{}
for _, f := range functions {
funcs = append(funcs, f.clone())
}
return funcs
}
// Equals returns true if two Functions structures are equal
func (functions Functions) Equals(funcs Functions) bool {
if len(functions) != len(funcs) {
return false
}
for _, fi := range functions {
fistr := fi.String()
found := false
for _, fj := range funcs {
if fistr == fj.String() {
found = true
break
}
}
if !found {
return false
}
}
return true
}
// MarshalJSON - encodes Functions to JSON data.
func (functions Functions) MarshalJSON() ([]byte, error) {
nm := make(map[string]map[string]ValueSet)
for _, f := range functions {
fname := f.name().String()
if _, ok := nm[fname]; !ok {
nm[fname] = map[string]ValueSet{}
}
for k, v := range f.toMap() {
nm[fname][k.String()] = v
}
}
return json.Marshal(nm)
}
func (functions Functions) String() string {
funcStrings := []string{}
for _, f := range functions {
s := fmt.Sprintf("%v", f)
funcStrings = append(funcStrings, s)
}
sort.Strings(funcStrings)
return fmt.Sprintf("%v", funcStrings)
}
var conditionFuncMap = map[string]func(Key, ValueSet, string) (Function, error){
stringEquals: newStringEqualsFunc,
stringNotEquals: newStringNotEqualsFunc,
stringEqualsIgnoreCase: newStringEqualsIgnoreCaseFunc,
stringNotEqualsIgnoreCase: newStringNotEqualsIgnoreCaseFunc,
binaryEquals: newBinaryEqualsFunc,
stringLike: newStringLikeFunc,
stringNotLike: newStringNotLikeFunc,
ipAddress: newIPAddressFunc,
notIPAddress: newNotIPAddressFunc,
null: newNullFunc,
boolean: newBooleanFunc,
numericEquals: newNumericEqualsFunc,
numericNotEquals: newNumericNotEqualsFunc,
numericLessThan: newNumericLessThanFunc,
numericLessThanEquals: newNumericLessThanEqualsFunc,
numericGreaterThan: newNumericGreaterThanFunc,
numericGreaterThanIfExists: newNumericGreaterThanIfExistsFunc,
numericGreaterThanEquals: newNumericGreaterThanEqualsFunc,
dateEquals: newDateEqualsFunc,
dateNotEquals: newDateNotEqualsFunc,
dateLessThan: newDateLessThanFunc,
dateLessThanEquals: newDateLessThanEqualsFunc,
dateGreaterThan: newDateGreaterThanFunc,
dateGreaterThanEquals: newDateGreaterThanEqualsFunc,
// Add new conditions here.
}
// UnmarshalJSON - decodes JSON data to Functions.
func (functions *Functions) UnmarshalJSON(data []byte) error {
// As string kind, int kind then json.Unmarshaler is checked at
// https://github.com/golang/go/blob/master/src/encoding/json/decode.go#L618
// UnmarshalJSON() is not called for types extending string
// see https://play.golang.org/p/HrSsKksHvrS, better way to do is
// https://play.golang.org/p/y9ElWpBgVAB
//
// Due to this issue, name and Key types cannot be used as map keys below.
nm := make(map[string]map[string]ValueSet)
if err := json.Unmarshal(data, &nm); err != nil {
return err
}
if len(nm) == 0 {
return fmt.Errorf("condition must not be empty")
}
funcs := []Function{}
for nameString, args := range nm {
n, err := parseName(nameString)
if err != nil {
return err
}
for keyString, values := range args {
key, err := parseKey(keyString)
if err != nil {
return err
}
fn, ok := conditionFuncMap[n.name]
if !ok {
return fmt.Errorf("condition %v is not handled", n)
}
f, err := fn(key, values, n.qualifier)
if err != nil {
return err
}
funcs = append(funcs, f)
}
}
*functions = funcs
return nil
}
// GobEncode - encodes Functions to gob data.
func (functions Functions) GobEncode() ([]byte, error) {
return functions.MarshalJSON()
}
// GobDecode - decodes gob data to Functions.
func (functions *Functions) GobDecode(data []byte) error {
return functions.UnmarshalJSON(data)
}
// NewFunctions - returns new Functions with given function list.
func NewFunctions(functions ...Function) Functions {
return Functions(functions)
}
golang-github-minio-pkg-3.0.10/policy/condition/func_test.go 0000664 0000000 0000000 00000027235 14655770405 0024046 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package condition
import (
"encoding/json"
"fmt"
"reflect"
"testing"
)
func TestFunctionsEvaluate(t *testing.T) {
func1, err := newNullFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewBoolValue(true)), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func2, err := newIPAddressFunc(AWSSourceIP.ToKey(), NewValueSet(NewStringValue("192.168.1.0/24")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func3, err := newStringEqualsFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func4, err := newStringLikeFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject*")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case1Function := NewFunctions(func1, func2, func3, func4)
testCases := []struct {
functions Functions
values map[string][]string
expectedResult bool
}{
{case1Function, map[string][]string{
"x-amz-copy-source": {"mybucket/myobject"},
"SourceIp": {"192.168.1.10"},
}, false},
{case1Function, map[string][]string{
"x-amz-copy-source": {"mybucket/myobject"},
"SourceIp": {"192.168.1.10"},
"Refer": {"http://example.org/"},
}, false},
{case1Function, map[string][]string{"x-amz-copy-source": {"mybucket/myobject"}}, false},
{case1Function, map[string][]string{"SourceIp": {"192.168.1.10"}}, false},
{case1Function, map[string][]string{
"x-amz-copy-source": {"mybucket/yourobject"},
"SourceIp": {"192.168.1.10"},
}, false},
{case1Function, map[string][]string{
"x-amz-copy-source": {"mybucket/myobject"},
"SourceIp": {"192.168.2.10"},
}, false},
{case1Function, map[string][]string{
"x-amz-copy-source": {"mybucket/myobject"},
"Refer": {"http://example.org/"},
}, false},
}
for i, testCase := range testCases {
result := testCase.functions.Evaluate(testCase.values)
if result != testCase.expectedResult {
t.Errorf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestFunctionsKeys(t *testing.T) {
func1, err := newNullFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewBoolValue(true)), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func2, err := newIPAddressFunc(AWSSourceIP.ToKey(), NewValueSet(NewStringValue("192.168.1.0/24")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func3, err := newStringEqualsFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func4, err := newStringLikeFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject*")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
functions Functions
expectedResult KeySet
}{
{NewFunctions(func1, func2, func3, func4), NewKeySet(S3XAmzCopySource.ToKey(), AWSSourceIP.ToKey())},
}
for i, testCase := range testCases {
result := testCase.functions.Keys()
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestFunctionsMarshalJSON(t *testing.T) {
func1, err := newStringLikeFunc(S3XAmzMetadataDirective.ToKey(), NewValueSet(NewStringValue("REPL*")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func2, err := newStringEqualsFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func3, err := newStringNotEqualsFunc(S3XAmzServerSideEncryption.ToKey(), NewValueSet(NewStringValue("AES256")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func3SSEKMS, err := newStringNotEqualsFunc(S3XAmzServerSideEncryption.ToKey(), NewValueSet(NewStringValue("aws:kms")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func4, err := newNotIPAddressFunc(AWSSourceIP.ToKey(),
NewValueSet(NewStringValue("10.1.10.0/24")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func5, err := newStringNotLikeFunc(S3XAmzStorageClass.ToKey(), NewValueSet(NewStringValue("STANDARD")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func6, err := newNullFunc(S3XAmzServerSideEncryptionCustomerAlgorithm.ToKey(), NewValueSet(NewBoolValue(true)), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func7, err := newIPAddressFunc(AWSSourceIP.ToKey(),
NewValueSet(NewStringValue("192.168.1.0/24")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case1Result := []byte(`{"IpAddress":{"aws:SourceIp":["192.168.1.0/24"]},"NotIpAddress":{"aws:SourceIp":["10.1.10.0/24"]},"Null":{"s3:x-amz-server-side-encryption-customer-algorithm":[true]},"StringEquals":{"s3:x-amz-copy-source":["mybucket/myobject"]},"StringLike":{"s3:x-amz-metadata-directive":["REPL*"]},"StringNotEquals":{"s3:x-amz-server-side-encryption":["AES256"]},"StringNotLike":{"s3:x-amz-storage-class":["STANDARD"]}}`)
case1ResultKMS := []byte(`{"IpAddress":{"aws:SourceIp":["192.168.1.0/24"]},"NotIpAddress":{"aws:SourceIp":["10.1.10.0/24"]},"Null":{"s3:x-amz-server-side-encryption-customer-algorithm":[true]},"StringEquals":{"s3:x-amz-copy-source":["mybucket/myobject"]},"StringLike":{"s3:x-amz-metadata-directive":["REPL*"]},"StringNotEquals":{"s3:x-amz-server-side-encryption":["aws:kms"]},"StringNotLike":{"s3:x-amz-storage-class":["STANDARD"]}}`)
case2Result := []byte(`{"Null":{"s3:x-amz-server-side-encryption-customer-algorithm":[true]}}`)
testCases := []struct {
functions Functions
expectedResult []byte
expectErr bool
}{
{NewFunctions(func1, func2, func3, func4, func5, func6, func7), case1Result, false},
{NewFunctions(func1, func2, func3SSEKMS, func4, func5, func6, func7), case1ResultKMS, false},
{NewFunctions(func6), case2Result, false},
{NewFunctions(), []byte(`{}`), false},
{nil, []byte(`{}`), false},
}
for i, testCase := range testCases {
result, err := json.Marshal(testCase.functions)
expectErr := (err != nil)
if testCase.expectErr != expectErr {
fmt.Println(err)
t.Fatalf("case %v: error: expected: %v, got: %v %v", i+1, testCase.expectErr, expectErr, string(case1Result))
}
if !testCase.expectErr {
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v", i+1, string(testCase.expectedResult), string(result))
}
}
}
}
func TestFunctionsUnmarshalJSON(t *testing.T) {
case1Data := []byte(`{
"StringLike": {
"s3:x-amz-metadata-directive": "REPL*"
},
"StringEquals": {
"s3:x-amz-copy-source": "mybucket/myobject"
},
"StringNotEquals": {
"s3:x-amz-server-side-encryption": "AES256"
},
"NotIpAddress": {
"aws:SourceIp": [
"10.1.10.0/24",
"10.10.1.0/24"
]
},
"StringNotLike": {
"s3:x-amz-storage-class": "STANDARD"
},
"Null": {
"s3:x-amz-server-side-encryption-customer-algorithm": true
},
"IpAddress": {
"aws:SourceIp": [
"192.168.1.0/24",
"192.168.2.0/24"
]
}
}`)
func1, err := newStringLikeFunc(S3XAmzMetadataDirective.ToKey(), NewValueSet(NewStringValue("REPL*")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func2, err := newStringEqualsFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func3, err := newStringNotEqualsFunc(S3XAmzServerSideEncryption.ToKey(), NewValueSet(NewStringValue("AES256")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func4, err := newNotIPAddressFunc(AWSSourceIP.ToKey(),
NewValueSet(NewStringValue("10.1.10.0/24"), NewStringValue("10.10.1.0/24")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func5, err := newStringNotLikeFunc(S3XAmzStorageClass.ToKey(), NewValueSet(NewStringValue("STANDARD")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func6, err := newNullFunc(S3XAmzServerSideEncryptionCustomerAlgorithm.ToKey(), NewValueSet(NewBoolValue(true)), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func7, err := newIPAddressFunc(AWSSourceIP.ToKey(),
NewValueSet(NewStringValue("192.168.1.0/24"), NewStringValue("192.168.2.0/24")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Data := []byte(`{
"Null": {
"s3:x-amz-server-side-encryption-customer-algorithm": true
},
"Null": {
"s3:x-amz-server-side-encryption-customer-algorithm": "true"
}
}`)
case3Data := []byte(`{}`)
case4Data := []byte(`{
"StringLike": {
"s3:x-amz-metadata-directive": "REPL*"
},
"StringEquals": {
"s3:x-amz-copy-source": "mybucket/myobject",
"s3:prefix": [
"",
"home/"
],
"s3:delimiter": [
"/"
]
},
"StringNotEquals": {
"s3:x-amz-server-side-encryption": "AES256"
},
"NotIpAddress": {
"aws:SourceIp": [
"10.1.10.0/24",
"10.10.1.0/24"
]
},
"StringNotLike": {
"s3:x-amz-storage-class": "STANDARD"
},
"Null": {
"s3:x-amz-server-side-encryption-customer-algorithm": true
},
"IpAddress": {
"aws:SourceIp": [
"192.168.1.0/24",
"192.168.2.0/24"
]
}
}`)
func2_1, err := newStringEqualsFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func2_2, err := newStringEqualsFunc(S3Prefix.ToKey(), NewValueSet(NewStringValue(""), NewStringValue("home/")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func2_3, err := newStringEqualsFunc(S3Delimiter.ToKey(), NewValueSet(NewStringValue("/")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
data []byte
expectedResult Functions
expectErr bool
}{
// Success case, basic conditions.
{case1Data, NewFunctions(func1, func2, func3, func4, func5, func6, func7), false},
// Duplicate conditions, success case only one value is preserved.
{case2Data, NewFunctions(func6), false},
// empty condition error.
{case3Data, nil, true},
// Success case multiple keys, same condition.
{case4Data, NewFunctions(func1, func2_1, func2_2, func2_3, func3, func4, func5, func6, func7), false},
}
for i, testCase := range testCases {
result := new(Functions)
err := json.Unmarshal(testCase.data, result)
expectErr := (err != nil)
if testCase.expectErr != expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if (*result).String() != testCase.expectedResult.String() {
t.Fatalf("case %v: result: expected: %v, got: %v", i+1, testCase.expectedResult, *result)
}
}
}
}
golang-github-minio-pkg-3.0.10/policy/condition/ipaddrfunc.go 0000664 0000000 0000000 00000011534 14655770405 0024166 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package condition
import (
"fmt"
"net"
"sort"
"strings"
)
// ipaddrFunc - IP address function. It checks whether value by Key in given
// values is in IP network. Here Key must be AWSSourceIP.
// For example,
// - if values = [192.168.1.0/24], at evaluate() it returns whether IP address
// in value map for AWSSourceIP falls in the network 192.168.1.10/24.
type ipaddrFunc struct {
n name
k Key
values []*net.IPNet
negate bool
}
func (f ipaddrFunc) eval(values map[string][]string) bool {
rvalues := getValuesByKey(values, f.k)
IPs := []net.IP{}
for _, s := range rvalues {
IP := net.ParseIP(s)
if IP == nil {
return false
}
IPs = append(IPs, IP)
}
for _, IP := range IPs {
for _, IPNet := range f.values {
if IPNet.Contains(IP) {
return true
}
}
}
return false
}
// evaluate() - evaluates to check whether IP address in values map for AWSSourceIP
// falls in one of network or not.
func (f ipaddrFunc) evaluate(values map[string][]string) bool {
result := f.eval(values)
if f.negate {
return !result
}
return result
}
// key() - returns condition key which is used by this condition function.
// Key is always AWSSourceIP.
func (f ipaddrFunc) key() Key {
return f.k
}
// name() - returns "IpAddress" condition name.
func (f ipaddrFunc) name() name {
return f.n
}
func (f ipaddrFunc) String() string {
valueStrings := []string{}
for _, value := range f.values {
valueStrings = append(valueStrings, value.String())
}
sort.Strings(valueStrings)
return fmt.Sprintf("%v:%v:%v", f.n, f.k, valueStrings)
}
// toMap - returns map representation of this function.
func (f ipaddrFunc) toMap() map[Key]ValueSet {
if !f.k.IsValid() {
return nil
}
values := NewValueSet()
for _, value := range f.values {
values.Add(NewStringValue(value.String()))
}
return map[Key]ValueSet{
f.k: values,
}
}
func (f ipaddrFunc) clone() Function {
values := []*net.IPNet{}
for _, value := range f.values {
_, IPNet, _ := net.ParseCIDR(value.String())
values = append(values, IPNet)
}
return &ipaddrFunc{
n: f.n,
k: f.k,
values: values,
negate: f.negate,
}
}
func valuesToIPNets(n string, values ValueSet) ([]*net.IPNet, error) {
IPNets := []*net.IPNet{}
for v := range values {
s, err := v.GetString()
if err != nil {
return nil, fmt.Errorf("value %v must be string representation of CIDR for %v condition", v, n)
}
// If you specify an IP address without the associated routing prefix, IAM uses the default prefix value of /32.
// https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html#Conditions_IPAddress
if strings.IndexByte(s, '/') == -1 {
s += "/32"
}
var IPNet *net.IPNet
_, IPNet, err = net.ParseCIDR(s)
if err != nil {
return nil, fmt.Errorf("value %v must be CIDR string for %v condition", s, n)
}
IPNets = append(IPNets, IPNet)
}
return IPNets, nil
}
func newIPAddrFunc(n string, key Key, values []*net.IPNet, negate bool) (Function, error) {
if !key.Is(AWSSourceIP) {
return nil, fmt.Errorf("only %v key is allowed for %v condition", AWSSourceIP, n)
}
return &ipaddrFunc{
n: name{name: n},
k: key,
values: values,
negate: negate,
}, nil
}
// newIPAddressFunc - returns new IP address function.
func newIPAddressFunc(key Key, values ValueSet, _ string) (Function, error) {
IPNets, err := valuesToIPNets(ipAddress, values)
if err != nil {
return nil, err
}
return NewIPAddressFunc(key, IPNets...)
}
// NewIPAddressFunc - returns new IP address function.
func NewIPAddressFunc(key Key, IPNets ...*net.IPNet) (Function, error) {
return newIPAddrFunc(ipAddress, key, IPNets, false)
}
// newNotIPAddressFunc - returns new Not IP address function.
func newNotIPAddressFunc(key Key, values ValueSet, _ string) (Function, error) {
IPNets, err := valuesToIPNets(notIPAddress, values)
if err != nil {
return nil, err
}
return NewNotIPAddressFunc(key, IPNets...)
}
// NewNotIPAddressFunc - returns new Not IP address function.
func NewNotIPAddressFunc(key Key, IPNets ...*net.IPNet) (Function, error) {
return newIPAddrFunc(notIPAddress, key, IPNets, true)
}
golang-github-minio-pkg-3.0.10/policy/condition/ipaddrfunc_test.go 0000664 0000000 0000000 00000022574 14655770405 0025233 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package condition
import (
"bytes"
"encoding/json"
"net"
"reflect"
"testing"
)
func TestIPAddrFuncEvaluate(t *testing.T) {
case1Function, err := newIPAddressFunc(AWSSourceIP.ToKey(), NewValueSet(NewStringValue("192.168.1.0/24")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Function, err := newNotIPAddressFunc(AWSSourceIP.ToKey(), NewValueSet(NewStringValue("192.168.1.0/24")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
function Function
values map[string][]string
expectedResult bool
}{
{case1Function, map[string][]string{"SourceIp": {"192.168.1.10"}}, true},
{case1Function, map[string][]string{"SourceIp": {"192.168.2.10"}}, false},
{case1Function, map[string][]string{}, false},
{case1Function, map[string][]string{"delimiter": {"/"}}, false},
{case2Function, map[string][]string{"SourceIp": {"192.168.1.10"}}, false},
{case2Function, map[string][]string{"SourceIp": {"192.168.2.10"}}, true},
{case2Function, map[string][]string{}, true},
{case2Function, map[string][]string{"delimiter": {"/"}}, true},
}
for i, testCase := range testCases {
result := testCase.function.evaluate(testCase.values)
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
if _, err := newIPAddressFunc(S3Prefix.ToKey(), NewValueSet(NewStringValue("192.168.1.0/24")), ""); err == nil {
t.Fatalf("expected error")
}
}
func TestIPAddrFuncKey(t *testing.T) {
case1Function, err := newIPAddressFunc(AWSSourceIP.ToKey(), NewValueSet(NewStringValue("192.168.1.0/24")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
function Function
expectedResult Key
}{
{case1Function, AWSSourceIP.ToKey()},
}
for i, testCase := range testCases {
result := testCase.function.key()
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestIPAddrFuncName(t *testing.T) {
case1Function, err := newIPAddressFunc(AWSSourceIP.ToKey(), NewValueSet(NewStringValue("192.168.1.0/24")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Function, err := newNotIPAddressFunc(AWSSourceIP.ToKey(), NewValueSet(NewStringValue("192.168.1.0/24")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
function Function
expectedResult name
}{
{case1Function, name{name: ipAddress}},
{case2Function, name{name: notIPAddress}},
}
for i, testCase := range testCases {
result := testCase.function.name()
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestIPAddrFuncToMap(t *testing.T) {
case1Function, err := newIPAddressFunc(AWSSourceIP.ToKey(), NewValueSet(NewStringValue("192.168.1.0/24")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case1Result := map[Key]ValueSet{
AWSSourceIP.ToKey(): NewValueSet(NewStringValue("192.168.1.0/24")),
}
case2Function, err := newIPAddressFunc(AWSSourceIP.ToKey(), NewValueSet(NewStringValue("192.168.1.0/24"), NewStringValue("10.1.10.1/32")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Result := map[Key]ValueSet{
AWSSourceIP.ToKey(): NewValueSet(NewStringValue("192.168.1.0/24"), NewStringValue("10.1.10.1/32")),
}
testCases := []struct {
f Function
expectedResult map[Key]ValueSet
}{
{case1Function, case1Result},
{case2Function, case2Result},
{&ipaddrFunc{}, nil},
}
for i, testCase := range testCases {
result := testCase.f.toMap()
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestIPAddrFuncClone(t *testing.T) {
_, IPNet1, err := net.ParseCIDR("192.168.1.0/24")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
_, IPNet2, err := net.ParseCIDR("10.1.10.1/32")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case1Function, err := newIPAddressFunc(AWSSourceIP.ToKey(), NewValueSet(NewStringValue("192.168.1.0/24")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case1Result := &ipaddrFunc{
n: name{name: ipAddress},
k: AWSSourceIP.ToKey(),
values: []*net.IPNet{IPNet1},
negate: false,
}
case2Function, err := newIPAddressFunc(AWSSourceIP.ToKey(), NewValueSet(NewStringValue("192.168.1.0/24"), NewStringValue("10.1.10.1/32")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Result := &ipaddrFunc{
n: name{name: ipAddress},
k: AWSSourceIP.ToKey(),
values: []*net.IPNet{IPNet1, IPNet2},
negate: false,
}
case3Function, err := newNotIPAddressFunc(AWSSourceIP.ToKey(), NewValueSet(NewStringValue("192.168.1.0/24")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case3Result := &ipaddrFunc{
n: name{name: notIPAddress},
k: AWSSourceIP.ToKey(),
values: []*net.IPNet{IPNet1},
negate: true,
}
testCases := []struct {
f Function
expectedResult Function
}{
{case1Function, case1Result},
{case2Function, case2Result},
{case3Function, case3Result},
}
for i, testCase := range testCases {
result := testCase.f.clone()
exp1, _ := json.Marshal(result)
exp2, _ := json.Marshal(testCase.expectedResult)
if !bytes.Equal(exp1, exp2) {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestNewIPAddressFunc(t *testing.T) {
case1Function, err := newIPAddressFunc(AWSSourceIP.ToKey(), NewValueSet(NewStringValue("192.168.1.0/24")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Function, err := newIPAddressFunc(AWSSourceIP.ToKey(), NewValueSet(NewStringValue("192.168.1.0/24"), NewStringValue("10.1.10.1/32")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
key Key
values ValueSet
expectedResult Function
expectErr bool
}{
{AWSSourceIP.ToKey(), NewValueSet(NewStringValue("192.168.1.0/24")), case1Function, false},
{AWSSourceIP.ToKey(), NewValueSet(NewStringValue("192.168.1.0/24"), NewStringValue("10.1.10.1/32")), case2Function, false},
// Unsupported key error.
{S3Prefix.ToKey(), NewValueSet(NewStringValue("192.168.1.0/24")), nil, true},
// Invalid value error.
{AWSSourceIP.ToKey(), NewValueSet(NewStringValue("node1.example.org")), nil, true},
// Invalid CIDR format error.
{AWSSourceIP.ToKey(), NewValueSet(NewStringValue("192.168.1.0.0/24")), nil, true},
}
for i, testCase := range testCases {
result, err := newIPAddressFunc(testCase.key, testCase.values, "")
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if result.String() != testCase.expectedResult.String() {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
}
func TestNewNotIPAddressFunc(t *testing.T) {
case1Function, err := newNotIPAddressFunc(AWSSourceIP.ToKey(), NewValueSet(NewStringValue("192.168.1.0/24")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Function, err := newNotIPAddressFunc(AWSSourceIP.ToKey(), NewValueSet(NewStringValue("192.168.1.0/24"), NewStringValue("10.1.10.1/32")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
key Key
values ValueSet
expectedResult Function
expectErr bool
}{
{AWSSourceIP.ToKey(), NewValueSet(NewStringValue("192.168.1.0/24")), case1Function, false},
{AWSSourceIP.ToKey(), NewValueSet(NewStringValue("192.168.1.0/24"), NewStringValue("10.1.10.1/32")), case2Function, false},
// Unsupported key error.
{S3Prefix.ToKey(), NewValueSet(NewStringValue("192.168.1.0/24")), nil, true},
// Invalid value error.
{AWSSourceIP.ToKey(), NewValueSet(NewStringValue("node1.example.org")), nil, true},
// Invalid CIDR format error.
{AWSSourceIP.ToKey(), NewValueSet(NewStringValue("192.168.1.0.0/24")), nil, true},
}
for i, testCase := range testCases {
result, err := newNotIPAddressFunc(testCase.key, testCase.values, "")
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if result.String() != testCase.expectedResult.String() {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
}
golang-github-minio-pkg-3.0.10/policy/condition/key.go 0000664 0000000 0000000 00000007747 14655770405 0022652 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package condition
import (
"encoding/json"
"fmt"
"strings"
)
// Key - conditional key whose name and it's optional variable.
type Key struct {
name KeyName
variable string
}
// IsValid - checks if key is valid or not.
func (key Key) IsValid() bool {
for _, name := range AllSupportedKeys {
if key.name == name {
return true
}
}
return false
}
// Is - checks if this key has same key name or not.
func (key Key) Is(name KeyName) bool {
return key.name == name
}
func (key Key) String() string {
if key.variable != "" {
return string(key.name) + "/" + key.variable
}
return string(key.name)
}
// MarshalJSON - encodes Key to JSON data.
func (key Key) MarshalJSON() ([]byte, error) {
if !key.IsValid() {
return nil, fmt.Errorf("unknown key %v", key)
}
return json.Marshal(key.String())
}
// VarName - returns variable key name, such as "${aws:username}"
func (key Key) VarName() string {
return key.name.VarName()
}
// Name - returns key name which is stripped value of prefixes "aws:" and "s3:"
func (key Key) Name() string {
name := key.name.Name()
if key.variable != "" {
return name + "/" + key.variable
}
return name
}
// UnmarshalJSON - decodes JSON data to Key.
func (key *Key) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
parsedKey, err := parseKey(s)
if err != nil {
return err
}
*key = parsedKey
return nil
}
func parseKey(s string) (Key, error) {
name, variable := s, ""
if strings.Contains(s, "/") {
tokens := strings.SplitN(s, "/", 2)
name, variable = tokens[0], tokens[1]
}
key := Key{
name: KeyName(name),
variable: variable,
}
if key.IsValid() {
return key, nil
}
return key, fmt.Errorf("invalid condition key '%v'", s)
}
// NewKey - creates new key
func NewKey(name KeyName, variable string) Key {
return Key{
name: name,
variable: variable,
}
}
// KeySet - set representation of slice of keys.
type KeySet map[Key]struct{}
// Add - add a key to key set.
func (set KeySet) Add(key Key) {
set[key] = struct{}{}
}
// Merge merges two key sets, duplicates are overwritten
func (set KeySet) Merge(mset KeySet) {
for k, v := range mset {
set[k] = v
}
}
// Match matches the input key name with current keySet
func (set KeySet) Match(key Key) bool {
_, ok := set[key]
if ok {
return true
}
_, ok = set[key.name.ToKey()]
return ok
}
// Difference - returns a key set contains difference of two keys.
// Example:
//
// keySet1 := ["one", "two", "three"]
// keySet2 := ["two", "four", "three"]
// keySet1.Difference(keySet2) == ["one"]
func (set KeySet) Difference(sset KeySet) KeySet {
nset := make(KeySet)
for k := range set {
if !sset.Match(k) {
nset.Add(k)
}
}
return nset
}
// IsEmpty - returns whether key set is empty or not.
func (set KeySet) IsEmpty() bool {
return len(set) == 0
}
func (set KeySet) String() string {
return fmt.Sprintf("%v", set.ToSlice())
}
// ToSlice - returns slice of keys.
func (set KeySet) ToSlice() []Key {
keys := []Key{}
for key := range set {
keys = append(keys, key)
}
return keys
}
// NewKeySet - returns new KeySet contains given keys.
func NewKeySet(keys ...Key) KeySet {
set := make(KeySet)
for _, key := range keys {
set.Add(key)
}
return set
}
golang-github-minio-pkg-3.0.10/policy/condition/key_test.go 0000664 0000000 0000000 00000013420 14655770405 0023672 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package condition
import (
"encoding/json"
"reflect"
"testing"
)
func TestKeyIsValid(t *testing.T) {
testCases := []struct {
key Key
expectedResult bool
}{
{S3XAmzCopySource.ToKey(), true},
{S3XAmzServerSideEncryption.ToKey(), true},
{S3XAmzServerSideEncryptionCustomerAlgorithm.ToKey(), true},
{S3XAmzMetadataDirective.ToKey(), true},
{S3XAmzStorageClass.ToKey(), true},
{S3LocationConstraint.ToKey(), true},
{S3Prefix.ToKey(), true},
{S3Delimiter.ToKey(), true},
{S3MaxKeys.ToKey(), true},
{AWSReferer.ToKey(), true},
{AWSSourceIP.ToKey(), true},
{ExistingObjectTag.ToKey(), true},
{RequestObjectTagKeys.ToKey(), true},
{RequestObjectTag.ToKey(), true},
{Key{name: "foo"}, false},
}
for i, testCase := range testCases {
result := testCase.key.IsValid()
if testCase.expectedResult != result {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestKeyMarshalJSON(t *testing.T) {
testCases := []struct {
key Key
expectedResult []byte
expectErr bool
}{
{S3XAmzCopySource.ToKey(), []byte(`"s3:x-amz-copy-source"`), false},
{Key{name: "foo"}, nil, true},
}
for i, testCase := range testCases {
result, err := json.Marshal(testCase.key)
expectErr := (err != nil)
if testCase.expectErr != expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: key: expected: %v, got: %v\n", i+1, string(testCase.expectedResult), string(result))
}
}
}
}
func TestKeyName(t *testing.T) {
testCases := []struct {
key Key
expectedResult string
}{
{S3XAmzCopySource.ToKey(), "x-amz-copy-source"},
{AWSReferer.ToKey(), "Referer"},
}
for i, testCase := range testCases {
result := testCase.key.Name()
if testCase.expectedResult != result {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestKeyUnmarshalJSON(t *testing.T) {
testCases := []struct {
data []byte
expectedKey Key
expectErr bool
}{
{[]byte(`"s3:x-amz-copy-source"`), S3XAmzCopySource.ToKey(), false},
{[]byte(`"foo"`), Key{name: ""}, true},
}
for i, testCase := range testCases {
var key Key
err := json.Unmarshal(testCase.data, &key)
expectErr := (err != nil)
if testCase.expectErr != expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if testCase.expectedKey != key {
t.Fatalf("case %v: key: expected: %v, got: %v\n", i+1, testCase.expectedKey, key)
}
}
}
}
func TestKeySetAdd(t *testing.T) {
testCases := []struct {
set KeySet
key Key
expectedResult KeySet
}{
{NewKeySet(), S3XAmzCopySource.ToKey(), NewKeySet(S3XAmzCopySource.ToKey())},
{NewKeySet(S3XAmzCopySource.ToKey()), S3XAmzCopySource.ToKey(), NewKeySet(S3XAmzCopySource.ToKey())},
}
for i, testCase := range testCases {
testCase.set.Add(testCase.key)
if !reflect.DeepEqual(testCase.expectedResult, testCase.set) {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, testCase.set)
}
}
}
func TestKeySetDifference(t *testing.T) {
testCases := []struct {
set KeySet
setToDiff KeySet
expectedResult KeySet
}{
{NewKeySet(), NewKeySet(S3XAmzCopySource.ToKey()), NewKeySet()},
{NewKeySet(S3Prefix.ToKey(), S3Delimiter.ToKey(), S3MaxKeys.ToKey()), NewKeySet(S3Delimiter.ToKey(), S3MaxKeys.ToKey()), NewKeySet(S3Prefix.ToKey())},
}
for i, testCase := range testCases {
result := testCase.set.Difference(testCase.setToDiff)
if !reflect.DeepEqual(testCase.expectedResult, result) {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestKeySetIsEmpty(t *testing.T) {
testCases := []struct {
set KeySet
expectedResult bool
}{
{NewKeySet(), true},
{NewKeySet(S3Delimiter.ToKey()), false},
}
for i, testCase := range testCases {
result := testCase.set.IsEmpty()
if testCase.expectedResult != result {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestKeySetString(t *testing.T) {
testCases := []struct {
set KeySet
expectedResult string
}{
{NewKeySet(), `[]`},
{NewKeySet(S3Delimiter.ToKey()), `[s3:delimiter]`},
}
for i, testCase := range testCases {
result := testCase.set.String()
if testCase.expectedResult != result {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestKeySetToSlice(t *testing.T) {
testCases := []struct {
set KeySet
expectedResult []Key
}{
{NewKeySet(), []Key{}},
{NewKeySet(S3Delimiter.ToKey()), []Key{S3Delimiter.ToKey()}},
}
for i, testCase := range testCases {
result := testCase.set.ToSlice()
if !reflect.DeepEqual(testCase.expectedResult, result) {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
golang-github-minio-pkg-3.0.10/policy/condition/keyname.go 0000664 0000000 0000000 00000025553 14655770405 0023506 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package condition
import (
"fmt"
"strings"
)
// KeyName - conditional key which is used to fetch values for any condition.
// Refer https://docs.aws.amazon.com/IAM/latest/UserGuide/list_s3.html
// for more information about available condition keys.
type KeyName string
// Name - returns key name which is stripped value of prefixes "aws:", "s3:", "jwt:" and "ldap:"
func (key KeyName) Name() string {
name := string(key)
switch {
case strings.HasPrefix(name, "aws:"):
return strings.TrimPrefix(name, "aws:")
case strings.HasPrefix(name, "jwt:"):
return strings.TrimPrefix(name, "jwt:")
case strings.HasPrefix(name, "ldap:"):
return strings.TrimPrefix(name, "ldap:")
case strings.HasPrefix(name, "sts:"):
return strings.TrimPrefix(name, "sts:")
case strings.HasPrefix(name, "svc:"):
return strings.TrimPrefix(name, "svc:")
default:
return strings.TrimPrefix(name, "s3:")
}
}
// VarName - returns variable key name, such as "${aws:username}"
func (key KeyName) VarName() string {
return fmt.Sprintf("${%s}", key)
}
// ToKey - creates key from name.
func (key KeyName) ToKey() Key {
return NewKey(key, "")
}
// Condition key names.
const (
// S3XAmzCopySource - key representing x-amz-copy-source HTTP header applicable to PutObject API only.
S3XAmzCopySource KeyName = "s3:x-amz-copy-source"
// S3XAmzServerSideEncryption - key representing x-amz-server-side-encryption HTTP header applicable
// to PutObject API only.
S3XAmzServerSideEncryption KeyName = "s3:x-amz-server-side-encryption"
// S3XAmzServerSideEncryptionCustomerAlgorithm - key representing
// x-amz-server-side-encryption-customer-algorithm HTTP header applicable to PutObject API only.
S3XAmzServerSideEncryptionCustomerAlgorithm KeyName = "s3:x-amz-server-side-encryption-customer-algorithm"
// S3XAmzMetadataDirective - key representing x-amz-metadata-directive HTTP header applicable to
// PutObject API only.
S3XAmzMetadataDirective KeyName = "s3:x-amz-metadata-directive"
// S3XAmzContentSha256 - set a static content-sha256 for all calls for a given action.
S3XAmzContentSha256 KeyName = "s3:x-amz-content-sha256"
// S3XAmzStorageClass - key representing x-amz-storage-class HTTP header applicable to PutObject API
// only.
S3XAmzStorageClass KeyName = "s3:x-amz-storage-class"
// S3XAmzServerSideEncryptionAwsKmsKeyID - key representing x-amz-server-side-encryption-aws-kms-key-id
// HTTP header for S3 API calls
S3XAmzServerSideEncryptionAwsKmsKeyID KeyName = "s3:x-amz-server-side-encryption-aws-kms-key-id"
// S3LocationConstraint - key representing LocationConstraint XML tag of CreateBucket API only.
S3LocationConstraint KeyName = "s3:LocationConstraint"
// S3Prefix - key representing prefix query parameter of ListBucket API only.
S3Prefix KeyName = "s3:prefix"
// S3Delimiter - key representing delimiter query parameter of ListBucket API only.
S3Delimiter KeyName = "s3:delimiter"
// S3VersionID - Enables you to limit the permission for the
// s3:PutObjectVersionTagging action to a specific object version.
S3VersionID KeyName = "s3:versionid"
// S3MaxKeys - key representing max-keys query parameter of ListBucket API only.
S3MaxKeys KeyName = "s3:max-keys"
// S3ObjectLockRemainingRetentionDays - key representing object-lock-remaining-retention-days
// Enables enforcement of an object relative to the remaining retention days, you can set
// minimum and maximum allowable retention periods for a bucket using a bucket policy.
// This key are specific for s3:PutObjectRetention API.
S3ObjectLockRemainingRetentionDays KeyName = "s3:object-lock-remaining-retention-days"
// S3ObjectLockMode - key representing object-lock-mode
// Enables enforcement of the specified object retention mode
S3ObjectLockMode KeyName = "s3:object-lock-mode"
// S3ObjectLockRetainUntilDate - key representing object-lock-retain-util-date
// Enables enforcement of a specific retain-until-date
S3ObjectLockRetainUntilDate KeyName = "s3:object-lock-retain-until-date"
// S3ObjectLockLegalHold - key representing object-local-legal-hold
// Enables enforcement of the specified object legal hold status
S3ObjectLockLegalHold KeyName = "s3:object-lock-legal-hold"
// AWSReferer - key representing Referer header of any API.
AWSReferer KeyName = "aws:Referer"
// AWSSourceIP - key representing client's IP address (not intermittent proxies) of any API.
AWSSourceIP KeyName = "aws:SourceIp"
// AWSUserAgent - key representing UserAgent header for any API.
AWSUserAgent KeyName = "aws:UserAgent"
// AWSSecureTransport - key representing if the clients request is authenticated or not.
AWSSecureTransport KeyName = "aws:SecureTransport"
// AWSCurrentTime - key representing the current time.
AWSCurrentTime KeyName = "aws:CurrentTime"
// AWSEpochTime - key representing the current epoch time.
AWSEpochTime KeyName = "aws:EpochTime"
// AWSPrincipalType - user principal type currently supported values are "User" and "Anonymous".
AWSPrincipalType KeyName = "aws:principaltype"
// AWSUserID - user unique ID, in MinIO this value is same as your user Access Key.
AWSUserID KeyName = "aws:userid"
// AWSUsername - user friendly name, in MinIO this value is same as your user Access Key.
AWSUsername KeyName = "aws:username"
// AWSGroups - groups for any authenticating Access Key.
AWSGroups KeyName = "aws:groups"
// S3SignatureVersion - identifies the version of AWS Signature that you want to support for authenticated requests.
S3SignatureVersion KeyName = "s3:signatureversion"
// S3SignatureAge - identifies the maximum age of presgiend URL allowed
S3SignatureAge KeyName = "s3:signatureAge"
// S3AuthType - optionally use this condition key to restrict incoming requests to use a specific authentication method.
S3AuthType KeyName = "s3:authType"
// Refer https://docs.aws.amazon.com/AmazonS3/latest/userguide/tagging-and-policies.html
ExistingObjectTag KeyName = "s3:ExistingObjectTag"
RequestObjectTagKeys KeyName = "s3:RequestObjectTagKeys"
RequestObjectTag KeyName = "s3:RequestObjectTag"
)
// JWT claims supported substitutions.
// https://www.iana.org/assignments/jwt/jwt.xhtml#claims
const (
// JWTSub - JWT subject claim substitution.
JWTSub KeyName = "jwt:sub"
// JWTIss issuer claim substitution.
JWTIss KeyName = "jwt:iss"
// JWTAud audience claim substitution.
JWTAud KeyName = "jwt:aud"
// JWTJti JWT unique identifier claim substitution.
JWTJti KeyName = "jwt:jti"
JWTUpn KeyName = "jwt:upn"
JWTName KeyName = "jwt:name"
JWTGroups KeyName = "jwt:groups"
JWTGivenName KeyName = "jwt:given_name"
JWTFamilyName KeyName = "jwt:family_name"
JWTMiddleName KeyName = "jwt:middle_name"
JWTNickName KeyName = "jwt:nickname"
JWTPrefUsername KeyName = "jwt:preferred_username"
JWTProfile KeyName = "jwt:profile"
JWTPicture KeyName = "jwt:picture"
JWTWebsite KeyName = "jwt:website"
JWTEmail KeyName = "jwt:email"
JWTGender KeyName = "jwt:gender"
JWTBirthdate KeyName = "jwt:birthdate"
JWTPhoneNumber KeyName = "jwt:phone_number"
JWTAddress KeyName = "jwt:address"
JWTScope KeyName = "jwt:scope"
JWTClientID KeyName = "jwt:client_id"
)
const (
// LDAPUser - LDAP username, in MinIO this value is equal to your authenticating LDAP user DN.
LDAPUser KeyName = "ldap:user"
// LDAPUsername - LDAP username, in MinIO is the authenticated simple user.
LDAPUsername KeyName = "ldap:username"
// LDAPGroups - LDAP groups, in MinIO this value is equal LDAP Group DNs for the authenticating user.
LDAPGroups KeyName = "ldap:groups"
)
const (
// STSDurationSeconds - Duration seconds condition for STS policy
STSDurationSeconds KeyName = "sts:DurationSeconds"
// SVCDurationSeconds - Duration seconds condition for Admin policy
SVCDurationSeconds KeyName = "svc:DurationSeconds"
)
// JWTKeys - Supported JWT keys, non-exhaustive list please
// expand as new claims are standardized.
var JWTKeys = []KeyName{
JWTSub,
JWTIss,
JWTAud,
JWTJti,
JWTName,
JWTUpn,
JWTGroups,
JWTGivenName,
JWTFamilyName,
JWTMiddleName,
JWTNickName,
JWTPrefUsername,
JWTProfile,
JWTPicture,
JWTWebsite,
JWTEmail,
JWTGender,
JWTBirthdate,
JWTPhoneNumber,
JWTAddress,
JWTScope,
JWTClientID,
}
// AllSupportedKeys - is list of all all supported keys.
var AllSupportedKeys = []KeyName{
S3SignatureVersion,
S3AuthType,
S3SignatureAge,
S3XAmzCopySource,
S3XAmzServerSideEncryption,
S3XAmzServerSideEncryptionCustomerAlgorithm,
S3XAmzMetadataDirective,
S3XAmzStorageClass,
S3XAmzServerSideEncryptionAwsKmsKeyID,
S3XAmzContentSha256,
S3LocationConstraint,
S3Prefix,
S3Delimiter,
S3MaxKeys,
S3VersionID,
S3ObjectLockRemainingRetentionDays,
S3ObjectLockMode,
S3ObjectLockLegalHold,
S3ObjectLockRetainUntilDate,
AWSReferer,
AWSSourceIP,
AWSUserAgent,
AWSSecureTransport,
AWSCurrentTime,
AWSEpochTime,
AWSPrincipalType,
AWSUserID,
AWSUsername,
AWSGroups,
LDAPUser,
LDAPUsername,
LDAPGroups,
RequestObjectTag,
ExistingObjectTag,
RequestObjectTagKeys,
JWTSub,
JWTIss,
JWTAud,
JWTJti,
JWTName,
JWTUpn,
JWTGroups,
JWTGivenName,
JWTFamilyName,
JWTMiddleName,
JWTNickName,
JWTPrefUsername,
JWTProfile,
JWTPicture,
JWTWebsite,
JWTEmail,
JWTGender,
JWTBirthdate,
JWTPhoneNumber,
JWTAddress,
JWTScope,
JWTClientID,
STSDurationSeconds,
SVCDurationSeconds,
}
// CommonKeys - is list of all common condition keys.
var CommonKeys = append([]KeyName{
S3SignatureVersion,
S3AuthType,
S3SignatureAge,
S3XAmzContentSha256,
S3LocationConstraint,
AWSReferer,
AWSSourceIP,
AWSUserAgent,
AWSSecureTransport,
AWSCurrentTime,
AWSEpochTime,
AWSPrincipalType,
AWSUserID,
AWSUsername,
AWSGroups,
LDAPUser,
LDAPUsername,
LDAPGroups,
}, JWTKeys...)
// AllSupportedAdminKeys - is list of all admin supported keys.
var AllSupportedAdminKeys = append([]KeyName{
AWSReferer,
AWSSourceIP,
AWSUserAgent,
AWSSecureTransport,
AWSCurrentTime,
AWSEpochTime,
AWSPrincipalType,
AWSUserID,
AWSUsername,
AWSGroups,
LDAPUser,
LDAPUsername,
LDAPGroups,
SVCDurationSeconds,
// Add new supported condition keys.
}, JWTKeys...)
// AllSupportedSTSKeys is the all supported conditions for STS policies
var AllSupportedSTSKeys = []KeyName{
STSDurationSeconds,
// Add new supported condition keys.
}
golang-github-minio-pkg-3.0.10/policy/condition/name.go 0000664 0000000 0000000 00000010433 14655770405 0022764 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package condition
import (
"encoding/json"
"fmt"
"strings"
)
const (
// names
stringEquals = "StringEquals"
stringNotEquals = "StringNotEquals"
stringEqualsIgnoreCase = "StringEqualsIgnoreCase"
stringNotEqualsIgnoreCase = "StringNotEqualsIgnoreCase"
stringLike = "StringLike"
stringNotLike = "StringNotLike"
binaryEquals = "BinaryEquals"
ipAddress = "IpAddress"
notIPAddress = "NotIpAddress"
null = "Null"
boolean = "Bool"
numericEquals = "NumericEquals"
numericNotEquals = "NumericNotEquals"
numericLessThan = "NumericLessThan"
numericLessThanEquals = "NumericLessThanEquals"
numericGreaterThan = "NumericGreaterThan"
numericGreaterThanIfExists = "NumericGreaterThanIfExists"
numericGreaterThanEquals = "NumericGreaterThanEquals"
dateEquals = "DateEquals"
dateNotEquals = "DateNotEquals"
dateLessThan = "DateLessThan"
dateLessThanEquals = "DateLessThanEquals"
dateGreaterThan = "DateGreaterThan"
dateGreaterThanEquals = "DateGreaterThanEquals"
// qualifiers
// refer https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_multi-value-conditions.html#reference_policies_multi-key-or-value-conditions
forAllValues = "ForAllValues"
forAnyValue = "ForAnyValue"
)
var names = map[string]struct{}{
stringEquals: {},
stringNotEquals: {},
stringEqualsIgnoreCase: {},
stringNotEqualsIgnoreCase: {},
binaryEquals: {},
stringLike: {},
stringNotLike: {},
ipAddress: {},
notIPAddress: {},
null: {},
boolean: {},
numericEquals: {},
numericNotEquals: {},
numericLessThan: {},
numericLessThanEquals: {},
numericGreaterThan: {},
numericGreaterThanIfExists: {},
numericGreaterThanEquals: {},
dateEquals: {},
dateNotEquals: {},
dateLessThan: {},
dateLessThanEquals: {},
dateGreaterThan: {},
dateGreaterThanEquals: {},
}
var qualifiers = map[string]struct{}{
forAllValues: {},
forAnyValue: {},
}
type name struct {
qualifier string
name string
}
func (n name) String() string {
if n.qualifier != "" {
return n.qualifier + ":" + n.name
}
return n.name
}
// IsValid - checks if name is valid or not.
func (n name) IsValid() bool {
if n.qualifier != "" {
if _, found := qualifiers[n.qualifier]; !found {
return false
}
}
_, found := names[n.name]
return found
}
// MarshalJSON - encodes name to JSON data.
func (n name) MarshalJSON() ([]byte, error) {
if !n.IsValid() {
return nil, fmt.Errorf("invalid name %v", n)
}
return json.Marshal(n.String())
}
// UnmarshalJSON - decodes JSON data to condition name.
func (n *name) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
parsedName, err := parseName(s)
if err != nil {
return err
}
*n = parsedName
return nil
}
func parseName(s string) (name, error) {
tokens := strings.Split(s, ":")
var n name
switch len(tokens) {
case 0, 1:
n = name{name: s}
case 2:
n = name{qualifier: tokens[0], name: tokens[1]}
default:
return n, fmt.Errorf("invalid condition name '%v'", s)
}
if n.IsValid() {
return n, nil
}
return n, fmt.Errorf("invalid condition name '%v'", s)
}
golang-github-minio-pkg-3.0.10/policy/condition/name_test.go 0000664 0000000 0000000 00000007173 14655770405 0024032 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package condition
import (
"encoding/json"
"reflect"
"testing"
)
func TestNameIsValid(t *testing.T) {
testCases := []struct {
n name
expectedResult bool
}{
{name{name: stringEquals}, true},
{name{name: stringNotEquals}, true},
{name{name: stringLike}, true},
{name{name: stringNotLike}, true},
{name{name: ipAddress}, true},
{name{name: notIPAddress}, true},
{name{name: null}, true},
{name{name: "foo"}, false},
{name{qualifier: forAllValues, name: stringEquals}, true},
{name{qualifier: forAnyValue, name: stringNotEquals}, true},
}
for i, testCase := range testCases {
result := testCase.n.IsValid()
if testCase.expectedResult != result {
t.Fatalf("case %v: expected: %v, got: %v", i+1, testCase.expectedResult, result)
}
}
}
func TestNameMarshalJSON(t *testing.T) {
testCases := []struct {
n name
expectedResult []byte
expectErr bool
}{
{name{name: stringEquals}, []byte(`"StringEquals"`), false},
{name{name: stringNotEquals}, []byte(`"StringNotEquals"`), false},
{name{name: stringLike}, []byte(`"StringLike"`), false},
{name{name: stringNotLike}, []byte(`"StringNotLike"`), false},
{name{name: ipAddress}, []byte(`"IpAddress"`), false},
{name{name: notIPAddress}, []byte(`"NotIpAddress"`), false},
{name{name: null}, []byte(`"Null"`), false},
{name{name: "foo"}, nil, true},
{name{qualifier: forAllValues, name: stringEquals}, []byte(`"ForAllValues:StringEquals"`), false},
{name{qualifier: forAnyValue, name: stringNotEquals}, []byte(`"ForAnyValue:StringNotEquals"`), false},
}
for i, testCase := range testCases {
result, err := json.Marshal(testCase.n)
expectErr := (err != nil)
if testCase.expectErr != expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v", i+1, string(testCase.expectedResult), string(result))
}
}
}
}
func TestNameUnmarshalJSON(t *testing.T) {
testCases := []struct {
data []byte
expectedResult name
expectErr bool
}{
{[]byte(`"StringEquals"`), name{name: stringEquals}, false},
{[]byte(`"foo"`), name{name: ""}, true},
{[]byte(`"ForAllValues:StringEquals"`), name{qualifier: forAllValues, name: stringEquals}, false},
{[]byte(`"ForAnyValue:StringNotEquals"`), name{qualifier: forAnyValue, name: stringNotEquals}, false},
}
for i, testCase := range testCases {
var result name
err := json.Unmarshal(testCase.data, &result)
expectErr := (err != nil)
if testCase.expectErr != expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if testCase.expectedResult != result {
t.Fatalf("case %v: result: expected: %v, got: %v", i+1, testCase.expectedResult, result)
}
}
}
}
golang-github-minio-pkg-3.0.10/policy/condition/nullfunc.go 0000664 0000000 0000000 00000006101 14655770405 0023667 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package condition
import (
"fmt"
"reflect"
"strconv"
)
// nullFunc - Null condition function. It checks whether Key is not present in given
// values or not.
// For example,
// 1. if Key = S3XAmzCopySource and Value = true, at evaluate() it returns whether
// S3XAmzCopySource is NOT in given value map or not.
// 2. if Key = S3XAmzCopySource and Value = false, at evaluate() it returns whether
// S3XAmzCopySource is in given value map or not.
//
// https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html#Conditions_Null
type nullFunc struct {
k Key
value bool
}
// evaluate() - evaluates to check whether Key is present in given values or not.
// Depending on condition boolean value, this function returns true or false.
func (f nullFunc) evaluate(values map[string][]string) bool {
rvalues := getValuesByKey(values, f.k)
if f.value {
return len(rvalues) == 0
}
return len(rvalues) != 0
}
// key() - returns condition key which is used by this condition function.
func (f nullFunc) key() Key {
return f.k
}
// name() - returns "Null" condition name.
func (f nullFunc) name() name {
return name{name: null}
}
func (f nullFunc) String() string {
return fmt.Sprintf("%v:%v:%v", null, f.k, f.value)
}
// toMap - returns map representation of this function.
func (f nullFunc) toMap() map[Key]ValueSet {
if !f.k.IsValid() {
return nil
}
return map[Key]ValueSet{
f.k: NewValueSet(NewBoolValue(f.value)),
}
}
func (f nullFunc) clone() Function {
return &nullFunc{
k: f.k,
value: f.value,
}
}
func newNullFunc(key Key, values ValueSet, _ string) (Function, error) {
if len(values) != 1 {
return nil, fmt.Errorf("only one value is allowed for Null condition")
}
var value bool
for v := range values {
switch v.GetType() {
case reflect.Bool:
value, _ = v.GetBool()
case reflect.String:
var err error
s, _ := v.GetString()
if value, err = strconv.ParseBool(s); err != nil {
return nil, fmt.Errorf("value must be a boolean string for Null condition")
}
default:
return nil, fmt.Errorf("value must be a boolean for Null condition")
}
}
return &nullFunc{key, value}, nil
}
// NewNullFunc - returns new Null function.
func NewNullFunc(key Key, value bool) (Function, error) {
return newNullFunc(key, NewValueSet(NewBoolValue(value)), "")
}
golang-github-minio-pkg-3.0.10/policy/condition/nullfunc_test.go 0000664 0000000 0000000 00000014455 14655770405 0024741 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package condition
import (
"reflect"
"testing"
)
func TestNullFuncEvaluate(t *testing.T) {
case1Function, err := newNullFunc(S3Prefix.ToKey(), NewValueSet(NewBoolValue(true)), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Function, err := newNullFunc(S3Prefix.ToKey(), NewValueSet(NewBoolValue(false)), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
function Function
values map[string][]string
expectedResult bool
}{
{case1Function, map[string][]string{"prefix": {"true"}}, false},
{case1Function, map[string][]string{"prefix": {"false"}}, false},
{case1Function, map[string][]string{"prefix": {"mybucket/foo"}}, false},
{case1Function, map[string][]string{}, true},
{case1Function, map[string][]string{"delimiter": {"/"}}, true},
{case2Function, map[string][]string{"prefix": {"true"}}, true},
{case2Function, map[string][]string{"prefix": {"false"}}, true},
{case2Function, map[string][]string{"prefix": {"mybucket/foo"}}, true},
{case2Function, map[string][]string{}, false},
{case2Function, map[string][]string{"delimiter": {"/"}}, false},
}
for i, testCase := range testCases {
result := testCase.function.evaluate(testCase.values)
if result != testCase.expectedResult {
t.Errorf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestNullFuncKey(t *testing.T) {
case1Function, err := newNullFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewBoolValue(true)), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
function Function
expectedResult Key
}{
{case1Function, S3XAmzCopySource.ToKey()},
}
for i, testCase := range testCases {
result := testCase.function.key()
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestNullFuncName(t *testing.T) {
case1Function, err := newNullFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewBoolValue(true)), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
function Function
expectedResult name
}{
{case1Function, name{name: null}},
}
for i, testCase := range testCases {
result := testCase.function.name()
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestNullFuncToMap(t *testing.T) {
case1Function, err := newNullFunc(S3Prefix.ToKey(), NewValueSet(NewBoolValue(true)), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case1Result := map[Key]ValueSet{
S3Prefix.ToKey(): NewValueSet(NewBoolValue(true)),
}
case2Function, err := newNullFunc(S3Prefix.ToKey(), NewValueSet(NewBoolValue(false)), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Result := map[Key]ValueSet{
S3Prefix.ToKey(): NewValueSet(NewBoolValue(false)),
}
testCases := []struct {
f Function
expectedResult map[Key]ValueSet
}{
{case1Function, case1Result},
{case2Function, case2Result},
{&nullFunc{}, nil},
}
for i, testCase := range testCases {
result := testCase.f.toMap()
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestNullFuncClone(t *testing.T) {
case1Function, err := newNullFunc(S3Prefix.ToKey(), NewValueSet(NewBoolValue(true)), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case1Result := &nullFunc{
k: S3Prefix.ToKey(),
value: true,
}
case2Function, err := newNullFunc(S3Prefix.ToKey(), NewValueSet(NewBoolValue(false)), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Result := &nullFunc{
k: S3Prefix.ToKey(),
value: false,
}
testCases := []struct {
f Function
expectedResult Function
}{
{case1Function, case1Result},
{case2Function, case2Result},
}
for i, testCase := range testCases {
result := testCase.f.clone()
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %+v, got: %+v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestNewNullFunc(t *testing.T) {
case1Function, err := newNullFunc(S3Prefix.ToKey(), NewValueSet(NewBoolValue(true)), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Function, err := newNullFunc(S3Prefix.ToKey(), NewValueSet(NewBoolValue(false)), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
key Key
values ValueSet
expectedResult Function
expectErr bool
}{
{S3Prefix.ToKey(), NewValueSet(NewBoolValue(true)), case1Function, false},
{S3Prefix.ToKey(), NewValueSet(NewStringValue("false")), case2Function, false},
// Multiple values error.
{S3Prefix.ToKey(), NewValueSet(NewBoolValue(true), NewBoolValue(false)), nil, true},
// Invalid boolean string error.
{S3Prefix.ToKey(), NewValueSet(NewStringValue("foo")), nil, true},
// Invalid value error.
{S3Prefix.ToKey(), NewValueSet(NewIntValue(7)), nil, true},
}
for i, testCase := range testCases {
result, err := newNullFunc(testCase.key, testCase.values, "")
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
}
golang-github-minio-pkg-3.0.10/policy/condition/numericfunc.go 0000664 0000000 0000000 00000014431 14655770405 0024364 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package condition
import (
"fmt"
"reflect"
"strconv"
)
type numericFunc struct {
n name
k Key
value int
c condition
ifExists bool
}
func (f numericFunc) evaluate(values map[string][]string) (ret bool) {
rvalues := getValuesByKey(values, f.k)
if len(rvalues) == 0 {
return f.ifExists
}
rv, err := strconv.Atoi(rvalues[0])
if err != nil {
return false
}
switch f.c {
case equals:
return rv == f.value
case notEquals:
return rv != f.value
case greaterThan:
return rv > f.value
case greaterThanEquals:
return rv >= f.value
case lessThan:
return rv < f.value
case lessThanEquals:
return rv <= f.value
}
// This never happens.
return false
}
func (f numericFunc) key() Key {
return f.k
}
func (f numericFunc) name() name {
return f.n
}
func (f numericFunc) String() string {
return fmt.Sprintf("%v:%v:%v:%v", f.n, f.ifExists, f.k, f.value)
}
func (f numericFunc) toMap() map[Key]ValueSet {
if !f.k.IsValid() {
return nil
}
values := NewValueSet()
values.Add(NewIntValue(f.value))
return map[Key]ValueSet{
f.k: values,
}
}
func (f numericFunc) clone() Function {
return &numericFunc{
n: f.n,
k: f.k,
value: f.value,
c: f.c,
ifExists: f.ifExists,
}
}
func valueToInt(n string, values ValueSet) (v int, err error) {
if len(values) != 1 {
return -1, fmt.Errorf("only one value is allowed for %s condition", n)
}
for vs := range values {
switch vs.GetType() {
case reflect.Int:
if v, err = vs.GetInt(); err != nil {
return -1, err
}
case reflect.String:
s, err := vs.GetString()
if err != nil {
return -1, err
}
if v, err = strconv.Atoi(s); err != nil {
return -1, fmt.Errorf("value %s must be a int for %s condition: %w", vs, n, err)
}
default:
return -1, fmt.Errorf("value %s must be a int for %s condition", vs, n)
}
}
return v, nil
}
func newNumericFunc(n string, ifExists bool, key Key, values ValueSet, cond condition) (Function, error) {
v, err := valueToInt(n, values)
if err != nil {
return nil, err
}
return &numericFunc{
n: name{name: n},
k: key,
value: v,
c: cond,
ifExists: ifExists,
}, nil
}
// newNumericEqualsFunc - returns new NumericEquals function.
func newNumericEqualsFunc(key Key, values ValueSet, _ string) (Function, error) {
return newNumericFunc(numericEquals, false, key, values, equals)
}
// NewNumericEqualsFunc - returns new NumericEquals function.
func NewNumericEqualsFunc(key Key, value int) (Function, error) {
return &numericFunc{n: name{name: numericEquals}, k: key, value: value, c: equals}, nil
}
// newNumericNotEqualsFunc - returns new NumericNotEquals function.
func newNumericNotEqualsFunc(key Key, values ValueSet, _ string) (Function, error) {
return newNumericFunc(numericNotEquals, false, key, values, notEquals)
}
// NewNumericNotEqualsFunc - returns new NumericNotEquals function.
func NewNumericNotEqualsFunc(key Key, value int) (Function, error) {
return &numericFunc{n: name{name: numericNotEquals}, k: key, value: value, c: notEquals}, nil
}
// newNumericGreaterThanFunc - returns new NumericGreaterThan function.
func newNumericGreaterThanFunc(key Key, values ValueSet, _ string) (Function, error) {
return newNumericFunc(numericGreaterThan, false, key, values, greaterThan)
}
// NewNumericGreaterThanFunc - returns new NumericGreaterThan function.
func NewNumericGreaterThanFunc(key Key, value int) (Function, error) {
return &numericFunc{n: name{name: numericGreaterThan}, k: key, value: value, c: greaterThan}, nil
}
// newNumericGreaterThanIfExistsFunc - returns new NumericGreaterThanIfExists function.
func newNumericGreaterThanIfExistsFunc(key Key, values ValueSet, _ string) (Function, error) {
return newNumericFunc(numericGreaterThanIfExists, true, key, values, greaterThan)
}
// NewNumericGreaterThanIfExistsFunc - returns new NumericGreaterThanIfExists function.
func NewNumericGreaterThanIfExistsFunc(key Key, value int) (Function, error) {
return &numericFunc{n: name{name: numericGreaterThan}, ifExists: true, k: key, value: value, c: greaterThan}, nil
}
// newNumericGreaterThanEqualsFunc - returns new NumericGreaterThanEquals function.
func newNumericGreaterThanEqualsFunc(key Key, values ValueSet, _ string) (Function, error) {
return newNumericFunc(numericGreaterThanEquals, false, key, values, greaterThanEquals)
}
// NewNumericGreaterThanEqualsFunc - returns new NumericGreaterThanEquals function.
func NewNumericGreaterThanEqualsFunc(key Key, value int) (Function, error) {
return &numericFunc{n: name{name: numericGreaterThanEquals}, k: key, value: value, c: greaterThanEquals}, nil
}
// newNumericLessThanFunc - returns new NumericLessThan function.
func newNumericLessThanFunc(key Key, values ValueSet, _ string) (Function, error) {
return newNumericFunc(numericLessThan, false, key, values, lessThan)
}
// NewNumericLessThanFunc - returns new NumericLessThan function.
func NewNumericLessThanFunc(key Key, value int) (Function, error) {
return &numericFunc{n: name{name: numericLessThan}, k: key, value: value, c: lessThan}, nil
}
// newNumericLessThanEqualsFunc - returns new NumericLessThanEquals function.
func newNumericLessThanEqualsFunc(key Key, values ValueSet, _ string) (Function, error) {
return newNumericFunc(numericLessThanEquals, false, key, values, lessThanEquals)
}
// NewNumericLessThanEqualsFunc - returns new NumericLessThanEquals function.
func NewNumericLessThanEqualsFunc(key Key, value int) (Function, error) {
return &numericFunc{n: name{name: numericLessThanEquals}, k: key, value: value, c: lessThanEquals}, nil
}
golang-github-minio-pkg-3.0.10/policy/condition/numericfunc_test.go 0000664 0000000 0000000 00000023621 14655770405 0025424 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package condition
import (
"reflect"
"testing"
)
func testNumericFuncEvaluate(t *testing.T, funcs ...Function) {
testCases := []struct {
function Function
values map[string][]string
expectedResult bool
}{
{funcs[0], map[string][]string{"max-keys": {"16"}}, true},
{funcs[0], map[string][]string{"max-keys": {"61"}}, false},
{funcs[1], map[string][]string{"max-keys": {"16"}}, false},
{funcs[1], map[string][]string{"max-keys": {"61"}}, true},
{funcs[2], map[string][]string{"max-keys": {"16"}}, false},
{funcs[2], map[string][]string{"max-keys": {"6"}}, false},
{funcs[2], map[string][]string{"max-keys": {"61"}}, true},
{funcs[3], map[string][]string{"max-keys": {"16"}}, true},
{funcs[3], map[string][]string{"max-keys": {"6"}}, false},
{funcs[3], map[string][]string{"max-keys": {"61"}}, true},
{funcs[4], map[string][]string{"max-keys": {"16"}}, false},
{funcs[4], map[string][]string{"max-keys": {"6"}}, true},
{funcs[4], map[string][]string{"max-keys": {"61"}}, false},
{funcs[5], map[string][]string{"max-keys": {"16"}}, true},
{funcs[5], map[string][]string{"max-keys": {"6"}}, true},
{funcs[5], map[string][]string{"max-keys": {"61"}}, false},
{funcs[6], map[string][]string{"max-keys": {"16"}}, false},
{funcs[6], map[string][]string{"max-keys": {"61"}}, true},
{funcs[6], map[string][]string{}, true},
}
for i, testCase := range testCases {
result := testCase.function.evaluate(testCase.values)
if result != testCase.expectedResult {
t.Errorf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestNumericFuncEvaluate(t *testing.T) {
valueSet := NewValueSet(NewIntValue(16))
case1Function, err := newNumericEqualsFunc(S3MaxKeys.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Function, err := newNumericNotEqualsFunc(S3MaxKeys.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case3Function, err := newNumericGreaterThanFunc(S3MaxKeys.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case4Function, err := newNumericGreaterThanEqualsFunc(S3MaxKeys.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case5Function, err := newNumericLessThanFunc(S3MaxKeys.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case6Function, err := newNumericLessThanEqualsFunc(S3MaxKeys.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case7Function, err := newNumericGreaterThanIfExistsFunc(S3MaxKeys.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testNumericFuncEvaluate(t, case1Function, case2Function, case3Function, case4Function, case5Function, case6Function, case7Function)
if _, err := newNumericEqualsFunc(S3MaxKeys.ToKey(), NewValueSet(NewIntValue(16), NewStringValue("16")), ""); err == nil {
t.Fatalf("error expected")
}
if _, err := newNumericEqualsFunc(S3MaxKeys.ToKey(), NewValueSet(NewStringValue("sixy one")), ""); err == nil {
t.Fatalf("error expected")
}
if _, err := newNumericEqualsFunc(S3MaxKeys.ToKey(), NewValueSet(NewBoolValue(true)), ""); err == nil {
t.Fatalf("error expected")
}
}
func TestNewNumericFuncEvaluate(t *testing.T) {
case1Function, err := NewNumericEqualsFunc(S3MaxKeys.ToKey(), 16)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Function, err := NewNumericNotEqualsFunc(S3MaxKeys.ToKey(), 16)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case3Function, err := NewNumericGreaterThanFunc(S3MaxKeys.ToKey(), 16)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case4Function, err := NewNumericGreaterThanEqualsFunc(S3MaxKeys.ToKey(), 16)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case5Function, err := NewNumericLessThanFunc(S3MaxKeys.ToKey(), 16)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case6Function, err := NewNumericLessThanEqualsFunc(S3MaxKeys.ToKey(), 16)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case7Function, err := NewNumericGreaterThanIfExistsFunc(S3MaxKeys.ToKey(), 16)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testNumericFuncEvaluate(t, case1Function, case2Function, case3Function, case4Function, case5Function, case6Function, case7Function)
}
func TestNumericFuncKey(t *testing.T) {
case1Function, err := newNumericEqualsFunc(S3MaxKeys.ToKey(), NewValueSet(NewStringValue("16")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
function Function
expectedResult Key
}{
{case1Function, S3MaxKeys.ToKey()},
}
for i, testCase := range testCases {
result := testCase.function.key()
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestNumericFuncName(t *testing.T) {
valueSet := NewValueSet(NewStringValue("16"))
case1Function, err := newNumericEqualsFunc(S3MaxKeys.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Function, err := newNumericNotEqualsFunc(S3MaxKeys.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case3Function, err := newNumericGreaterThanFunc(S3MaxKeys.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case4Function, err := newNumericGreaterThanEqualsFunc(S3MaxKeys.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case5Function, err := newNumericLessThanFunc(S3MaxKeys.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case6Function, err := newNumericLessThanEqualsFunc(S3MaxKeys.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
function Function
expectedResult name
}{
{case1Function, name{name: numericEquals}},
{case2Function, name{name: numericNotEquals}},
{case3Function, name{name: numericGreaterThan}},
{case4Function, name{name: numericGreaterThanEquals}},
{case5Function, name{name: numericLessThan}},
{case6Function, name{name: numericLessThanEquals}},
}
for i, testCase := range testCases {
result := testCase.function.name()
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestNumericFuncToMap(t *testing.T) {
valueSet := NewValueSet(NewIntValue(16))
case1Function, err := newNumericEqualsFunc(S3MaxKeys.ToKey(), valueSet, "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case1Result := map[Key]ValueSet{S3MaxKeys.ToKey(): valueSet}
testCases := []struct {
f Function
expectedResult map[Key]ValueSet
}{
{case1Function, case1Result},
}
for i, testCase := range testCases {
result := testCase.f.toMap()
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestNumericFuncClone(t *testing.T) {
case1Function, err := NewNumericEqualsFunc(S3MaxKeys.ToKey(), 16)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case1Result := &numericFunc{
n: name{name: numericEquals},
k: S3MaxKeys.ToKey(),
value: 16,
c: equals,
}
case2Function, err := NewNumericNotEqualsFunc(S3MaxKeys.ToKey(), 16)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Result := &numericFunc{
n: name{name: numericNotEquals},
k: S3MaxKeys.ToKey(),
value: 16,
c: notEquals,
}
case3Function, err := NewNumericGreaterThanFunc(S3MaxKeys.ToKey(), 16)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case3Result := &numericFunc{
n: name{name: numericGreaterThan},
k: S3MaxKeys.ToKey(),
value: 16,
c: greaterThan,
}
case4Function, err := NewNumericGreaterThanEqualsFunc(S3MaxKeys.ToKey(), 16)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case4Result := &numericFunc{
n: name{name: numericGreaterThanEquals},
k: S3MaxKeys.ToKey(),
value: 16,
c: greaterThanEquals,
}
case5Function, err := NewNumericLessThanFunc(S3MaxKeys.ToKey(), 16)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case5Result := &numericFunc{
n: name{name: numericLessThan},
k: S3MaxKeys.ToKey(),
value: 16,
c: lessThan,
}
case6Function, err := NewNumericLessThanEqualsFunc(S3MaxKeys.ToKey(), 16)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case6Result := &numericFunc{
n: name{name: numericLessThanEquals},
k: S3MaxKeys.ToKey(),
value: 16,
c: lessThanEquals,
}
testCases := []struct {
function Function
expectedResult Function
}{
{case1Function, case1Result},
{case2Function, case2Result},
{case3Function, case3Result},
{case4Function, case4Result},
{case5Function, case5Result},
{case6Function, case6Result},
}
for i, testCase := range testCases {
result := testCase.function.clone()
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
golang-github-minio-pkg-3.0.10/policy/condition/stringfunc.go 0000664 0000000 0000000 00000024674 14655770405 0024242 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package condition
import (
"encoding/base64"
"fmt"
"sort"
"strings"
"github.com/minio/minio-go/v7/pkg/s3utils"
"github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/pkg/v3/wildcard"
)
func substitute(values map[string][]string) func(string) string {
return func(v string) string {
for _, key := range CommonKeys {
// Empty values are not supported for policy variables.
if rvalues, ok := values[key.Name()]; ok && rvalues[0] != "" {
v = strings.Replace(v, key.VarName(), rvalues[0], -1)
}
}
return v
}
}
type stringFunc struct {
n name
k Key
values set.StringSet
ignoreCase bool
base64 bool
negate bool
}
func (f stringFunc) eval(values map[string][]string) bool {
rvalues := set.CreateStringSet(getValuesByKey(values, f.k)...)
fvalues := f.values.ApplyFunc(substitute(values))
if f.ignoreCase {
rvalues = rvalues.ApplyFunc(strings.ToLower)
fvalues = fvalues.ApplyFunc(strings.ToLower)
}
ivalues := rvalues.Intersection(fvalues)
if f.n.qualifier == forAllValues {
return rvalues.IsEmpty() || rvalues.Equals(ivalues)
}
return !ivalues.IsEmpty()
}
func (f stringFunc) evaluate(values map[string][]string) bool {
result := f.eval(values)
if f.negate {
return !result
}
return result
}
func (f stringFunc) key() Key {
return f.k
}
func (f stringFunc) name() name {
return f.n
}
func (f stringFunc) String() string {
valueStrings := f.values.ToSlice()
sort.Strings(valueStrings)
return fmt.Sprintf("%v:%v:%v", f.n, f.k, valueStrings)
}
func (f stringFunc) toMap() map[Key]ValueSet {
if !f.k.IsValid() {
return nil
}
values := NewValueSet()
for _, value := range f.values.ToSlice() {
if f.base64 {
values.Add(NewStringValue(base64.StdEncoding.EncodeToString([]byte(value))))
} else {
values.Add(NewStringValue(value))
}
}
return map[Key]ValueSet{
f.k: values,
}
}
func (f stringFunc) copy() stringFunc {
return stringFunc{
n: f.n,
k: f.k,
values: f.values.Union(set.NewStringSet()),
ignoreCase: f.ignoreCase,
base64: f.base64,
negate: f.negate,
}
}
func (f stringFunc) clone() Function {
c := f.copy()
return &c
}
// stringLikeFunc - String like function. It checks whether value by Key in given
// values map is widcard matching in condition values.
// For example,
// - if values = ["mybucket/foo*"], at evaluate() it returns whether string
// in value map for Key is wildcard matching in values.
type stringLikeFunc struct {
stringFunc
}
func (f stringLikeFunc) eval(values map[string][]string) bool {
rvalues := getValuesByKey(values, f.k)
fvalues := f.values.ApplyFunc(substitute(values))
for _, v := range rvalues {
matched := !fvalues.FuncMatch(wildcard.Match, v).IsEmpty()
if f.n.qualifier == forAllValues {
if !matched {
return false
}
} else if matched {
return true
}
}
return f.n.qualifier == forAllValues
}
// evaluate() - evaluates to check whether value by Key in given values is wildcard
// matching in condition values.
func (f stringLikeFunc) evaluate(values map[string][]string) bool {
result := f.eval(values)
if f.negate {
return !result
}
return result
}
func (f stringLikeFunc) clone() Function {
return &stringLikeFunc{stringFunc: f.copy()}
}
func valuesToStringSlice(n string, values ValueSet) ([]string, error) {
valueStrings := []string{}
for value := range values {
s, err := value.GetString()
if err != nil {
return nil, fmt.Errorf("value must be a string for %v condition", n)
}
valueStrings = append(valueStrings, s)
}
return valueStrings, nil
}
func validateStringValues(n string, key Key, values set.StringSet) error {
for _, s := range values.ToSlice() {
switch {
case key.Is(S3XAmzCopySource):
bucket, object := path2BucketAndObject(s)
if object == "" {
return fmt.Errorf("invalid value '%v' for '%v' for %v condition", s, S3XAmzCopySource, n)
}
if err := s3utils.CheckValidBucketName(bucket); err != nil {
return err
}
}
if n == stringLike || n == stringNotLike {
continue
}
switch {
case key.Is(S3XAmzServerSideEncryptionCustomerAlgorithm):
if s != "AES256" {
return fmt.Errorf("invalid value '%v' for '%v' for %v condition", s, S3XAmzServerSideEncryptionCustomerAlgorithm, n)
}
case key.Is(S3XAmzServerSideEncryption):
if s != "AES256" && s != "aws:kms" {
return fmt.Errorf("invalid value '%v' for '%v' for %v condition", s, S3XAmzServerSideEncryption, n)
}
case key.Is(S3XAmzMetadataDirective):
if s != "COPY" && s != "REPLACE" {
return fmt.Errorf("invalid value '%v' for '%v' for %v condition", s, S3XAmzMetadataDirective, n)
}
case key.Is(S3XAmzContentSha256):
if s == "" {
return fmt.Errorf("invalid empty value for '%v' for %v condition", S3XAmzContentSha256, n)
}
}
}
return nil
}
func newStringFunc(n string, key Key, values ValueSet, qualifier string, ignoreCase, base64, negate bool) (*stringFunc, error) {
valueStrings, err := valuesToStringSlice(n, values)
if err != nil {
return nil, err
}
sset := set.CreateStringSet(valueStrings...)
if err := validateStringValues(n, key, sset); err != nil {
return nil, err
}
if _, found := qualifiers[qualifier]; qualifier != "" && !found {
return nil, fmt.Errorf("set qualifier must be %v or %v", forAllValues, forAllValues)
}
return &stringFunc{
n: name{name: n, qualifier: qualifier},
k: key,
values: sset,
ignoreCase: ignoreCase,
base64: base64,
negate: negate,
}, nil
}
// newStringEqualsFunc - returns new StringEquals function.
func newStringEqualsFunc(key Key, values ValueSet, qualifier string) (Function, error) {
return newStringFunc(stringEquals, key, values, qualifier, false, false, false)
}
// NewStringEqualsFunc - returns new StringEquals function.
func NewStringEqualsFunc(qualifier string, key Key, values ...string) (Function, error) {
vset := NewValueSet()
for _, value := range values {
vset.Add(NewStringValue(value))
}
return newStringFunc(stringEquals, key, vset, qualifier, false, false, false)
}
// newStringNotEqualsFunc - returns new StringNotEquals function.
func newStringNotEqualsFunc(key Key, values ValueSet, qualifier string) (Function, error) {
return newStringFunc(stringNotEquals, key, values, qualifier, false, false, true)
}
// NewStringNotEqualsFunc - returns new StringNotEquals function.
func NewStringNotEqualsFunc(qualifier string, key Key, values ...string) (Function, error) {
vset := NewValueSet()
for _, value := range values {
vset.Add(NewStringValue(value))
}
return newStringFunc(stringNotEquals, key, vset, qualifier, false, false, true)
}
// newStringEqualsIgnoreCaseFunc - returns new StringEqualsIgnoreCase function.
func newStringEqualsIgnoreCaseFunc(key Key, values ValueSet, qualifier string) (Function, error) {
return newStringFunc(stringEqualsIgnoreCase, key, values, qualifier, true, false, false)
}
// NewStringEqualsIgnoreCaseFunc - returns new StringEqualsIgnoreCase function.
func NewStringEqualsIgnoreCaseFunc(qualifier string, key Key, values ...string) (Function, error) {
vset := NewValueSet()
for _, value := range values {
vset.Add(NewStringValue(value))
}
return newStringFunc(stringEqualsIgnoreCase, key, vset, qualifier, true, false, false)
}
// newStringNotEqualsIgnoreCaseFunc - returns new StringNotEqualsIgnoreCase function.
func newStringNotEqualsIgnoreCaseFunc(key Key, values ValueSet, qualifier string) (Function, error) {
return newStringFunc(stringNotEqualsIgnoreCase, key, values, qualifier, true, false, true)
}
// NewStringNotEqualsIgnoreCaseFunc - returns new StringNotEqualsIgnoreCase function.
func NewStringNotEqualsIgnoreCaseFunc(qualifier string, key Key, values ...string) (Function, error) {
vset := NewValueSet()
for _, value := range values {
vset.Add(NewStringValue(value))
}
return newStringFunc(stringNotEqualsIgnoreCase, key, vset, qualifier, true, false, true)
}
// newBinaryEqualsFunc - returns new BinaryEquals function.
func newBinaryEqualsFunc(key Key, values ValueSet, qualifier string) (Function, error) {
valueStrings, err := valuesToStringSlice(binaryEquals, values)
if err != nil {
return nil, err
}
return NewBinaryEqualsFunc(qualifier, key, valueStrings...)
}
// NewBinaryEqualsFunc - returns new BinaryEquals function.
func NewBinaryEqualsFunc(qualifier string, key Key, values ...string) (Function, error) {
vset := NewValueSet()
for _, value := range values {
data, err := base64.StdEncoding.DecodeString(value)
if err != nil {
return nil, err
}
vset.Add(NewStringValue(string(data)))
}
return newStringFunc(binaryEquals, key, vset, qualifier, false, true, false)
}
// newStringLikeFunc - returns new StringLike function.
func newStringLikeFunc(key Key, values ValueSet, qualifier string) (Function, error) {
sf, err := newStringFunc(stringLike, key, values, qualifier, false, false, false)
if err != nil {
return nil, err
}
return &stringLikeFunc{*sf}, nil
}
// NewStringLikeFunc - returns new StringLike function.
func NewStringLikeFunc(qualifier string, key Key, values ...string) (Function, error) {
vset := NewValueSet()
for _, value := range values {
vset.Add(NewStringValue(value))
}
return newStringLikeFunc(key, vset, qualifier)
}
// newStringNotLikeFunc - returns new StringNotLike function.
func newStringNotLikeFunc(key Key, values ValueSet, qualifier string) (Function, error) {
sf, err := newStringFunc(stringNotLike, key, values, qualifier, false, false, true)
if err != nil {
return nil, err
}
return &stringLikeFunc{*sf}, nil
}
// NewStringNotLikeFunc - returns new StringNotLike function.
func NewStringNotLikeFunc(qualifier string, key Key, values ...string) (Function, error) {
vset := NewValueSet()
for _, value := range values {
vset.Add(NewStringValue(value))
}
return newStringNotLikeFunc(key, vset, qualifier)
}
golang-github-minio-pkg-3.0.10/policy/condition/stringfunc_test.go 0000664 0000000 0000000 00000072326 14655770405 0025276 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package condition
import (
"encoding/base64"
"reflect"
"testing"
"github.com/minio/minio-go/v7/pkg/set"
)
func TestStringEqualsFuncEvaluate(t *testing.T) {
case1Function, err := newStringEqualsFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Function, err := newStringEqualsFunc(S3LocationConstraint.ToKey(), NewValueSet(NewStringValue("eu-west-1"), NewStringValue("ap-southeast-1")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case3Function, err := newStringEqualsFunc(JWTGroups.ToKey(), NewValueSet(NewStringValue("prod"), NewStringValue("art")), forAllValues)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case4Function, err := newStringEqualsFunc(JWTGroups.ToKey(), NewValueSet(NewStringValue("prod"), NewStringValue("art")), forAnyValue)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case5Function, err := newStringEqualsFunc(S3LocationConstraint.ToKey(), NewValueSet(NewStringValue(S3LocationConstraint.ToKey().VarName())), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case6Function, err := newStringEqualsFunc(NewKey(ExistingObjectTag, "security"), NewValueSet(NewStringValue("public")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
function Function
values map[string][]string
expectedResult bool
}{
{case1Function, map[string][]string{"x-amz-copy-source": {"mybucket/myobject"}}, true},
{case1Function, map[string][]string{"x-amz-copy-source": {"yourbucket/myobject"}}, false},
{case1Function, map[string][]string{}, false},
{case1Function, map[string][]string{"delimiter": {"/"}}, false},
{case2Function, map[string][]string{"LocationConstraint": {"eu-west-1"}}, true},
{case2Function, map[string][]string{"LocationConstraint": {"ap-southeast-1"}}, true},
{case2Function, map[string][]string{"LocationConstraint": {"us-east-1"}}, false},
{case2Function, map[string][]string{}, false},
{case2Function, map[string][]string{"delimiter": {"/"}}, false},
{case3Function, map[string][]string{"groups": {"prod", "art"}}, true},
{case3Function, map[string][]string{"groups": {"art"}}, true},
{case3Function, map[string][]string{}, true},
{case3Function, map[string][]string{"delimiter": {"/"}}, true},
{case4Function, map[string][]string{"groups": {"prod", "art"}}, true},
{case4Function, map[string][]string{"groups": {"art"}}, true},
{case4Function, map[string][]string{}, false},
{case4Function, map[string][]string{"delimiter": {"/"}}, false},
{case5Function, map[string][]string{"LocationConstraint": {"us-west-1"}}, true},
{case6Function, map[string][]string{"ExistingObjectTag/security": {"public"}}, true},
{case6Function, map[string][]string{"ExistingObjectTag/security": {"private"}}, false},
{case6Function, map[string][]string{"ExistingObjectTag/project": {"foo"}}, false},
}
for i, testCase := range testCases {
result := testCase.function.evaluate(testCase.values)
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestStringNotEqualsFuncEvaluate(t *testing.T) {
case1Function, err := newStringNotEqualsFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Function, err := newStringNotEqualsFunc(S3LocationConstraint.ToKey(), NewValueSet(NewStringValue("eu-west-1"), NewStringValue("ap-southeast-1")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case3Function, err := newStringNotEqualsFunc(JWTGroups.ToKey(), NewValueSet(NewStringValue("prod"), NewStringValue("art")), forAllValues)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case4Function, err := newStringNotEqualsFunc(JWTGroups.ToKey(), NewValueSet(NewStringValue("prod"), NewStringValue("art")), forAnyValue)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
function Function
values map[string][]string
expectedResult bool
}{
{case1Function, map[string][]string{"x-amz-copy-source": {"mybucket/myobject"}}, false},
{case1Function, map[string][]string{"x-amz-copy-source": {"yourbucket/myobject"}}, true},
{case1Function, map[string][]string{}, true},
{case1Function, map[string][]string{"delimiter": {"/"}}, true},
{case2Function, map[string][]string{"LocationConstraint": {"eu-west-1"}}, false},
{case2Function, map[string][]string{"LocationConstraint": {"ap-southeast-1"}}, false},
{case2Function, map[string][]string{"LocationConstraint": {"us-east-1"}}, true},
{case2Function, map[string][]string{}, true},
{case2Function, map[string][]string{"delimiter": {"/"}}, true},
{case3Function, map[string][]string{"groups": {"prod", "art"}}, false},
{case3Function, map[string][]string{"groups": {"art"}}, false},
{case3Function, map[string][]string{}, false},
{case3Function, map[string][]string{"delimiter": {"/"}}, false},
{case4Function, map[string][]string{"groups": {"prod", "art"}}, false},
{case4Function, map[string][]string{"groups": {"art"}}, false},
{case4Function, map[string][]string{}, true},
{case4Function, map[string][]string{"delimiter": {"/"}}, true},
}
for i, testCase := range testCases {
result := testCase.function.evaluate(testCase.values)
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestStringEqualsIgnoreCaseFuncEvaluate(t *testing.T) {
case1Function, err := newStringEqualsIgnoreCaseFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/MYOBJECT")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Function, err := newStringEqualsIgnoreCaseFunc(S3LocationConstraint.ToKey(), NewValueSet(NewStringValue("EU-WEST-1"), NewStringValue("AP-southeast-1")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case3Function, err := newStringEqualsIgnoreCaseFunc(JWTGroups.ToKey(), NewValueSet(NewStringValue("Prod"), NewStringValue("Art")), forAllValues)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case4Function, err := newStringEqualsIgnoreCaseFunc(JWTGroups.ToKey(), NewValueSet(NewStringValue("Prod"), NewStringValue("Art")), forAnyValue)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
function Function
values map[string][]string
expectedResult bool
}{
{case1Function, map[string][]string{"x-amz-copy-source": {"mybucket/myobject"}}, true},
{case1Function, map[string][]string{"x-amz-copy-source": {"yourbucket/myobject"}}, false},
{case1Function, map[string][]string{}, false},
{case1Function, map[string][]string{"delimiter": {"/"}}, false},
{case2Function, map[string][]string{"LocationConstraint": {"eu-west-1"}}, true},
{case2Function, map[string][]string{"LocationConstraint": {"ap-southeast-1"}}, true},
{case2Function, map[string][]string{"LocationConstraint": {"us-east-1"}}, false},
{case2Function, map[string][]string{}, false},
{case2Function, map[string][]string{"delimiter": {"/"}}, false},
{case3Function, map[string][]string{"groups": {"prod", "art"}}, true},
{case3Function, map[string][]string{"groups": {"art"}}, true},
{case3Function, map[string][]string{}, true},
{case3Function, map[string][]string{"delimiter": {"/"}}, true},
{case4Function, map[string][]string{"groups": {"prod", "art"}}, true},
{case4Function, map[string][]string{"groups": {"art"}}, true},
{case4Function, map[string][]string{}, false},
{case4Function, map[string][]string{"delimiter": {"/"}}, false},
}
for i, testCase := range testCases {
result := testCase.function.evaluate(testCase.values)
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestStringNotEqualsIgnoreCaseFuncEvaluate(t *testing.T) {
case1Function, err := newStringNotEqualsIgnoreCaseFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/MYOBJECT")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Function, err := newStringNotEqualsIgnoreCaseFunc(S3LocationConstraint.ToKey(), NewValueSet(NewStringValue("EU-WEST-1"), NewStringValue("AP-southeast-1")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case3Function, err := newStringNotEqualsIgnoreCaseFunc(JWTGroups.ToKey(), NewValueSet(NewStringValue("Prod"), NewStringValue("Art")), forAllValues)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case4Function, err := newStringNotEqualsIgnoreCaseFunc(JWTGroups.ToKey(), NewValueSet(NewStringValue("Prod"), NewStringValue("Art")), forAnyValue)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
function Function
values map[string][]string
expectedResult bool
}{
{case1Function, map[string][]string{"x-amz-copy-source": {"mybucket/myobject"}}, false},
{case1Function, map[string][]string{"x-amz-copy-source": {"yourbucket/myobject"}}, true},
{case1Function, map[string][]string{}, true},
{case1Function, map[string][]string{"delimiter": {"/"}}, true},
{case2Function, map[string][]string{"LocationConstraint": {"eu-west-1"}}, false},
{case2Function, map[string][]string{"LocationConstraint": {"ap-southeast-1"}}, false},
{case2Function, map[string][]string{"LocationConstraint": {"us-east-1"}}, true},
{case2Function, map[string][]string{}, true},
{case2Function, map[string][]string{"delimiter": {"/"}}, true},
{case3Function, map[string][]string{"groups": {"prod", "art"}}, false},
{case3Function, map[string][]string{"groups": {"art"}}, false},
{case3Function, map[string][]string{}, false},
{case3Function, map[string][]string{"delimiter": {"/"}}, false},
{case4Function, map[string][]string{"groups": {"prod", "art"}}, false},
{case4Function, map[string][]string{"groups": {"art"}}, false},
{case4Function, map[string][]string{}, true},
{case4Function, map[string][]string{"delimiter": {"/"}}, true},
}
for i, testCase := range testCases {
result := testCase.function.evaluate(testCase.values)
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestBinaryEqualsFuncEvaluate(t *testing.T) {
case1Function, err := newBinaryEqualsFunc(
S3XAmzCopySource.ToKey(),
NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("mybucket/myobject")))),
"",
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Function, err := newBinaryEqualsFunc(
S3LocationConstraint.ToKey(),
NewValueSet(
NewStringValue(base64.StdEncoding.EncodeToString([]byte("eu-west-1"))),
NewStringValue(base64.StdEncoding.EncodeToString([]byte("ap-southeast-1"))),
),
"",
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case3Function, err := newBinaryEqualsFunc(
JWTGroups.ToKey(),
NewValueSet(
NewStringValue(base64.StdEncoding.EncodeToString([]byte("prod"))),
NewStringValue(base64.StdEncoding.EncodeToString([]byte("art"))),
),
forAllValues,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case4Function, err := newBinaryEqualsFunc(
JWTGroups.ToKey(),
NewValueSet(NewStringValue(
base64.StdEncoding.EncodeToString([]byte("prod"))),
NewStringValue(base64.StdEncoding.EncodeToString([]byte("art"))),
),
forAnyValue,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
function Function
values map[string][]string
expectedResult bool
}{
{case1Function, map[string][]string{"x-amz-copy-source": {"mybucket/myobject"}}, true},
{case1Function, map[string][]string{"x-amz-copy-source": {"yourbucket/myobject"}}, false},
{case1Function, map[string][]string{}, false},
{case1Function, map[string][]string{"delimiter": {"/"}}, false},
{case2Function, map[string][]string{"LocationConstraint": {"eu-west-1"}}, true},
{case2Function, map[string][]string{"LocationConstraint": {"ap-southeast-1"}}, true},
{case2Function, map[string][]string{"LocationConstraint": {"us-east-1"}}, false},
{case2Function, map[string][]string{}, false},
{case2Function, map[string][]string{"delimiter": {"/"}}, false},
{case3Function, map[string][]string{"groups": {"prod", "art"}}, true},
{case3Function, map[string][]string{"groups": {"art"}}, true},
{case3Function, map[string][]string{}, true},
{case3Function, map[string][]string{"delimiter": {"/"}}, true},
{case4Function, map[string][]string{"groups": {"prod", "art"}}, true},
{case4Function, map[string][]string{"groups": {"art"}}, true},
{case4Function, map[string][]string{}, false},
{case4Function, map[string][]string{"delimiter": {"/"}}, false},
}
for i, testCase := range testCases {
result := testCase.function.evaluate(testCase.values)
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestStringLikeFuncEvaluate(t *testing.T) {
case1Function, err := newStringLikeFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Function, err := newStringLikeFunc(S3LocationConstraint.ToKey(), NewValueSet(NewStringValue("eu-west-*"), NewStringValue("ap-southeast-1")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case3Function, err := newStringLikeFunc(JWTGroups.ToKey(), NewValueSet(NewStringValue("prod"), NewStringValue("art*")), forAllValues)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case4Function, err := newStringLikeFunc(JWTGroups.ToKey(), NewValueSet(NewStringValue("prod*"), NewStringValue("art")), forAnyValue)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
function Function
values map[string][]string
expectedResult bool
}{
{case1Function, map[string][]string{"x-amz-copy-source": {"mybucket/myobject"}}, true},
{case1Function, map[string][]string{"x-amz-copy-source": {"yourbucket/myobject"}}, false},
{case1Function, map[string][]string{}, false},
{case1Function, map[string][]string{"delimiter": {"/"}}, false},
{case2Function, map[string][]string{"LocationConstraint": {"eu-west-1"}}, true},
{case2Function, map[string][]string{"LocationConstraint": {"eu-west-2"}}, true},
{case2Function, map[string][]string{"LocationConstraint": {"ap-southeast-1"}}, true},
{case2Function, map[string][]string{"LocationConstraint": {"us-east-1"}}, false},
{case2Function, map[string][]string{}, false},
{case2Function, map[string][]string{"delimiter": {"/"}}, false},
{case3Function, map[string][]string{"groups": {"prod", "arts"}}, true},
{case3Function, map[string][]string{"groups": {"art"}}, true},
{case3Function, map[string][]string{}, true},
{case3Function, map[string][]string{"delimiter": {"/"}}, true},
{case4Function, map[string][]string{"groups": {"prods", "art"}}, true},
{case4Function, map[string][]string{"groups": {"art"}}, true},
{case4Function, map[string][]string{}, false},
{case4Function, map[string][]string{"delimiter": {"/"}}, false},
}
for i, testCase := range testCases {
result := testCase.function.evaluate(testCase.values)
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestStringNotLikeFuncEvaluate(t *testing.T) {
case1Function, err := newStringNotLikeFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Function, err := newStringNotLikeFunc(S3LocationConstraint.ToKey(), NewValueSet(NewStringValue("eu-west-*"), NewStringValue("ap-southeast-1")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case3Function, err := newStringNotLikeFunc(JWTGroups.ToKey(), NewValueSet(NewStringValue("prod"), NewStringValue("art*")), forAllValues)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case4Function, err := newStringNotLikeFunc(JWTGroups.ToKey(), NewValueSet(NewStringValue("prod*"), NewStringValue("art")), forAnyValue)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
function Function
values map[string][]string
expectedResult bool
}{
{case1Function, map[string][]string{"x-amz-copy-source": {"mybucket/myobject"}}, false},
{case1Function, map[string][]string{"x-amz-copy-source": {"yourbucket/myobject"}}, true},
{case1Function, map[string][]string{}, true},
{case1Function, map[string][]string{"delimiter": {"/"}}, true},
{case2Function, map[string][]string{"LocationConstraint": {"eu-west-1"}}, false},
{case2Function, map[string][]string{"LocationConstraint": {"eu-west-2"}}, false},
{case2Function, map[string][]string{"LocationConstraint": {"ap-southeast-1"}}, false},
{case2Function, map[string][]string{"LocationConstraint": {"us-east-1"}}, true},
{case2Function, map[string][]string{}, true},
{case2Function, map[string][]string{"delimiter": {"/"}}, true},
{case3Function, map[string][]string{"groups": {"prod", "arts"}}, false},
{case3Function, map[string][]string{"groups": {"art"}}, false},
{case3Function, map[string][]string{}, false},
{case3Function, map[string][]string{"delimiter": {"/"}}, false},
{case4Function, map[string][]string{"groups": {"prods", "art"}}, false},
{case4Function, map[string][]string{"groups": {"art"}}, false},
{case4Function, map[string][]string{}, true},
{case4Function, map[string][]string{"delimiter": {"/"}}, true},
}
for i, testCase := range testCases {
result := testCase.function.evaluate(testCase.values)
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestStringFuncKey(t *testing.T) {
case1Function, err := newStringEqualsFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
function Function
expectedResult Key
}{
{case1Function, S3XAmzCopySource.ToKey()},
}
for i, testCase := range testCases {
result := testCase.function.key()
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestStringFuncName(t *testing.T) {
case1Function, err := newStringEqualsFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Function, err := newStringNotEqualsFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case3Function, err := newStringEqualsIgnoreCaseFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/MYOBJECT")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case4Function, err := newStringNotEqualsIgnoreCaseFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/MYOBJECT")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case5Function, err := newBinaryEqualsFunc(
S3XAmzCopySource.ToKey(),
NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("mybucket/myobject")))),
"",
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case6Function, err := newStringLikeFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case7Function, err := newStringNotLikeFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case8Function, err := newStringLikeFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), forAllValues)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case9Function, err := newStringNotLikeFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), forAnyValue)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
function Function
expectedResult name
}{
{case1Function, name{name: stringEquals}},
{case2Function, name{name: stringNotEquals}},
{case3Function, name{name: stringEqualsIgnoreCase}},
{case4Function, name{name: stringNotEqualsIgnoreCase}},
{case5Function, name{name: binaryEquals}},
{case6Function, name{name: stringLike}},
{case7Function, name{name: stringNotLike}},
{case8Function, name{qualifier: forAllValues, name: stringLike}},
{case9Function, name{qualifier: forAnyValue, name: stringNotLike}},
}
for i, testCase := range testCases {
result := testCase.function.name()
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestStringEqualsFuncToMap(t *testing.T) {
case1Function, err := newStringEqualsFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case1Result := map[Key]ValueSet{
S3XAmzCopySource.ToKey(): NewValueSet(NewStringValue("mybucket/myobject")),
}
case2Function, err := newStringEqualsFunc(S3XAmzCopySource.ToKey(),
NewValueSet(
NewStringValue("mybucket/myobject"),
NewStringValue("yourbucket/myobject"),
),
"",
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Result := map[Key]ValueSet{
S3XAmzCopySource.ToKey(): NewValueSet(
NewStringValue("mybucket/myobject"),
NewStringValue("yourbucket/myobject"),
),
}
case3Function, err := newStringEqualsIgnoreCaseFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/MYOBJECT")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case3Result := map[Key]ValueSet{
S3XAmzCopySource.ToKey(): NewValueSet(NewStringValue("mybucket/MYOBJECT")),
}
case4Function, err := newBinaryEqualsFunc(
S3XAmzCopySource.ToKey(),
NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("mybucket/myobject")))),
"",
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case4Result := map[Key]ValueSet{
S3XAmzCopySource.ToKey(): NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("mybucket/myobject")))),
}
testCases := []struct {
f Function
expectedResult map[Key]ValueSet
}{
{case1Function, case1Result},
{case2Function, case2Result},
{case3Function, case3Result},
{case4Function, case4Result},
{&stringFunc{}, nil},
}
for i, testCase := range testCases {
result := testCase.f.toMap()
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestStringFuncClone(t *testing.T) {
case1Function, err := newStringEqualsFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case1Result := &stringFunc{
n: name{name: stringEquals},
k: S3XAmzCopySource.ToKey(),
values: set.CreateStringSet("mybucket/myobject"),
ignoreCase: false,
base64: false,
negate: false,
}
case2Function, err := newStringNotEqualsFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Result := &stringFunc{
n: name{name: stringNotEquals},
k: S3XAmzCopySource.ToKey(),
values: set.CreateStringSet("mybucket/myobject"),
ignoreCase: false,
base64: false,
negate: true,
}
case3Function, err := newStringEqualsIgnoreCaseFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/MYOBJECT")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case3Result := &stringFunc{
n: name{name: stringEqualsIgnoreCase},
k: S3XAmzCopySource.ToKey(),
values: set.CreateStringSet("mybucket/MYOBJECT"),
ignoreCase: true,
base64: false,
negate: false,
}
case4Function, err := newStringNotEqualsIgnoreCaseFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/MYOBJECT")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case4Result := &stringFunc{
n: name{name: stringNotEqualsIgnoreCase},
k: S3XAmzCopySource.ToKey(),
values: set.CreateStringSet("mybucket/MYOBJECT"),
ignoreCase: true,
base64: false,
negate: true,
}
case5Function, err := newBinaryEqualsFunc(
S3XAmzCopySource.ToKey(),
NewValueSet(NewStringValue(base64.StdEncoding.EncodeToString([]byte("mybucket/myobject")))),
"",
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case5Result := &stringFunc{
n: name{name: binaryEquals},
k: S3XAmzCopySource.ToKey(),
values: set.CreateStringSet("mybucket/myobject"),
ignoreCase: false,
base64: true,
negate: false,
}
case6Function, err := newStringLikeFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case6Result := &stringLikeFunc{stringFunc{
n: name{name: stringLike},
k: S3XAmzCopySource.ToKey(),
values: set.CreateStringSet("mybucket/myobject"),
ignoreCase: false,
base64: false,
negate: false,
}}
case7Function, err := newStringNotLikeFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), "")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case7Result := &stringLikeFunc{stringFunc{
n: name{name: stringNotLike},
k: S3XAmzCopySource.ToKey(),
values: set.CreateStringSet("mybucket/myobject"),
ignoreCase: false,
base64: false,
negate: true,
}}
case8Function, err := newStringLikeFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), forAllValues)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case8Result := &stringLikeFunc{stringFunc{
n: name{qualifier: forAllValues, name: stringLike},
k: S3XAmzCopySource.ToKey(),
values: set.CreateStringSet("mybucket/myobject"),
ignoreCase: false,
base64: false,
negate: false,
}}
case9Function, err := newStringNotLikeFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), forAnyValue)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case9Result := &stringLikeFunc{stringFunc{
n: name{qualifier: forAnyValue, name: stringNotLike},
k: S3XAmzCopySource.ToKey(),
values: set.CreateStringSet("mybucket/myobject"),
ignoreCase: false,
base64: false,
negate: true,
}}
testCases := []struct {
function Function
expectedResult Function
}{
{case1Function, case1Result},
{case2Function, case2Result},
{case3Function, case3Result},
{case4Function, case4Result},
{case5Function, case5Result},
{case6Function, case6Result},
{case7Function, case7Result},
{case8Function, case8Result},
{case9Function, case9Result},
}
for i, testCase := range testCases {
result := testCase.function.clone()
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestNewStringFuncError(t *testing.T) {
testCases := []struct {
key Key
values ValueSet
qualifier string
}{
{S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject"), NewIntValue(7)), ""},
{S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket")), ""},
{S3XAmzServerSideEncryption.ToKey(), NewValueSet(NewStringValue("SSE-C")), ""},
{S3XAmzServerSideEncryptionCustomerAlgorithm.ToKey(), NewValueSet(NewStringValue("SSE-C")), ""},
{S3XAmzMetadataDirective.ToKey(), NewValueSet(NewStringValue("DUPLICATE")), ""},
{S3XAmzContentSha256.ToKey(), NewValueSet(NewStringValue("")), ""},
{S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), "For_All_Values"},
}
for i, testCase := range testCases {
if _, err := newStringEqualsFunc(testCase.key, testCase.values, testCase.qualifier); err == nil {
t.Errorf("case %v: error expected", i+1)
}
}
if _, err := newBinaryEqualsFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket/myobject")), ""); err == nil {
t.Errorf("error expected")
}
if _, err := newStringLikeFunc(S3XAmzCopySource.ToKey(), NewValueSet(NewStringValue("mybucket")), ""); err == nil {
t.Errorf("error expected")
}
}
golang-github-minio-pkg-3.0.10/policy/condition/value.go 0000664 0000000 0000000 00000007652 14655770405 0023171 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package condition
import (
"encoding/json"
"fmt"
"net/http"
"reflect"
"strconv"
"strings"
)
func getValuesByKey(m map[string][]string, key Key) []string {
name := key.Name()
if values, found := m[http.CanonicalHeaderKey(name)]; found {
return values
}
return m[name]
}
// Splits an incoming path into bucket and object components.
func path2BucketAndObject(path string) (bucket, object string) {
// Skip the first element if it is '/', split the rest.
path = strings.TrimPrefix(path, "/")
pathComponents := strings.SplitN(path, "/", 2)
// Save the bucket and object extracted from path.
switch len(pathComponents) {
case 1:
bucket = pathComponents[0]
case 2:
bucket = pathComponents[0]
object = pathComponents[1]
}
return bucket, object
}
// Value - is enum type of string, int or bool.
type Value struct {
t reflect.Kind
s string
i int
b bool
}
// GetBool - gets stored bool value.
func (v Value) GetBool() (bool, error) {
var err error
if v.t != reflect.Bool {
err = fmt.Errorf("not a bool Value")
}
return v.b, err
}
// GetInt - gets stored int value.
func (v Value) GetInt() (int, error) {
var err error
if v.t != reflect.Int {
err = fmt.Errorf("not a int Value")
}
return v.i, err
}
// GetString - gets stored string value.
func (v Value) GetString() (string, error) {
var err error
if v.t != reflect.String {
err = fmt.Errorf("not a string Value")
}
return v.s, err
}
// GetType - gets enum type.
func (v Value) GetType() reflect.Kind {
return v.t
}
// MarshalJSON - encodes Value to JSON data.
func (v Value) MarshalJSON() ([]byte, error) {
switch v.t {
case reflect.String:
return json.Marshal(v.s)
case reflect.Int:
return json.Marshal(v.i)
case reflect.Bool:
return json.Marshal(v.b)
}
return nil, fmt.Errorf("unknown value kind %v", v.t)
}
// StoreBool - stores bool value.
func (v *Value) StoreBool(b bool) {
*v = Value{t: reflect.Bool, b: b}
}
// StoreInt - stores int value.
func (v *Value) StoreInt(i int) {
*v = Value{t: reflect.Int, i: i}
}
// StoreString - stores string value.
func (v *Value) StoreString(s string) {
*v = Value{t: reflect.String, s: s}
}
// String - returns string representation of value.
func (v Value) String() string {
switch v.t {
case reflect.String:
return v.s
case reflect.Int:
return strconv.Itoa(v.i)
case reflect.Bool:
return strconv.FormatBool(v.b)
}
return ""
}
// UnmarshalJSON - decodes JSON data.
func (v *Value) UnmarshalJSON(data []byte) error {
var b bool
if err := json.Unmarshal(data, &b); err == nil {
v.StoreBool(b)
return nil
}
var i int
if err := json.Unmarshal(data, &i); err == nil {
v.StoreInt(i)
return nil
}
var s string
if err := json.Unmarshal(data, &s); err == nil {
v.StoreString(s)
return nil
}
return fmt.Errorf("unknown json data '%v'", data)
}
// NewBoolValue - returns new bool value.
func NewBoolValue(b bool) Value {
value := &Value{}
value.StoreBool(b)
return *value
}
// NewIntValue - returns new int value.
func NewIntValue(i int) Value {
value := &Value{}
value.StoreInt(i)
return *value
}
// NewStringValue - returns new string value.
func NewStringValue(s string) Value {
value := &Value{}
value.StoreString(s)
return *value
}
golang-github-minio-pkg-3.0.10/policy/condition/value_test.go 0000664 0000000 0000000 00000014702 14655770405 0024222 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package condition
import (
"encoding/json"
"reflect"
"testing"
)
func TestValueGetBool(t *testing.T) {
testCases := []struct {
value Value
expectedResult bool
expectErr bool
}{
{NewBoolValue(true), true, false},
{NewIntValue(7), false, true},
{Value{}, false, true},
}
for i, testCase := range testCases {
result, err := testCase.value.GetBool()
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if result != testCase.expectedResult {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
}
func TestValueGetInt(t *testing.T) {
testCases := []struct {
value Value
expectedResult int
expectErr bool
}{
{NewIntValue(7), 7, false},
{NewBoolValue(true), 0, true},
{Value{}, 0, true},
}
for i, testCase := range testCases {
result, err := testCase.value.GetInt()
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if result != testCase.expectedResult {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
}
func TestValueGetString(t *testing.T) {
testCases := []struct {
value Value
expectedResult string
expectErr bool
}{
{NewStringValue("foo"), "foo", false},
{NewBoolValue(true), "", true},
{Value{}, "", true},
}
for i, testCase := range testCases {
result, err := testCase.value.GetString()
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if result != testCase.expectedResult {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
}
func TestValueGetType(t *testing.T) {
testCases := []struct {
value Value
expectedResult reflect.Kind
}{
{NewBoolValue(true), reflect.Bool},
{NewIntValue(7), reflect.Int},
{NewStringValue("foo"), reflect.String},
{Value{}, reflect.Invalid},
}
for i, testCase := range testCases {
result := testCase.value.GetType()
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestValueMarshalJSON(t *testing.T) {
testCases := []struct {
value Value
expectedResult []byte
expectErr bool
}{
{NewBoolValue(true), []byte("true"), false},
{NewIntValue(7), []byte("7"), false},
{NewStringValue("foo"), []byte(`"foo"`), false},
{Value{}, nil, true},
}
for i, testCase := range testCases {
result, err := json.Marshal(testCase.value)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
}
func TestValueStoreBool(t *testing.T) {
testCases := []struct {
value bool
expectedResult Value
}{
{false, NewBoolValue(false)},
{true, NewBoolValue(true)},
}
for i, testCase := range testCases {
var result Value
result.StoreBool(testCase.value)
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestValueStoreInt(t *testing.T) {
testCases := []struct {
value int
expectedResult Value
}{
{0, NewIntValue(0)},
{7, NewIntValue(7)},
}
for i, testCase := range testCases {
var result Value
result.StoreInt(testCase.value)
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestValueStoreString(t *testing.T) {
testCases := []struct {
value string
expectedResult Value
}{
{"", NewStringValue("")},
{"foo", NewStringValue("foo")},
}
for i, testCase := range testCases {
var result Value
result.StoreString(testCase.value)
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestValueString(t *testing.T) {
testCases := []struct {
value Value
expectedResult string
}{
{NewBoolValue(true), "true"},
{NewIntValue(7), "7"},
{NewStringValue("foo"), "foo"},
{Value{}, ""},
}
for i, testCase := range testCases {
result := testCase.value.String()
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestValueUnmarshalJSON(t *testing.T) {
testCases := []struct {
data []byte
expectedResult Value
expectErr bool
}{
{[]byte("true"), NewBoolValue(true), false},
{[]byte("7"), NewIntValue(7), false},
{[]byte(`"foo"`), NewStringValue("foo"), false},
{[]byte("True"), Value{}, true},
{[]byte("7.1"), Value{}, true},
{[]byte(`["foo"]`), Value{}, true},
}
for i, testCase := range testCases {
var result Value
err := json.Unmarshal(testCase.data, &result)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
}
golang-github-minio-pkg-3.0.10/policy/condition/valueset.go 0000664 0000000 0000000 00000004423 14655770405 0023676 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package condition
import (
"encoding/json"
"fmt"
)
// ValueSet - unique list of values.
type ValueSet map[Value]struct{}
// Add - adds given value to value set.
func (set ValueSet) Add(value Value) {
set[value] = struct{}{}
}
// ToSlice converts ValueSet to a slice of Value
func (set ValueSet) ToSlice() []Value {
var values []Value
for k := range set {
values = append(values, k)
}
return values
}
// MarshalJSON - encodes ValueSet to JSON data.
func (set ValueSet) MarshalJSON() ([]byte, error) {
var values []Value
for k := range set {
values = append(values, k)
}
if len(values) == 0 {
return nil, fmt.Errorf("invalid value set %v", set)
}
return json.Marshal(values)
}
// UnmarshalJSON - decodes JSON data.
func (set *ValueSet) UnmarshalJSON(data []byte) error {
var v Value
if err := json.Unmarshal(data, &v); err == nil {
*set = make(ValueSet)
set.Add(v)
return nil
}
var values []Value
if err := json.Unmarshal(data, &values); err != nil {
return err
}
if len(values) < 1 {
return fmt.Errorf("invalid value")
}
*set = make(ValueSet)
for _, v = range values {
if _, found := (*set)[v]; found {
return fmt.Errorf("duplicate value found '%v'", v)
}
set.Add(v)
}
return nil
}
// Clone clones ValueSet structure
func (set ValueSet) Clone() ValueSet {
return NewValueSet(set.ToSlice()...)
}
// NewValueSet - returns new value set containing given values.
func NewValueSet(values ...Value) ValueSet {
set := make(ValueSet)
for _, value := range values {
set.Add(value)
}
return set
}
golang-github-minio-pkg-3.0.10/policy/condition/valueset_test.go 0000664 0000000 0000000 00000007225 14655770405 0024740 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package condition
import (
"encoding/json"
"reflect"
"testing"
)
func TestValueSetAdd(t *testing.T) {
testCases := []struct {
value Value
expectedResult ValueSet
}{
{NewBoolValue(true), NewValueSet(NewBoolValue(true))},
{NewIntValue(7), NewValueSet(NewIntValue(7))},
{NewStringValue("foo"), NewValueSet(NewStringValue("foo"))},
}
for i, testCase := range testCases {
result := NewValueSet()
result.Add(testCase.value)
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestValueSetMarshalJSON(t *testing.T) {
testCases := []struct {
set ValueSet
expectedResult string
expectErr bool
}{
{NewValueSet(NewBoolValue(true)), `[true]`, false},
{NewValueSet(NewIntValue(7)), `[7]`, false},
{NewValueSet(NewStringValue("foo")), `["foo"]`, false},
{NewValueSet(NewBoolValue(true)), `[true]`, false},
{NewValueSet(NewStringValue("7")), `["7"]`, false},
{NewValueSet(NewStringValue("foo")), `["foo"]`, false},
{make(ValueSet), "", true},
}
for i, testCase := range testCases {
result, err := json.Marshal(testCase.set)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if string(result) != testCase.expectedResult {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, string(result))
}
}
}
}
func TestValueSetUnmarshalJSON(t *testing.T) {
set1 := NewValueSet(
NewBoolValue(true),
NewStringValue("false"),
NewIntValue(7),
NewStringValue("7"),
NewStringValue("foo"),
NewStringValue("192.168.1.100/24"),
)
testCases := []struct {
data []byte
expectedResult ValueSet
expectErr bool
}{
{[]byte(`true`), NewValueSet(NewBoolValue(true)), false},
{[]byte(`7`), NewValueSet(NewIntValue(7)), false},
{[]byte(`"foo"`), NewValueSet(NewStringValue("foo")), false},
{[]byte(`[true]`), NewValueSet(NewBoolValue(true)), false},
{[]byte(`[7]`), NewValueSet(NewIntValue(7)), false},
{[]byte(`["foo"]`), NewValueSet(NewStringValue("foo")), false},
{[]byte(`[true, "false", 7, "7", "foo", "192.168.1.100/24"]`), set1, false},
{[]byte(`{}`), nil, true}, // Unsupported data.
{[]byte(`[]`), nil, true}, // Empty array.
{[]byte(`[7, 7, true]`), nil, true}, // Duplicate value.
}
for i, testCase := range testCases {
result := make(ValueSet)
err := json.Unmarshal(testCase.data, &result)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
}
golang-github-minio-pkg-3.0.10/policy/constants.go 0000664 0000000 0000000 00000006371 14655770405 0022100 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
import (
"github.com/minio/pkg/v3/policy/condition"
)
// Policy claim constants
const (
PolicyName = "policy"
SessionPolicyName = "sessionPolicy"
)
// DefaultPolicies - list of canned policies available in MinIO.
var DefaultPolicies = []struct {
Name string
Definition Policy
}{
// ReadWrite - provides full access to all buckets and all objects.
{
Name: "readwrite",
Definition: Policy{
Version: DefaultVersion,
Statements: []Statement{
{
SID: ID(""),
Effect: Allow,
Actions: NewActionSet(AllActions),
Resources: NewResourceSet(NewResource("*")),
},
},
},
},
// ReadOnly - read only.
{
Name: "readonly",
Definition: Policy{
Version: DefaultVersion,
Statements: []Statement{
{
SID: ID(""),
Effect: Allow,
Actions: NewActionSet(GetBucketLocationAction, GetObjectAction),
Resources: NewResourceSet(NewResource("*")),
},
},
},
},
// WriteOnly - provides write access.
{
Name: "writeonly",
Definition: Policy{
Version: DefaultVersion,
Statements: []Statement{
{
SID: ID(""),
Effect: Allow,
Actions: NewActionSet(PutObjectAction),
Resources: NewResourceSet(NewResource("*")),
},
},
},
},
// AdminDiagnostics - provides admin diagnostics access.
{
Name: "diagnostics",
Definition: Policy{
Version: DefaultVersion,
Statements: []Statement{
{
SID: ID(""),
Effect: Allow,
Actions: NewActionSet(ProfilingAdminAction,
TraceAdminAction, ConsoleLogAdminAction,
ServerInfoAdminAction, TopLocksAdminAction,
HealthInfoAdminAction, BandwidthMonitorAction,
PrometheusAdminAction,
),
Resources: NewResourceSet(NewResource("*")),
},
},
},
},
// Admin - provides admin all-access canned policy
{
Name: "consoleAdmin",
Definition: Policy{
Version: DefaultVersion,
Statements: []Statement{
{
SID: ID(""),
Effect: Allow,
Actions: NewActionSet(AllAdminActions),
Resources: NewResourceSet(),
Conditions: condition.NewFunctions(),
},
{
SID: ID(""),
Effect: Allow,
Actions: NewActionSet(AllKMSActions),
Resources: NewResourceSet(),
Conditions: condition.NewFunctions(),
},
{
SID: ID(""),
Effect: Allow,
Actions: NewActionSet(AllActions),
Resources: NewResourceSet(NewResource("*")),
Conditions: condition.NewFunctions(),
},
},
},
},
}
golang-github-minio-pkg-3.0.10/policy/effect.go 0000664 0000000 0000000 00000002353 14655770405 0021314 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
// Effect - policy statement effect Allow or Deny.
type Effect string
const (
// Allow - allow effect.
Allow Effect = "Allow"
// Deny - deny effect.
Deny = "Deny"
)
// IsAllowed - returns if given check is allowed or not.
func (effect Effect) IsAllowed(b bool) bool {
if effect == Allow {
return b
}
return !b
}
// IsValid - checks if Effect is valid or not
func (effect Effect) IsValid() bool {
switch effect {
case Allow, Deny:
return true
}
return false
}
golang-github-minio-pkg-3.0.10/policy/effect_test.go 0000664 0000000 0000000 00000003221 14655770405 0022346 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
import (
"testing"
)
func TestEffectIsAllowed(t *testing.T) {
testCases := []struct {
effect Effect
check bool
expectedResult bool
}{
{Allow, false, false},
{Allow, true, true},
{Deny, false, true},
{Deny, true, false},
}
for i, testCase := range testCases {
result := testCase.effect.IsAllowed(testCase.check)
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestEffectIsValid(t *testing.T) {
testCases := []struct {
effect Effect
expectedResult bool
}{
{Allow, true},
{Deny, true},
{Effect(""), false},
{Effect("foo"), false},
}
for i, testCase := range testCases {
result := testCase.effect.IsValid()
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
golang-github-minio-pkg-3.0.10/policy/error.go 0000664 0000000 0000000 00000002526 14655770405 0021213 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
import (
"fmt"
)
// Error is the generic type for any error happening during policy
// parsing.
type Error struct {
err error
}
// Errorf - formats according to a format specifier and returns
// the string as a value that satisfies error of type policy.Error
func Errorf(format string, a ...interface{}) error {
return Error{err: fmt.Errorf(format, a...)}
}
// Unwrap the internal error.
func (e Error) Unwrap() error { return e.err }
// Error 'error' compatible method.
func (e Error) Error() string {
if e.err == nil {
return "policy: cause "
}
return e.err.Error()
}
golang-github-minio-pkg-3.0.10/policy/id.go 0000664 0000000 0000000 00000001672 14655770405 0020457 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
import (
"unicode/utf8"
)
// ID - policy ID.
type ID string
// IsValid - checks if ID is valid or not.
func (id ID) IsValid() bool {
return utf8.ValidString(string(id))
}
golang-github-minio-pkg-3.0.10/policy/id_test.go 0000664 0000000 0000000 00000002267 14655770405 0021517 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
import (
"testing"
)
func TestIDIsValid(t *testing.T) {
testCases := []struct {
id ID
expectedResult bool
}{
{ID("DenyEncryptionSt1"), true},
{ID(""), true},
{ID("aa\xe2"), false},
}
for i, testCase := range testCases {
result := testCase.id.IsValid()
if result != testCase.expectedResult {
t.Errorf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
golang-github-minio-pkg-3.0.10/policy/kms-action.go 0000664 0000000 0000000 00000007703 14655770405 0022131 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2022 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
// KMSAction - KMS policy action.
type KMSAction string
const (
// KMSCreateKeyAction - allow creating a new KMS master key
KMSCreateKeyAction = "kms:CreateKey"
// KMSDeleteKeyAction - allow deleting a KMS master key
KMSDeleteKeyAction = "kms:DeleteKey"
// KMSListKeysAction - allow getting list of KMS keys
KMSListKeysAction = "kms:ListKeys"
// KMSImportKeyAction - allow importing KMS key
KMSImportKeyAction = "kms:ImportKey"
// KMSDescribePolicyAction - allow getting KMS policy
KMSDescribePolicyAction = "kms:DescribePolicy"
// KMSAssignPolicyAction - allow assigning an identity to a KMS policy
KMSAssignPolicyAction = "kms:AssignPolicy"
// KMSDeletePolicyAction - allow deleting a policy
KMSDeletePolicyAction = "kms:DeletePolicy"
// KMSSetPolicyAction - allow creating or updating a policy
KMSSetPolicyAction = "kms:SetPolicy"
// KMSGetPolicyAction - allow getting a policy
KMSGetPolicyAction = "kms:GetPolicy"
// KMSListPoliciesAction - allow getting list of KMS policies
KMSListPoliciesAction = "kms:ListPolicies"
// KMSDescribeIdentityAction - allow getting KMS identity
KMSDescribeIdentityAction = "kms:DescribeIdentity"
// KMSDescribeSelfIdentityAction - allow getting self KMS identity
KMSDescribeSelfIdentityAction = "kms:DescribeSelfIdentity"
// KMSDeleteIdentityAction - allow deleting a policy
KMSDeleteIdentityAction = "kms:DeleteIdentity"
// KMSListIdentitiesAction - allow getting list of KMS identities
KMSListIdentitiesAction = "kms:ListIdentities"
// KMSKeyStatusAction - allow getting KMS key status
KMSKeyStatusAction = "kms:KeyStatus"
// KMSStatusAction - allow getting KMS status
KMSStatusAction = "kms:Status"
// KMSAPIAction - allow getting a list of supported API endpoints
KMSAPIAction = "kms:API"
// KMSMetricsAction - allow getting server metrics in the Prometheus exposition format
KMSMetricsAction = "kms:Metrics"
// KMSVersionAction - allow getting version information
KMSVersionAction = "kms:Version"
// KMSAuditLogAction - subscribes to the audit log
KMSAuditLogAction = "kms:AuditLog"
// KMSErrorLogAction - subscribes to the error log
KMSErrorLogAction = "kms:ErrorLog"
// AllKMSActions - provides all admin permissions
AllKMSActions = "kms:*"
)
// List of all supported admin actions.
var supportedKMSActions = map[KMSAction]struct{}{
KMSCreateKeyAction: {},
KMSDeleteKeyAction: {},
KMSListKeysAction: {},
KMSImportKeyAction: {},
KMSDescribePolicyAction: {},
KMSAssignPolicyAction: {},
KMSDeletePolicyAction: {},
KMSSetPolicyAction: {},
KMSGetPolicyAction: {},
KMSListPoliciesAction: {},
KMSDescribeIdentityAction: {},
KMSDescribeSelfIdentityAction: {},
KMSDeleteIdentityAction: {},
KMSListIdentitiesAction: {},
KMSKeyStatusAction: {},
KMSStatusAction: {},
KMSAPIAction: {},
KMSMetricsAction: {},
KMSVersionAction: {},
KMSAuditLogAction: {},
KMSErrorLogAction: {},
AllKMSActions: {},
}
// IsValid - checks if action is valid or not.
func (action KMSAction) IsValid() bool {
_, ok := supportedKMSActions[action]
return ok
}
golang-github-minio-pkg-3.0.10/policy/policy.go 0000664 0000000 0000000 00000021000 14655770405 0021345 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
import (
"encoding/json"
"io"
"strings"
"github.com/minio/minio-go/v7/pkg/set"
)
// DefaultVersion - default policy version as per AWS S3 specification.
const DefaultVersion = "2012-10-17"
// Args - arguments to policy to check whether it is allowed
type Args struct {
AccountName string `json:"account"`
Groups []string `json:"groups"`
Action Action `json:"action"`
BucketName string `json:"bucket"`
ConditionValues map[string][]string `json:"conditions"`
IsOwner bool `json:"owner"`
ObjectName string `json:"object"`
Claims map[string]interface{} `json:"claims"`
DenyOnly bool `json:"denyOnly"` // only applies deny
}
// GetValuesFromClaims returns the list of values for the input claimName.
// Supports values in following formats
// - string
// - comma separated values
// - string array
func GetValuesFromClaims(claims map[string]interface{}, claimName string) (set.StringSet, bool) {
s := set.NewStringSet()
pname, ok := claims[claimName]
if !ok {
return s, false
}
pnames, ok := pname.([]interface{})
if !ok {
pnameStr, ok := pname.(string)
if ok {
for _, pname := range strings.Split(pnameStr, ",") {
pname = strings.TrimSpace(pname)
if pname == "" {
// ignore any empty strings, considerate
// towards some user errors.
continue
}
s.Add(pname)
}
return s, true
}
return s, false
}
for _, pname := range pnames {
pnameStr, ok := pname.(string)
if ok {
for _, pnameStr := range strings.Split(pnameStr, ",") {
pnameStr = strings.TrimSpace(pnameStr)
if pnameStr == "" {
// ignore any empty strings, considerate
// towards some user errors.
continue
}
s.Add(pnameStr)
}
}
}
return s, true
}
// GetPoliciesFromClaims returns the list of policies to be applied for this
// incoming request, extracting the information from input JWT claims.
func GetPoliciesFromClaims(claims map[string]interface{}, policyClaimName string) (set.StringSet, bool) {
return GetValuesFromClaims(claims, policyClaimName)
}
// GetPolicies returns the list of policies to be applied for this
// incoming request, extracting the information from JWT claims.
func (a Args) GetPolicies(policyClaimName string) (set.StringSet, bool) {
return GetPoliciesFromClaims(a.Claims, policyClaimName)
}
// GetRoleArn returns the role ARN from JWT claims if present. Otherwise returns
// empty string.
func (a Args) GetRoleArn() string {
s, ok := a.Claims["roleArn"]
roleArn, ok2 := s.(string)
if ok && ok2 {
return roleArn
}
return ""
}
// Policy - iam bucket iamp.
type Policy struct {
ID ID `json:"ID,omitempty"`
Version string
Statements []Statement `json:"Statement"`
}
// MatchResource matches resource with match resource patterns
func (iamp Policy) MatchResource(resource string) bool {
for _, statement := range iamp.Statements {
if statement.Resources.MatchResource(resource) {
return true
}
}
return false
}
// IsAllowedActions returns all supported actions for this policy.
func (iamp Policy) IsAllowedActions(bucketName, objectName string, conditionValues map[string][]string) ActionSet {
actionSet := make(ActionSet)
for action := range supportedActions {
if iamp.IsAllowed(Args{
BucketName: bucketName,
ObjectName: objectName,
Action: action,
ConditionValues: conditionValues,
}) {
actionSet.Add(action)
}
}
for action := range supportedAdminActions {
admAction := Action(action)
if iamp.IsAllowed(Args{
BucketName: bucketName,
ObjectName: objectName,
Action: admAction,
ConditionValues: conditionValues,
// checks mainly for actions that can have explicit
// deny, while without it are implicitly enabled.
DenyOnly: action == CreateServiceAccountAdminAction || action == CreateUserAdminAction,
}) {
actionSet.Add(admAction)
}
}
for action := range supportedKMSActions {
kmsAction := Action(action)
if iamp.IsAllowed(Args{
BucketName: bucketName,
ObjectName: objectName,
Action: kmsAction,
ConditionValues: conditionValues,
}) {
actionSet.Add(kmsAction)
}
}
return actionSet
}
// IsAllowed - checks given policy args is allowed to continue the Rest API.
func (iamp Policy) IsAllowed(args Args) bool {
// Check all deny statements. If any one statement denies, return false.
for _, statement := range iamp.Statements {
if statement.Effect == Deny {
if !statement.IsAllowed(args) {
return false
}
}
}
// Applied any 'Deny' only policies, if we have
// reached here it means that there were no 'Deny'
// policies - this function mainly used for
// specific scenarios where we only want to validate
// 'Deny' only policies.
if args.DenyOnly {
return true
}
// For owner, its allowed by default.
if args.IsOwner {
return true
}
// Check all allow statements. If any one statement allows, return true.
for _, statement := range iamp.Statements {
if statement.Effect == Allow {
if statement.IsAllowed(args) {
return true
}
}
}
return false
}
// IsEmpty - returns whether policy is empty or not.
func (iamp Policy) IsEmpty() bool {
return len(iamp.Statements) == 0
}
// isValid - checks if Policy is valid or not.
func (iamp Policy) isValid() error {
if iamp.Version != DefaultVersion && iamp.Version != "" {
return Errorf("invalid version '%v'", iamp.Version)
}
for _, statement := range iamp.Statements {
if err := statement.isValid(); err != nil {
return err
}
}
return nil
}
// MergePolicies merges all the given policies into a single policy dropping any
// duplicate statements.
func MergePolicies(inputs ...Policy) Policy {
var merged Policy
for _, p := range inputs {
if merged.Version == "" {
merged.Version = p.Version
}
for _, st := range p.Statements {
merged.Statements = append(merged.Statements, st.Clone())
}
}
merged.dropDuplicateStatements()
return merged
}
func (iamp *Policy) dropDuplicateStatements() {
dups := make(map[int]struct{})
for i := range iamp.Statements {
if _, ok := dups[i]; ok {
// i is already a duplicate of some statement, so we do not need to
// compare with it.
continue
}
for j := i + 1; j < len(iamp.Statements); j++ {
if !iamp.Statements[i].Equals(iamp.Statements[j]) {
continue
}
// save duplicate statement index for removal.
dups[j] = struct{}{}
}
}
// remove duplicate items from the slice.
var c int
for i := range iamp.Statements {
if _, ok := dups[i]; ok {
continue
}
iamp.Statements[c] = iamp.Statements[i]
c++
}
iamp.Statements = iamp.Statements[:c]
}
// UnmarshalJSON - decodes JSON data to Iamp.
func (iamp *Policy) UnmarshalJSON(data []byte) error {
// subtype to avoid recursive call to UnmarshalJSON()
type subPolicy Policy
var sp subPolicy
if err := json.Unmarshal(data, &sp); err != nil {
return err
}
p := Policy(sp)
p.dropDuplicateStatements()
*iamp = p
return nil
}
// Validate - validates all statements are for given bucket or not.
func (iamp Policy) Validate() error {
return iamp.isValid()
}
// ParseConfig - parses data in given reader to Iamp.
func ParseConfig(reader io.Reader) (*Policy, error) {
var iamp Policy
decoder := json.NewDecoder(reader)
decoder.DisallowUnknownFields()
if err := decoder.Decode(&iamp); err != nil {
return nil, Errorf("%w", err)
}
return &iamp, iamp.Validate()
}
// Equals returns true if the two policies are identical
func (iamp *Policy) Equals(p Policy) bool {
if iamp.ID != p.ID || iamp.Version != p.Version {
return false
}
if len(iamp.Statements) != len(p.Statements) {
return false
}
for i, st := range iamp.Statements {
if !p.Statements[i].Equals(st) {
return false
}
}
return true
}
golang-github-minio-pkg-3.0.10/policy/policy_test.go 0000664 0000000 0000000 00000104717 14655770405 0022425 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
import (
"bytes"
"encoding/json"
"net"
"strings"
"testing"
"time"
"github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/pkg/v3/policy/condition"
)
func TestGetPoliciesFromClaims(t *testing.T) {
attributesArray := `{
"exp": 1594690452,
"iat": 1594689552,
"auth_time": 1594689552,
"jti": "18ed05c9-2c69-45d5-a33f-8c94aca99ad5",
"iss": "http://localhost:8080/auth/realms/minio",
"aud": "account",
"sub": "7e5e2f30-1c97-4616-8623-2eae14dee9b1",
"typ": "ID",
"azp": "account",
"nonce": "66ZoLzwJbjdkiedI",
"session_state": "3df7b526-5310-4038-9f35-50ecd295a31d",
"acr": "1",
"upn": "harsha",
"address": {},
"email_verified": false,
"groups": [
"offline_access"
],
"preferred_username": "harsha",
"policy": [
"readwrite",
"readwrite,readonly",
" readonly",
""
]}`
m := make(map[string]interface{})
if err := json.Unmarshal([]byte(attributesArray), &m); err != nil {
t.Fatal(err)
}
expectedSet := set.CreateStringSet("readwrite", "readonly")
gotSet, ok := GetPoliciesFromClaims(m, "policy")
if !ok {
t.Fatal("no policy claim was found")
}
if gotSet.IsEmpty() {
t.Fatal("no policies were found in policy claim")
}
if !gotSet.Equals(expectedSet) {
t.Fatalf("Expected %v got %v", expectedSet, gotSet)
}
}
func TestPolicyIsAllowedActions(t *testing.T) {
policy1 := `{
"Version":"2012-10-17",
"Statement":[
{
"Sid":"statement1",
"Effect":"Allow",
"Action": "s3:CreateBucket",
"Resource": "arn:aws:s3:::*",
"Condition": {
"StringLike": {
"s3:LocationConstraint": "us-east-1"
}
}
},
{
"Sid":"statement2",
"Effect":"Deny",
"Action": "s3:CreateBucket",
"Resource": "arn:aws:s3:::*",
"Condition": {
"StringNotLike": {
"s3:LocationConstraint": "us-east-1"
}
}
}
]
}`
p, err := ParseConfig(strings.NewReader(policy1))
if err != nil {
t.Fatal(err)
}
allowedActions := p.IsAllowedActions("testbucket", "", map[string][]string{
"LocationConstraint": {"us-east-1"},
})
if !allowedActions.Match(CreateBucketAction) {
t.Fatal("expected success for CreateBucket, but failed to match")
}
allowedActions = p.IsAllowedActions("testbucket", "", map[string][]string{
"LocationConstraint": {"us-east-2"},
})
if allowedActions.Match(CreateBucketAction) {
t.Fatal("expected no CreateBucket in allowed actions, but found instead")
}
}
func TestPolicyIsAllowed(t *testing.T) {
case1Policy := Policy{
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(GetBucketLocationAction, PutObjectAction),
NewResourceSet(NewResource("*")),
condition.NewFunctions(),
),
},
}
case2Policy := Policy{
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(GetObjectAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
_, IPNet, err := net.ParseCIDR("192.168.1.0/24")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func1, err := condition.NewIPAddressFunc(
condition.AWSSourceIP.ToKey(),
IPNet,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case3Policy := Policy{
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(GetObjectAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1),
),
},
}
case4Policy := Policy{
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Deny,
NewActionSet(GetObjectAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1),
),
},
}
anonGetBucketLocationArgs := Args{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: GetBucketLocationAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{},
}
anonPutObjectActionArgs := Args{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: PutObjectAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{
"x-amz-copy-source": {"mybucket/myobject"},
"SourceIp": {"192.168.1.10"},
},
ObjectName: "myobject",
}
anonGetObjectActionArgs := Args{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: GetObjectAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{},
ObjectName: "myobject",
}
getBucketLocationArgs := Args{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: GetBucketLocationAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{},
}
putObjectActionArgs := Args{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: PutObjectAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{
"x-amz-copy-source": {"mybucket/myobject"},
"SourceIp": {"192.168.1.10"},
},
ObjectName: "myobject",
}
getObjectActionArgs := Args{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: GetObjectAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{},
ObjectName: "myobject",
}
testCases := []struct {
policy Policy
args Args
expectedResult bool
}{
{case1Policy, anonGetBucketLocationArgs, true},
{case1Policy, anonPutObjectActionArgs, true},
{case1Policy, anonGetObjectActionArgs, false},
{case1Policy, getBucketLocationArgs, true},
{case1Policy, putObjectActionArgs, true},
{case1Policy, getObjectActionArgs, false},
{case2Policy, anonGetBucketLocationArgs, false},
{case2Policy, anonPutObjectActionArgs, true},
{case2Policy, anonGetObjectActionArgs, true},
{case2Policy, getBucketLocationArgs, false},
{case2Policy, putObjectActionArgs, true},
{case2Policy, getObjectActionArgs, true},
{case3Policy, anonGetBucketLocationArgs, false},
{case3Policy, anonPutObjectActionArgs, true},
{case3Policy, anonGetObjectActionArgs, false},
{case3Policy, getBucketLocationArgs, false},
{case3Policy, putObjectActionArgs, true},
{case3Policy, getObjectActionArgs, false},
{case4Policy, anonGetBucketLocationArgs, false},
{case4Policy, anonPutObjectActionArgs, false},
{case4Policy, anonGetObjectActionArgs, false},
{case4Policy, getBucketLocationArgs, false},
{case4Policy, putObjectActionArgs, false},
{case4Policy, getObjectActionArgs, false},
}
for i, testCase := range testCases {
result := testCase.policy.IsAllowed(testCase.args)
if result != testCase.expectedResult {
t.Errorf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestPolicyIsEmpty(t *testing.T) {
case1Policy := Policy{
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
case2Policy := Policy{
ID: "MyPolicyForMyBucket",
Version: DefaultVersion,
}
testCases := []struct {
policy Policy
expectedResult bool
}{
{case1Policy, false},
{case2Policy, true},
}
for i, testCase := range testCases {
result := testCase.policy.IsEmpty()
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestPolicyIsValid(t *testing.T) {
case1Policy := Policy{
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
case2Policy := Policy{
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
NewStatement(
"",
Deny,
NewActionSet(GetObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
case3Policy := Policy{
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
NewStatement(
"",
Deny,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/yourobject*")),
condition.NewFunctions(),
),
},
}
func1, err := condition.NewNullFunc(
condition.S3XAmzCopySource.ToKey(),
true,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func2, err := condition.NewNullFunc(
condition.S3XAmzServerSideEncryption.ToKey(),
false,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case4Policy := Policy{
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1),
),
NewStatement(
"",
Deny,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func2),
),
},
}
case5Policy := Policy{
Version: "17-10-2012",
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
case6Policy := Policy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(GetObjectAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1, func2),
),
},
}
case7Policy := Policy{
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
NewStatement(
"",
Deny,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
case8Policy := Policy{
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
NewStatement(
"",
Allow,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
testCases := []struct {
policy Policy
expectErr bool
}{
{case1Policy, false},
// allowed duplicate principal.
{case2Policy, false},
// allowed duplicate principal and action.
{case3Policy, false},
// allowed duplicate principal, action and resource.
{case4Policy, false},
// Invalid version error.
{case5Policy, true},
// Invalid statement error.
{case6Policy, true},
// Duplicate statement different Effects.
{case7Policy, false},
// Duplicate statement same Effects, duplicate effect will be removed.
{case8Policy, false},
}
for i, testCase := range testCases {
err := testCase.policy.isValid()
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
}
}
// Parse config with location constraints
func TestPolicyParseConfig(t *testing.T) {
policy1LocationConstraint := `{
"Version":"2012-10-17",
"Statement":[
{
"Sid":"statement1",
"Effect":"Allow",
"Action": "s3:CreateBucket",
"Resource": "arn:aws:s3:::*",
"Condition": {
"StringLike": {
"s3:LocationConstraint": "us-east-1"
}
}
},
{
"Sid":"statement2",
"Effect":"Deny",
"Action": "s3:CreateBucket",
"Resource": "arn:aws:s3:::*",
"Condition": {
"StringNotLike": {
"s3:LocationConstraint": "us-east-1"
}
}
}
]
}`
policy2Condition := `{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "statement1",
"Effect": "Allow",
"Action": "s3:GetObjectVersion",
"Resource": "arn:aws:s3:::test/HappyFace.jpg"
},
{
"Sid": "statement2",
"Effect": "Deny",
"Action": "s3:GetObjectVersion",
"Resource": "arn:aws:s3:::test/HappyFace.jpg",
"Condition": {
"StringNotEquals": {
"s3:versionid": "AaaHbAQitwiL_h47_44lRO2DDfLlBO5e"
}
}
}
]
}`
policy3ConditionActionRegex := `{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "statement2",
"Effect": "Allow",
"Action": "s3:Get*",
"Resource": "arn:aws:s3:::test/HappyFace.jpg",
"Condition": {
"StringEquals": {
"s3:versionid": "AaaHbAQitwiL_h47_44lRO2DDfLlBO5e"
}
}
}
]
}`
policy4ConditionAction := `{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "statement2",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::test/HappyFace.jpg",
"Condition": {
"StringEquals": {
"s3:versionid": "AaaHbAQitwiL_h47_44lRO2DDfLlBO5e"
}
}
}
]
}`
policy5ConditionCurrenTime := `{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:Get*",
"s3:Put*"
],
"Resource": [
"arn:aws:s3:::test/*"
],
"Condition": {
"DateGreaterThan": {
"aws:CurrentTime": [
"2017-02-28T00:00:00Z"
]
}
}
}
]
}`
policy5ConditionCurrenTimeLesser := `{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:Get*",
"s3:Put*"
],
"Resource": [
"arn:aws:s3:::test/*"
],
"Condition": {
"DateLessThan": {
"aws:CurrentTime": [
"2017-02-28T00:00:00Z"
]
}
}
}
]
}`
tests := []struct {
p string
args Args
allowed bool
}{
{
p: policy1LocationConstraint,
allowed: true,
args: Args{
AccountName: "allowed",
Action: CreateBucketAction,
BucketName: "test",
ConditionValues: map[string][]string{"LocationConstraint": {"us-east-1"}},
},
},
{
p: policy1LocationConstraint,
allowed: false,
args: Args{
AccountName: "disallowed",
Action: CreateBucketAction,
BucketName: "test",
ConditionValues: map[string][]string{"LocationConstraint": {"us-east-2"}},
},
},
{
p: policy2Condition,
allowed: true,
args: Args{
AccountName: "allowed",
Action: GetObjectAction,
BucketName: "test",
ObjectName: "HappyFace.jpg",
ConditionValues: map[string][]string{"versionid": {"AaaHbAQitwiL_h47_44lRO2DDfLlBO5e"}},
},
},
{
p: policy2Condition,
allowed: false,
args: Args{
AccountName: "disallowed",
Action: GetObjectAction,
BucketName: "test",
ObjectName: "HappyFace.jpg",
ConditionValues: map[string][]string{"versionid": {"AaaHbAQitwiL_h47_44lRO2DDfLlBO5f"}},
},
},
{
p: policy3ConditionActionRegex,
allowed: true,
args: Args{
AccountName: "allowed",
Action: GetObjectAction,
BucketName: "test",
ObjectName: "HappyFace.jpg",
ConditionValues: map[string][]string{"versionid": {"AaaHbAQitwiL_h47_44lRO2DDfLlBO5e"}},
},
},
{
p: policy3ConditionActionRegex,
allowed: false,
args: Args{
AccountName: "disallowed",
Action: GetObjectAction,
BucketName: "test",
ObjectName: "HappyFace.jpg",
ConditionValues: map[string][]string{"versionid": {"AaaHbAQitwiL_h47_44lRO2DDfLlBO5f"}},
},
},
{
p: policy4ConditionAction,
allowed: true,
args: Args{
AccountName: "allowed",
Action: GetObjectAction,
BucketName: "test",
ObjectName: "HappyFace.jpg",
ConditionValues: map[string][]string{"versionid": {"AaaHbAQitwiL_h47_44lRO2DDfLlBO5e"}},
},
},
{
p: policy5ConditionCurrenTime,
allowed: true,
args: Args{
AccountName: "allowed",
Action: GetObjectAction,
BucketName: "test",
ObjectName: "HappyFace.jpg",
ConditionValues: map[string][]string{
"CurrentTime": {time.Now().Format(time.RFC3339)},
},
},
},
{
p: policy5ConditionCurrenTimeLesser,
allowed: false,
args: Args{
AccountName: "disallowed",
Action: GetObjectAction,
BucketName: "test",
ObjectName: "HappyFace.jpg",
ConditionValues: map[string][]string{
"CurrentTime": {time.Now().Format(time.RFC3339)},
},
},
},
}
for _, test := range tests {
test := test
t.Run(test.args.AccountName, func(t *testing.T) {
ip, err := ParseConfig(strings.NewReader(test.p))
if err != nil {
t.Error(err)
}
if got := ip.IsAllowed(test.args); got != test.allowed {
t.Errorf("Expected %t, got %t", test.allowed, got)
}
})
}
}
func TestPolicyUnmarshalJSONAndValidate(t *testing.T) {
case1Data := []byte(`{
"ID": "MyPolicyForMyBucket1",
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SomeId1",
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
}
]
}`)
case1Policy := Policy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
case1Policy.Statements[0].SID = "SomeId1"
case2Data := []byte(`{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
},
{
"Effect": "Deny",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::mybucket/yourobject*",
"Condition": {
"IpAddress": {
"aws:SourceIp": "192.168.1.0/24"
}
}
}
]
}`)
_, IPNet1, err := net.ParseCIDR("192.168.1.0/24")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func1, err := condition.NewIPAddressFunc(
condition.AWSSourceIP.ToKey(),
IPNet1,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Policy := Policy{
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
NewStatement(
"",
Deny,
NewActionSet(GetObjectAction),
NewResourceSet(NewResource("mybucket/yourobject*")),
condition.NewFunctions(func1),
),
},
}
case3Data := []byte(`{
"ID": "MyPolicyForMyBucket1",
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
},
{
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
}
]
}`)
case3Policy := Policy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(GetObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
NewStatement(
"",
Allow,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
case4Data := []byte(`{
"ID": "MyPolicyForMyBucket1",
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
},
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
}
]
}`)
case4Policy := Policy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
NewStatement(
"",
Allow,
NewActionSet(GetObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
case5Data := []byte(`{
"ID": "MyPolicyForMyBucket1",
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
},
{
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/yourobject*"
}
]
}`)
case5Policy := Policy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
NewStatement(
"",
Allow,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/yourobject*")),
condition.NewFunctions(),
),
},
}
case6Data := []byte(`{
"ID": "MyPolicyForMyBucket1",
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*",
"Condition": {
"IpAddress": {
"aws:SourceIp": "192.168.1.0/24"
}
}
},
{
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*",
"Condition": {
"IpAddress": {
"aws:SourceIp": "192.168.2.0/24"
}
}
}
]
}`)
_, IPNet2, err := net.ParseCIDR("192.168.2.0/24")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func2, err := condition.NewIPAddressFunc(
condition.AWSSourceIP.ToKey(),
IPNet2,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case6Policy := Policy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1),
),
NewStatement(
"",
Allow,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func2),
),
},
}
case7Data := []byte(`{
"ID": "MyPolicyForMyBucket1",
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:GetBucketLocation",
"Resource": "arn:aws:s3:::mybucket"
}
]
}`)
case7Policy := Policy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(GetBucketLocationAction),
NewResourceSet(NewResource("mybucket")),
condition.NewFunctions(),
),
},
}
case8Data := []byte(`{
"ID": "MyPolicyForMyBucket1",
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:GetBucketLocation",
"Resource": "arn:aws:s3:::*"
}
]
}`)
case8Policy := Policy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(GetBucketLocationAction),
NewResourceSet(NewResource("*")),
condition.NewFunctions(),
),
},
}
case9Data := []byte(`{
"ID": "MyPolicyForMyBucket1",
"Version": "17-10-2012",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
}
]
}`)
case10Data := []byte(`{
"ID": "MyPolicyForMyBucket1",
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
},
{
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
}
]
}`)
case10Policy := Policy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
case11Data := []byte(`{
"ID": "MyPolicyForMyBucket1",
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
},
{
"Effect": "Deny",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
}
]
}`)
case11Policy := Policy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
NewStatement(
"",
Deny,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
case12Data := []byte(`{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::*"
]
},
{
"Effect": "Allow",
"Action": [
"admin:*"
]
},
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::*"
]
}
]
}`)
case12Policy := Policy{
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(AllActions),
NewResourceSet(NewResource("*")),
condition.NewFunctions(),
),
NewStatement(
"",
Allow,
NewActionSet(AllAdminActions),
ResourceSet{},
condition.NewFunctions(),
),
},
}
case13Data := []byte(`{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::*"
]
},
{
"Effect": "Deny",
"Action": [
"admin:*"
]
},
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::*"
]
},
{
"Effect": "Allow",
"Action": [
"admin:*"
]
}
]
}`)
case13Policy := Policy{
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(AllActions),
NewResourceSet(NewResource("*")),
condition.NewFunctions(),
),
NewStatement(
"",
Deny,
NewActionSet(AllAdminActions),
ResourceSet{},
condition.NewFunctions(),
),
NewStatement(
"",
Allow,
NewActionSet(AllAdminActions),
ResourceSet{},
condition.NewFunctions(),
),
},
}
case14Data := []byte(`{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::*"
]
},
{
"Effect": "Deny",
"Action": [
"admin:*"
]
},
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::*"
]
}
]
}`)
case14Policy := Policy{
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(AllActions),
NewResourceSet(NewResource("*")),
condition.NewFunctions(),
),
NewStatement(
"",
Deny,
NewActionSet(AllAdminActions),
ResourceSet{},
condition.NewFunctions(),
),
},
}
testCases := []struct {
data []byte
expectedResult Policy
expectUnmarshalErr bool
expectValidationErr bool
}{
{case1Data, case1Policy, false, false},
{case2Data, case2Policy, false, false},
{case3Data, case3Policy, false, false},
{case4Data, case4Policy, false, false},
{case5Data, case5Policy, false, false},
{case6Data, case6Policy, false, false},
{case7Data, case7Policy, false, false},
{case8Data, case8Policy, false, false},
// Invalid version error.
{case9Data, Policy{}, false, true},
// Duplicate statement success, duplicate statement is removed.
{case10Data, case10Policy, false, false},
// Duplicate statement success (Effect differs).
{case11Data, case11Policy, false, false},
// Duplicate statement success, must be removed.
{case12Data, case12Policy, false, false},
// Duplicate statement success, must be removed.
{case13Data, case13Policy, false, false},
// Duplicate statement success, must be removed.
{case14Data, case14Policy, false, false},
}
for i, testCase := range testCases {
var result Policy
err := json.Unmarshal(testCase.data, &result)
expectErr := (err != nil)
if expectErr != testCase.expectUnmarshalErr {
t.Errorf("case %v: error during unmarshal: expected: %v, got: %v", i+1, testCase.expectUnmarshalErr, expectErr)
}
err = result.Validate()
expectErr = (err != nil)
if expectErr != testCase.expectValidationErr {
t.Errorf("case %v: error during validation: expected: %v, got: %v", i+1, testCase.expectValidationErr, expectErr)
}
if !testCase.expectUnmarshalErr && !testCase.expectValidationErr {
exp1, _ := json.Marshal(result)
exp2, _ := json.Marshal(testCase.expectedResult)
if !bytes.Equal(exp1, exp2) {
t.Errorf("case %v: result: expected: %v, got: %v", i+1, testCase.expectedResult, result)
}
}
}
}
func TestPolicyValidate(t *testing.T) {
case1Policy := Policy{
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("")),
condition.NewFunctions(),
),
},
}
func1, err := condition.NewNullFunc(
condition.S3XAmzCopySource.ToKey(),
true,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func2, err := condition.NewNullFunc(
condition.S3XAmzServerSideEncryption.ToKey(),
false,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Policy := Policy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(GetObjectAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1, func2),
),
},
}
case3Policy := Policy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(GetObjectAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
),
},
}
testCases := []struct {
policy Policy
expectErr bool
}{
{case1Policy, true},
{case2Policy, true},
{case3Policy, false},
}
for i, testCase := range testCases {
err := testCase.policy.Validate()
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
}
}
func TestMergePolicies(t *testing.T) {
p1 := Policy{
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Deny,
NewActionSet(AllAdminActions),
ResourceSet{},
condition.NewFunctions(),
),
NewStatement(
"",
Allow,
NewActionSet(AllActions),
NewResourceSet(NewResource("*")),
condition.NewFunctions(),
),
},
}
// p2 is a subset of p1
p2 := Policy{
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Deny,
NewActionSet(AllAdminActions),
ResourceSet{},
condition.NewFunctions(),
),
},
}
p3 := Policy{
ID: "MyPolicyForMyBucket1",
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Allow,
NewActionSet(GetBucketLocationAction),
NewResourceSet(NewResource("mybucket")),
condition.NewFunctions(),
),
},
}
testCases := []struct {
inputs []Policy
expected Policy
}{
{
inputs: nil,
expected: Policy{},
},
{
inputs: []Policy{},
expected: Policy{},
},
{
inputs: []Policy{p1},
expected: p1,
},
{
inputs: []Policy{p1, p1},
expected: p1,
},
{
inputs: []Policy{p1, p1, p1},
expected: p1,
},
{ // case 6
inputs: []Policy{p1, p2},
expected: p1,
},
{
inputs: []Policy{p1, p2, p1},
expected: p1,
},
{
inputs: []Policy{p1, p2, p3},
expected: Policy{
Version: DefaultVersion,
Statements: []Statement{
NewStatement(
"",
Deny,
NewActionSet(AllAdminActions),
ResourceSet{},
condition.NewFunctions(),
),
NewStatement(
"",
Allow,
NewActionSet(AllActions),
NewResourceSet(NewResource("*")),
condition.NewFunctions(),
),
NewStatement(
"",
Allow,
NewActionSet(GetBucketLocationAction),
NewResourceSet(NewResource("mybucket")),
condition.NewFunctions(),
),
},
},
},
}
for i, testCase := range testCases {
got := MergePolicies(testCase.inputs...)
if !got.Equals(testCase.expected) {
t.Errorf("Case %d: expected: %v, got %v", i+1, got, testCase.expected)
}
}
}
golang-github-minio-pkg-3.0.10/policy/principal.go 0000664 0000000 0000000 00000005227 14655770405 0022044 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
import (
"encoding/json"
"github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/pkg/v3/wildcard"
)
// Principal - policy principal.
type Principal struct {
AWS set.StringSet
}
// IsValid - checks whether Principal is valid or not.
func (p Principal) IsValid() bool {
return len(p.AWS) != 0
}
// Equals - returns true if principals are equal.
func (p Principal) Equals(pp Principal) bool {
return p.AWS.Equals(pp.AWS)
}
// Intersection - returns principals available in both Principal.
func (p Principal) Intersection(principal Principal) set.StringSet {
return p.AWS.Intersection(principal.AWS)
}
// MarshalJSON - encodes Principal to JSON data.
func (p Principal) MarshalJSON() ([]byte, error) {
if !p.IsValid() {
return nil, Errorf("invalid principal %v", p)
}
// subtype to avoid recursive call to MarshalJSON()
type subPrincipal Principal
sp := subPrincipal(p)
return json.Marshal(sp)
}
// Match - matches given principal is wildcard matching with Principal.
func (p Principal) Match(principal string) bool {
for _, pattern := range p.AWS.ToSlice() {
if wildcard.MatchSimple(pattern, principal) {
return true
}
}
return false
}
// UnmarshalJSON - decodes JSON data to Principal.
func (p *Principal) UnmarshalJSON(data []byte) error {
// subtype to avoid recursive call to UnmarshalJSON()
type subPrincipal Principal
var sp subPrincipal
if err := json.Unmarshal(data, &sp); err != nil {
var s string
if err = json.Unmarshal(data, &s); err != nil {
return err
}
if s != "*" {
return Errorf("invalid principal '%v'", s)
}
sp.AWS = set.CreateStringSet("*")
}
*p = Principal(sp)
return nil
}
// Clone clones Principal structure
func (p Principal) Clone() Principal {
return NewPrincipal(p.AWS.ToSlice()...)
}
// NewPrincipal - creates new Principal.
func NewPrincipal(principals ...string) Principal {
return Principal{AWS: set.CreateStringSet(principals...)}
}
golang-github-minio-pkg-3.0.10/policy/principal_test.go 0000664 0000000 0000000 00000010537 14655770405 0023103 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
import (
"encoding/json"
"reflect"
"testing"
"github.com/minio/minio-go/v7/pkg/set"
)
func TestPrincipalIsValid(t *testing.T) {
testCases := []struct {
principal Principal
expectedResult bool
}{
{NewPrincipal("*"), true},
{NewPrincipal("arn:aws:iam::AccountNumber:root"), true},
{NewPrincipal(), false},
}
for i, testCase := range testCases {
result := testCase.principal.IsValid()
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestPrincipalIntersection(t *testing.T) {
testCases := []struct {
principal Principal
principalToIntersect Principal
expectedResult set.StringSet
}{
{NewPrincipal("*"), NewPrincipal("*"), set.CreateStringSet("*")},
{NewPrincipal("arn:aws:iam::AccountNumber:root"), NewPrincipal("arn:aws:iam::AccountNumber:myuser"), set.CreateStringSet()},
{NewPrincipal(), NewPrincipal("*"), set.CreateStringSet()},
}
for i, testCase := range testCases {
result := testCase.principal.Intersection(testCase.principalToIntersect)
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestPrincipalMarshalJSON(t *testing.T) {
testCases := []struct {
principal Principal
expectedResult []byte
expectErr bool
}{
{NewPrincipal("*"), []byte(`{"AWS":["*"]}`), false},
{NewPrincipal("arn:aws:iam::AccountNumber:*"), []byte(`{"AWS":["arn:aws:iam::AccountNumber:*"]}`), false},
{NewPrincipal(), nil, true},
}
for i, testCase := range testCases {
result, err := json.Marshal(testCase.principal)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, string(testCase.expectedResult), string(result))
}
}
}
}
func TestPrincipalMatch(t *testing.T) {
testCases := []struct {
principals Principal
principal string
expectedResult bool
}{
{NewPrincipal("*"), "AccountNumber", true},
{NewPrincipal("arn:aws:iam::*"), "arn:aws:iam::AccountNumber:root", true},
{NewPrincipal("arn:aws:iam::AccountNumber:*"), "arn:aws:iam::TestAccountNumber:root", false},
}
for i, testCase := range testCases {
result := testCase.principals.Match(testCase.principal)
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestPrincipalUnmarshalJSON(t *testing.T) {
testCases := []struct {
data []byte
expectedResult Principal
expectErr bool
}{
{[]byte(`"*"`), NewPrincipal("*"), false},
{[]byte(`{"AWS": "*"}`), NewPrincipal("*"), false},
{[]byte(`{"AWS": "arn:aws:iam::AccountNumber:*"}`), NewPrincipal("arn:aws:iam::AccountNumber:*"), false},
{[]byte(`"arn:aws:iam::AccountNumber:*"`), NewPrincipal(), true},
{[]byte(`["arn:aws:iam::AccountNumber:*", "arn:aws:iam:AnotherAccount:*"]`), NewPrincipal(), true},
}
for i, testCase := range testCases {
var result Principal
err := json.Unmarshal(testCase.data, &result)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v\n", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
}
golang-github-minio-pkg-3.0.10/policy/resource.go 0000664 0000000 0000000 00000013414 14655770405 0021707 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
import (
"encoding/json"
"path"
"strings"
"github.com/minio/pkg/v3/policy/condition"
"github.com/minio/pkg/v3/wildcard"
)
const (
// ResourceARNPrefix - resource S3 ARN prefix as per S3 specification.
ResourceARNPrefix = "arn:aws:s3:::"
// ResourceARNKMSPrefix is for KMS key resources. MinIO specific API.
ResourceARNKMSPrefix = "arn:minio:kms:::"
)
// ResourceARNType - ARN prefix type
type ResourceARNType uint32
const (
// Zero value for detecting errors
unknownARN ResourceARNType = iota
// ResourceARNS3 is the ARN prefix type for S3 resources.
ResourceARNS3
// ResourceARNKMS is the ARN prefix type for MinIO KMS resources.
ResourceARNKMS
)
// ARNTypeToPrefix maps the type to prefix string
var ARNTypeToPrefix = map[ResourceARNType]string{
ResourceARNS3: ResourceARNPrefix,
ResourceARNKMS: ResourceARNKMSPrefix,
}
// ARNPrefixToType maps prefix to types.
var ARNPrefixToType map[string]ResourceARNType
func init() {
ARNPrefixToType = make(map[string]ResourceARNType)
for k, v := range ARNTypeToPrefix {
ARNPrefixToType[v] = k
}
}
func (a ResourceARNType) String() string {
return ARNTypeToPrefix[a]
}
// Resource - resource in policy statement.
type Resource struct {
Pattern string
Type ResourceARNType
}
func (r Resource) isKMS() bool {
return r.Type == ResourceARNKMS
}
func (r Resource) isS3() bool {
return r.Type == ResourceARNS3
}
func (r Resource) isBucketPattern() bool {
return !strings.Contains(r.Pattern, "/") || r.Pattern == "*"
}
func (r Resource) isObjectPattern() bool {
return strings.Contains(r.Pattern, "/") || strings.Contains(r.Pattern, "*")
}
// IsValid - checks whether Resource is valid or not.
func (r Resource) IsValid() bool {
if r.Type == unknownARN {
return false
}
if r.isS3() {
if strings.HasPrefix(r.Pattern, "/") {
return false
}
}
if r.isKMS() {
if strings.IndexFunc(r.Pattern, func(c rune) bool {
return c == '/' || c == '\\' || c == '.'
}) >= 0 {
return false
}
}
return r.Pattern != ""
}
// MatchResource matches object name with resource pattern only.
func (r Resource) MatchResource(resource string) bool {
return r.Match(resource, nil)
}
// Match - matches object name with resource pattern, including specific conditionals.
func (r Resource) Match(resource string, conditionValues map[string][]string) bool {
pattern := r.Pattern
if len(conditionValues) != 0 {
for _, key := range condition.CommonKeys {
// Empty values are not supported for policy variables.
if rvalues, ok := conditionValues[key.Name()]; ok && rvalues[0] != "" {
pattern = strings.Replace(pattern, key.VarName(), rvalues[0], -1)
}
}
}
if cp := path.Clean(resource); cp != "." && cp == pattern {
return true
}
return wildcard.Match(pattern, resource)
}
// MarshalJSON - encodes Resource to JSON data.
func (r Resource) MarshalJSON() ([]byte, error) {
if !r.IsValid() {
return nil, Errorf("invalid resource %v", r)
}
return json.Marshal(r.String())
}
func (r Resource) String() string {
return r.Type.String() + r.Pattern
}
// UnmarshalJSON - decodes JSON data to Resource.
func (r *Resource) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
parsedResource, err := parseResource(s)
if err != nil {
return err
}
*r = parsedResource
return nil
}
// Validate - validates Resource.
func (r Resource) Validate() error {
if !r.IsValid() {
return Errorf("invalid resource")
}
return nil
}
// ValidateBucket - validates that given bucketName is matched by Resource.
func (r Resource) ValidateBucket(bucketName string) error {
if !r.IsValid() {
return Errorf("invalid resource")
}
// For the resource to match the bucket, there are two cases:
//
// 1. the whole resource pattern must match the bucket name (e.g.
// `example*a` matches bucket 'example-east-a'), or
//
// 2. bucket name followed by '/' must match as a prefix of the resource
// pattern (e.g. `example*a` includes resources in a bucket 'example22'
// for example the object `example22/2023/a` is matched by this resource).
if !wildcard.Match(r.Pattern, bucketName) &&
!wildcard.MatchAsPatternPrefix(r.Pattern, bucketName+"/") {
return Errorf("bucket name does not match")
}
return nil
}
// parseResource - parses string to Resource.
func parseResource(s string) (Resource, error) {
r := Resource{}
for k, v := range ARNPrefixToType {
if rem, ok := strings.CutPrefix(s, k); ok {
r.Type = v
r.Pattern = rem
break
}
}
if r.Type == unknownARN {
return r, Errorf("invalid resource '%v'", s)
}
if strings.HasPrefix(r.Pattern, "/") {
return r, Errorf("invalid resource '%v' - starts with '/' will not match a bucket", s)
}
return r, nil
}
// NewResource - creates new resource with the default ARN type of S3.
func NewResource(pattern string) Resource {
return Resource{
Pattern: pattern,
Type: ResourceARNS3,
}
}
// NewKMSResource - creates new resource with type KMS
func NewKMSResource(pattern string) Resource {
return Resource{
Pattern: pattern,
Type: ResourceARNKMS,
}
}
golang-github-minio-pkg-3.0.10/policy/resource_test.go 0000664 0000000 0000000 00000020412 14655770405 0022742 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
import (
"encoding/json"
"reflect"
"testing"
)
func TestResourceIsBucketPattern(t *testing.T) {
testCases := []struct {
resource Resource
expectedResult bool
}{
{NewResource("*"), true},
{NewResource("mybucket"), true},
{NewResource("mybucket*"), true},
{NewResource("mybucket?0"), true},
{NewResource("*/*"), false},
{NewResource("mybucket/*"), false},
{NewResource("mybucket*/myobject"), false},
{NewResource("mybucket?0/2010/photos/*"), false},
{NewKMSResource("*"), true},
{NewKMSResource("mykey"), true},
{NewKMSResource("mykey*"), true},
{NewKMSResource("mykey?0"), true},
}
for i, testCase := range testCases {
result := testCase.resource.isBucketPattern()
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v", i+1, testCase.expectedResult, result)
}
}
}
func TestResourceIsObjectPattern(t *testing.T) {
testCases := []struct {
resource Resource
expectedResult bool
}{
{NewResource("*"), true},
{NewResource("mybucket*"), true},
{NewResource("*/*"), true},
{NewResource("mybucket/*"), true},
{NewResource("mybucket*/myobject"), true},
{NewResource("mybucket?0/2010/photos/*"), true},
{NewResource("mybucket"), false},
{NewResource("mybucket?0"), false},
}
for i, testCase := range testCases {
result := testCase.resource.isObjectPattern()
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v", i+1, testCase.expectedResult, result)
}
}
}
func TestResourceIsValid(t *testing.T) {
testCases := []struct {
resource Resource
expectedResult bool
}{
{NewResource("*"), true},
{NewResource("mybucket*"), true},
{NewResource("*/*"), true},
{NewResource("mybucket/*"), true},
{NewResource("mybucket*/myobject"), true},
{NewResource("mybucket?0/2010/photos/*"), true},
{NewResource("mybucket"), true},
{NewResource("mybucket?0"), true},
{NewResource("/*"), false},
{NewResource(""), false},
{NewKMSResource("*"), true},
{NewKMSResource("mykey*"), true},
{NewKMSResource("*/*"), false},
{NewKMSResource("mykey/*"), false},
{NewKMSResource("mykey/"), false},
{NewKMSResource("./mykey"), false},
{NewKMSResource("../../mykey"), false},
{NewKMSResource(""), false},
}
for i, testCase := range testCases {
result := testCase.resource.IsValid()
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v", i+1, testCase.expectedResult, result)
}
}
}
func TestResourceMatch(t *testing.T) {
// Only test with valid resources (specifically, resources must not start
// with '/')
testCases := []struct {
resource Resource
objectName string
expectedResult bool
}{
{NewResource("*"), "mybucket", true},
{NewResource("*"), "mybucket/myobject", true},
{NewResource("mybucket*"), "mybucket", true},
{NewResource("mybucket*"), "mybucket/myobject", true},
{NewResource("*/*"), "mybucket/myobject", true},
{NewResource("mybucket/*"), "mybucket/myobject", true},
{NewResource("mybucket*/myobject"), "mybucket/myobject", true},
{NewResource("mybucket*/myobject"), "mybucket100/myobject", true},
{NewResource("mybucket?0/2010/photos/*"), "mybucket20/2010/photos/1.jpg", true},
{NewResource("mybucket"), "mybucket", true},
{NewResource("mybucket?0"), "mybucket30", true},
{NewResource("*/*"), "mybucket", false},
{NewResource("mybucket/*"), "mybucket10/myobject", false},
{NewResource("mybucket?0/2010/photos/*"), "mybucket0/2010/photos/1.jpg", false},
{NewResource("mybucket"), "mybucket/myobject", false},
}
for i, testCase := range testCases {
result := testCase.resource.Match(testCase.objectName, nil)
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v", i+1, testCase.expectedResult, result)
}
}
}
func TestResourceMarshalJSON(t *testing.T) {
// Only test with valid resources (specifically, resources must not start
// with '/')
testCases := []struct {
resource Resource
expectedResult []byte
expectErr bool
}{
{NewResource("*"), []byte(`"arn:aws:s3:::*"`), false},
{NewResource("mybucket*"), []byte(`"arn:aws:s3:::mybucket*"`), false},
{NewResource("mybucket"), []byte(`"arn:aws:s3:::mybucket"`), false},
{NewResource("*/*"), []byte(`"arn:aws:s3:::*/*"`), false},
{NewResource("mybucket/*"), []byte(`"arn:aws:s3:::mybucket/*"`), false},
{NewResource("mybucket*/myobject"), []byte(`"arn:aws:s3:::mybucket*/myobject"`), false},
{NewResource("mybucket?0/2010/photos/*"), []byte(`"arn:aws:s3:::mybucket?0/2010/photos/*"`), false},
{Resource{}, nil, true},
}
for i, testCase := range testCases {
result, err := json.Marshal(testCase.resource)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v", i+1, string(testCase.expectedResult), string(result))
}
}
}
}
func TestResourceUnmarshalJSON(t *testing.T) {
testCases := []struct {
data []byte
expectedResult Resource
expectErr bool
}{
{[]byte(`"arn:aws:s3:::*"`), NewResource("*"), false},
{[]byte(`"arn:aws:s3:::mybucket*"`), NewResource("mybucket*"), false},
{[]byte(`"arn:aws:s3:::mybucket"`), NewResource("mybucket"), false},
{[]byte(`"arn:aws:s3:::*/*"`), NewResource("*/*"), false},
{[]byte(`"arn:aws:s3:::mybucket/*"`), NewResource("mybucket/*"), false},
{[]byte(`"arn:aws:s3:::mybucket*/myobject"`), NewResource("mybucket*/myobject"), false},
{[]byte(`"arn:aws:s3:::mybucket?0/2010/photos/*"`), NewResource("mybucket?0/2010/photos/*"), false},
{[]byte(`"mybucket/myobject*"`), Resource{}, true},
{[]byte(`"arn:aws:s3:::/*"`), Resource{}, true},
}
for i, testCase := range testCases {
var result Resource
err := json.Unmarshal(testCase.data, &result)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v", i+1, testCase.expectedResult, result)
}
}
}
}
func TestResourceValidate(t *testing.T) {
testCases := []struct {
resource Resource
expectErr bool
}{
{NewResource("mybucket/myobject*"), false},
{NewResource("/myobject*"), true},
{NewResource("/"), true},
}
for i, testCase := range testCases {
err := testCase.resource.Validate()
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
}
}
func TestResourceValidateBucket(t *testing.T) {
testCases := []struct {
resource Resource
bucketName string
expectErr bool
}{
{NewResource("mybucket/myobject*"), "mybucket", false},
{NewResource("/myobject*"), "yourbucket", true},
{NewResource("mybucket/myobject*"), "yourbucket", true},
{NewResource("mybucket*a/myobject*"), "mybucket-east-a", false},
// Following test cases **should validate** successfully - they are
// corner cases for the given patterns and buckets.
{NewResource("mybucket*a/myobject*"), "mybucket", false},
{NewResource("mybucket*a/myobject*"), "mybucket22", false},
}
for i, testCase := range testCases {
err := testCase.resource.ValidateBucket(testCase.bucketName)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Errorf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
}
}
golang-github-minio-pkg-3.0.10/policy/resourceset.go 0000664 0000000 0000000 00000012440 14655770405 0022421 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
import (
"encoding/json"
"fmt"
"sort"
"github.com/minio/minio-go/v7/pkg/set"
)
// ResourceSet - set of resources in policy statement.
type ResourceSet map[Resource]struct{}
// BucketResourceExists - checks if at least one bucket resource exists in the set.
func (resourceSet ResourceSet) BucketResourceExists() bool {
for resource := range resourceSet {
if resource.isBucketPattern() {
return true
}
}
return false
}
// ObjectResourceExists - checks if at least one object resource exists in the set.
func (resourceSet ResourceSet) ObjectResourceExists() bool {
for resource := range resourceSet {
if resource.isObjectPattern() {
return true
}
}
return false
}
// Add - adds resource to resource set.
func (resourceSet ResourceSet) Add(resource Resource) {
resourceSet[resource] = struct{}{}
}
// Equals - checks whether given resource set is equal to current resource set or not.
func (resourceSet ResourceSet) Equals(sresourceSet ResourceSet) bool {
// If length of set is not equal to length of given set, the
// set is not equal to given set.
if len(resourceSet) != len(sresourceSet) {
return false
}
// As both sets are equal in length, check each elements are equal.
for k := range resourceSet {
if _, ok := sresourceSet[k]; !ok {
return false
}
}
return true
}
// Intersection - returns resources available in both ResourceSet.
func (resourceSet ResourceSet) Intersection(sset ResourceSet) ResourceSet {
nset := NewResourceSet()
for k := range resourceSet {
if _, ok := sset[k]; ok {
nset.Add(k)
}
}
return nset
}
// MarshalJSON - encodes ResourceSet to JSON data.
func (resourceSet ResourceSet) MarshalJSON() ([]byte, error) {
resources := []Resource{}
for resource := range resourceSet {
resources = append(resources, resource)
}
return json.Marshal(resources)
}
// MatchResource matches object name with resource patterns only.
func (resourceSet ResourceSet) MatchResource(resource string) bool {
for r := range resourceSet {
if r.MatchResource(resource) {
return true
}
}
return false
}
// Match - matches object name with anyone of resource pattern in resource set.
func (resourceSet ResourceSet) Match(resource string, conditionValues map[string][]string) bool {
for r := range resourceSet {
if r.Match(resource, conditionValues) {
return true
}
}
return false
}
func (resourceSet ResourceSet) String() string {
resources := []string{}
for resource := range resourceSet {
resources = append(resources, resource.String())
}
sort.Strings(resources)
return fmt.Sprintf("%v", resources)
}
// UnmarshalJSON - decodes JSON data to ResourceSet.
func (resourceSet *ResourceSet) UnmarshalJSON(data []byte) error {
var sset set.StringSet
if err := json.Unmarshal(data, &sset); err != nil {
return err
}
*resourceSet = make(ResourceSet)
for _, s := range sset.ToSlice() {
resource, err := parseResource(s)
if err != nil {
return err
}
if _, found := (*resourceSet)[resource]; found {
return Errorf("duplicate resource '%v' found", s)
}
resourceSet.Add(resource)
}
return nil
}
// ValidateS3 - validates ResourceSet is S3.
func (resourceSet ResourceSet) ValidateS3() error {
for resource := range resourceSet {
if !resource.isS3() {
return Errorf("resource '%v' type is not S3", resource)
}
if err := resource.Validate(); err != nil {
return err
}
}
return nil
}
// ValidateKMS - validates ResourceSet is KMS.
func (resourceSet ResourceSet) ValidateKMS() error {
for resource := range resourceSet {
if !resource.isKMS() {
return Errorf("resource '%v' type is not KMS", resource)
}
if err := resource.Validate(); err != nil {
return err
}
}
return nil
}
// ValidateBucket - validates ResourceSet is for given bucket or not.
func (resourceSet ResourceSet) ValidateBucket(bucketName string) error {
for resource := range resourceSet {
if err := resource.ValidateBucket(bucketName); err != nil {
return err
}
}
return nil
}
// ToSlice - returns slice of resources from the resource set.
func (resourceSet ResourceSet) ToSlice() []Resource {
resources := []Resource{}
for resource := range resourceSet {
resources = append(resources, resource)
}
return resources
}
// Clone clones ResourceSet structure
func (resourceSet ResourceSet) Clone() ResourceSet {
return NewResourceSet(resourceSet.ToSlice()...)
}
// NewResourceSet - creates new resource set.
func NewResourceSet(resources ...Resource) ResourceSet {
resourceSet := make(ResourceSet)
for _, resource := range resources {
resourceSet.Add(resource)
}
return resourceSet
}
golang-github-minio-pkg-3.0.10/policy/resourceset_test.go 0000664 0000000 0000000 00000023016 14655770405 0023461 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
import (
"encoding/json"
"reflect"
"testing"
)
func TestResourceSetBucketResourceExists(t *testing.T) {
testCases := []struct {
resourceSet ResourceSet
expectedResult bool
}{
{NewResourceSet(NewResource("*")), true},
{NewResourceSet(NewResource("mybucket")), true},
{NewResourceSet(NewResource("mybucket*")), true},
{NewResourceSet(NewResource("mybucket?0")), true},
{NewResourceSet(NewResource("mybucket/2010/photos/*"),
NewResource("mybucket")), true},
{NewResourceSet(NewResource("*/*")), false},
{NewResourceSet(NewResource("mybucket/*")), false},
{NewResourceSet(NewResource("mybucket*/myobject")), false},
{NewResourceSet(NewResource("mybucket?0/2010/photos/*")), false},
}
for i, testCase := range testCases {
result := testCase.resourceSet.BucketResourceExists()
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v", i+1, testCase.expectedResult, result)
}
}
}
func TestResourceSetObjectResourceExists(t *testing.T) {
testCases := []struct {
resourceSet ResourceSet
expectedResult bool
}{
{NewResourceSet(NewResource("*")), true},
{NewResourceSet(NewResource("mybucket*")), true},
{NewResourceSet(NewResource("*/*")), true},
{NewResourceSet(NewResource("mybucket/*")), true},
{NewResourceSet(NewResource("mybucket*/myobject")), true},
{NewResourceSet(NewResource("mybucket?0/2010/photos/*")), true},
{NewResourceSet(NewResource("mybucket"), NewResource("mybucket/2910/photos/*")), true},
{NewResourceSet(NewResource("mybucket")), false},
{NewResourceSet(NewResource("mybucket?0")), false},
}
for i, testCase := range testCases {
result := testCase.resourceSet.ObjectResourceExists()
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v", i+1, testCase.expectedResult, result)
}
}
}
func TestResourceSetAdd(t *testing.T) {
testCases := []struct {
resourceSet ResourceSet
resource Resource
expectedResult ResourceSet
}{
{
NewResourceSet(), NewResource("mybucket/myobject*"),
NewResourceSet(NewResource("mybucket/myobject*")),
},
{
NewResourceSet(NewResource("mybucket/myobject*")),
NewResource("mybucket/yourobject*"),
NewResourceSet(NewResource("mybucket/myobject*"),
NewResource("mybucket/yourobject*")),
},
{
NewResourceSet(NewResource("mybucket/myobject*")),
NewResource("mybucket/myobject*"),
NewResourceSet(NewResource("mybucket/myobject*")),
},
}
for i, testCase := range testCases {
testCase.resourceSet.Add(testCase.resource)
if !reflect.DeepEqual(testCase.resourceSet, testCase.expectedResult) {
t.Fatalf("case %v: expected: %v, got: %v", i+1, testCase.expectedResult, testCase.resourceSet)
}
}
}
func TestResourceSetIntersection(t *testing.T) {
testCases := []struct {
set ResourceSet
setToIntersect ResourceSet
expectedResult ResourceSet
}{
{NewResourceSet(), NewResourceSet(NewResource("mybucket/myobject*")), NewResourceSet()},
{NewResourceSet(NewResource("mybucket/myobject*")), NewResourceSet(), NewResourceSet()},
{
NewResourceSet(NewResource("mybucket/myobject*")),
NewResourceSet(NewResource("mybucket/myobject*"), NewResource("mybucket/yourobject*")),
NewResourceSet(NewResource("mybucket/myobject*")),
},
}
for i, testCase := range testCases {
result := testCase.set.Intersection(testCase.setToIntersect)
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, testCase.set)
}
}
}
func TestResourceSetMarshalJSON(t *testing.T) {
testCases := []struct {
resoruceSet ResourceSet
expectedResult []byte
expectErr bool
}{
{
NewResourceSet(NewResource("mybucket/myobject*")),
[]byte(`["arn:aws:s3:::mybucket/myobject*"]`), false,
},
{
NewResourceSet(NewResource("mybucket/photos/myobject*")),
[]byte(`["arn:aws:s3:::mybucket/photos/myobject*"]`), false,
},
{NewResourceSet(), []byte(`[]`), false}, // Empty resources don't return error, only empty actions do.
}
for i, testCase := range testCases {
result, err := json.Marshal(testCase.resoruceSet)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v", i+1, string(testCase.expectedResult), string(result))
}
}
}
}
func TestResourceSetMatch(t *testing.T) {
testCases := []struct {
resourceSet ResourceSet
resource string
expectedResult bool
}{
{NewResourceSet(NewResource("*")), "mybucket", true},
{NewResourceSet(NewResource("*")), "mybucket/myobject", true},
{NewResourceSet(NewResource("mybucket*")), "mybucket", true},
{NewResourceSet(NewResource("mybucket*")), "mybucket/myobject", true},
{NewResourceSet(NewResource("*/*")), "mybucket/myobject", true},
{NewResourceSet(NewResource("mybucket/*")), "mybucket/myobject", true},
{NewResourceSet(NewResource("mybucket*/myobject")), "mybucket/myobject", true},
{NewResourceSet(NewResource("mybucket*/myobject")), "mybucket100/myobject", true},
{NewResourceSet(NewResource("mybucket?0/2010/photos/*")), "mybucket20/2010/photos/1.jpg", true},
{NewResourceSet(NewResource("mybucket")), "mybucket", true},
{NewResourceSet(NewResource("mybucket?0")), "mybucket30", true},
{NewResourceSet(NewResource("mybucket?0/2010/photos/*"),
NewResource("mybucket/2010/photos/*")), "mybucket/2010/photos/1.jpg", true},
{NewResourceSet(NewResource("*/*")), "mybucket", false},
{NewResourceSet(NewResource("mybucket/*")), "mybucket10/myobject", false},
{NewResourceSet(NewResource("mybucket")), "mybucket/myobject", false},
{NewResourceSet(), "mybucket/myobject", false},
}
for i, testCase := range testCases {
result := testCase.resourceSet.Match(testCase.resource, nil)
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v", i+1, testCase.expectedResult, result)
}
}
}
func TestResourceSetUnmarshalJSON(t *testing.T) {
testCases := []struct {
data []byte
expectedResult ResourceSet
expectErr bool
}{
{
[]byte(`"arn:aws:s3:::mybucket/myobject*"`),
NewResourceSet(NewResource("mybucket/myobject*")), false,
},
{
[]byte(`"arn:aws:s3:::mybucket/photos/myobject*"`),
NewResourceSet(NewResource("mybucket/photos/myobject*")), false,
},
{[]byte(`"arn:aws:s3:::mybucket"`), NewResourceSet(NewResource("mybucket")), false},
{[]byte(`"mybucket/myobject*"`), nil, true},
}
for i, testCase := range testCases {
var result ResourceSet
err := json.Unmarshal(testCase.data, &result)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
if !testCase.expectErr {
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v", i+1, testCase.expectedResult, result)
}
}
}
}
func TestResourceSetS3Validate(t *testing.T) {
testCases := []struct {
resourceSet ResourceSet
expectErr bool
}{
{NewResourceSet(NewResource("mybucket/myobject*")), false},
{NewResourceSet(NewResource("/")), true},
{NewResourceSet(NewResource("mybucket"), NewKMSResource("mykey")), true}, // mismatching types
}
for i, testCase := range testCases {
err := testCase.resourceSet.ValidateS3()
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
}
}
func TestResourceSetKMSValidate(t *testing.T) {
testCases := []struct {
resourceSet ResourceSet
expectErr bool
}{
{NewResourceSet(NewKMSResource("mykey/invalid")), true},
{NewResourceSet(NewKMSResource("/")), true},
{NewResourceSet(NewKMSResource("mykey")), false},
{NewResourceSet(NewKMSResource("mykey"), NewResource("mybucket")), true}, // mismatching types
}
for i, testCase := range testCases {
err := testCase.resourceSet.ValidateKMS()
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
}
}
func TestResourceSetValidateBucket(t *testing.T) {
testCases := []struct {
resourceSet ResourceSet
bucketName string
expectErr bool
}{
{NewResourceSet(NewResource("mybucket/myobject*")), "mybucket", false},
{NewResourceSet(NewResource("/myobject*")), "yourbucket", true},
{NewResourceSet(NewResource("mybucket/myobject*")), "yourbucket", true},
}
for i, testCase := range testCases {
err := testCase.resourceSet.ValidateBucket(testCase.bucketName)
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
}
}
golang-github-minio-pkg-3.0.10/policy/statement.go 0000664 0000000 0000000 00000015071 14655770405 0022065 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
import (
"strings"
"github.com/minio/pkg/v3/policy/condition"
)
// Statement - iam policy statement.
type Statement struct {
SID ID `json:"Sid,omitempty"`
Effect Effect `json:"Effect"`
Actions ActionSet `json:"Action"`
NotActions ActionSet `json:"NotAction,omitempty"`
Resources ResourceSet `json:"Resource,omitempty"`
Conditions condition.Functions `json:"Condition,omitempty"`
}
// IsAllowed - checks given policy args is allowed to continue the Rest API.
func (statement Statement) IsAllowed(args Args) bool {
check := func() bool {
if (!statement.Actions.Match(args.Action) && !statement.Actions.IsEmpty()) ||
statement.NotActions.Match(args.Action) {
return false
}
resource := args.BucketName
if args.ObjectName != "" {
if !strings.HasPrefix(args.ObjectName, "/") {
resource += "/"
}
resource += args.ObjectName
} else {
resource += "/"
}
if statement.isKMS() {
if resource == "/" || len(statement.Resources) == 0 {
// In previous MinIO versions, KMS statements ignored Resources, so if len(statement.Resources) == 0,
// allow backward compatibility by not trying to Match.
// When resource is "/", this allows evaluating KMS statements while explicitly excluding Resource,
// by passing Args with empty BucketName and ObjectName. This is useful when doing a
// two-phase authorization of a request.
return statement.Conditions.Evaluate(args.ConditionValues)
}
}
// For some admin statements, resource match can be ignored.
if !statement.Resources.Match(resource, args.ConditionValues) && !statement.isAdmin() && !statement.isSTS() {
return false
}
return statement.Conditions.Evaluate(args.ConditionValues)
}
return statement.Effect.IsAllowed(check())
}
func (statement Statement) isAdmin() bool {
for action := range statement.Actions {
if AdminAction(action).IsValid() {
return true
}
}
return false
}
func (statement Statement) isSTS() bool {
for action := range statement.Actions {
if STSAction(action).IsValid() {
return true
}
}
return false
}
func (statement Statement) isKMS() bool {
for action := range statement.Actions {
if KMSAction(action).IsValid() {
return true
}
}
return false
}
// isValid - checks whether statement is valid or not.
func (statement Statement) isValid() error {
if !statement.Effect.IsValid() {
return Errorf("invalid Effect %v", statement.Effect)
}
if len(statement.Actions) == 0 && len(statement.NotActions) == 0 {
return Errorf("Action must not be empty")
}
if statement.isAdmin() {
if err := statement.Actions.ValidateAdmin(); err != nil {
return err
}
for action := range statement.Actions {
keys := statement.Conditions.Keys()
keyDiff := keys.Difference(adminActionConditionKeyMap[action])
if !keyDiff.IsEmpty() {
return Errorf("unsupported condition keys '%v' used for action '%v'", keyDiff, action)
}
}
return nil
}
if statement.isSTS() {
if err := statement.Actions.ValidateSTS(); err != nil {
return err
}
for action := range statement.Actions {
keys := statement.Conditions.Keys()
keyDiff := keys.Difference(stsActionConditionKeyMap[action])
if !keyDiff.IsEmpty() {
return Errorf("unsupported condition keys '%v' used for action '%v'", keyDiff, action)
}
}
return nil
}
if statement.isKMS() {
if err := statement.Actions.ValidateKMS(); err != nil {
return err
}
return statement.Resources.ValidateKMS()
}
if !statement.SID.IsValid() {
return Errorf("invalid SID %v", statement.SID)
}
if len(statement.Resources) == 0 {
return Errorf("Resource must not be empty")
}
if err := statement.Resources.ValidateS3(); err != nil {
return err
}
if err := statement.Actions.Validate(); err != nil {
return err
}
for action := range statement.Actions {
if !statement.Resources.ObjectResourceExists() && !statement.Resources.BucketResourceExists() {
return Errorf("unsupported Resource found %v for action %v", statement.Resources, action)
}
keys := statement.Conditions.Keys()
keyDiff := keys.Difference(IAMActionConditionKeyMap.Lookup(action))
if !keyDiff.IsEmpty() {
return Errorf("unsupported condition keys '%v' used for action '%v'", keyDiff, action)
}
}
return nil
}
// Validate - validates Statement is for given bucket or not.
func (statement Statement) Validate() error {
return statement.isValid()
}
// Equals checks if two statements are equal
func (statement Statement) Equals(st Statement) bool {
if statement.Effect != st.Effect {
return false
}
if !statement.Actions.Equals(st.Actions) {
return false
}
if !statement.NotActions.Equals(st.NotActions) {
return false
}
if !statement.Resources.Equals(st.Resources) {
return false
}
if !statement.Conditions.Equals(st.Conditions) {
return false
}
return true
}
// Clone clones Statement structure
func (statement Statement) Clone() Statement {
return Statement{
SID: statement.SID,
Effect: statement.Effect,
Actions: statement.Actions.Clone(),
NotActions: statement.NotActions.Clone(),
Resources: statement.Resources.Clone(),
Conditions: statement.Conditions.Clone(),
}
}
// NewStatement - creates new statement.
func NewStatement(sid ID, effect Effect, actionSet ActionSet, resourceSet ResourceSet, conditions condition.Functions) Statement {
return Statement{
SID: sid,
Effect: effect,
Actions: actionSet,
Resources: resourceSet,
Conditions: conditions,
}
}
// NewStatementWithNotAction - creates new statement with NotAction.
func NewStatementWithNotAction(sid ID, effect Effect, notActions ActionSet, resources ResourceSet, conditions condition.Functions) Statement {
return Statement{
SID: sid,
Effect: effect,
NotActions: notActions,
Resources: resources,
Conditions: conditions,
}
}
golang-github-minio-pkg-3.0.10/policy/statement_test.go 0000664 0000000 0000000 00000033403 14655770405 0023123 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
import (
"encoding/json"
"net"
"reflect"
"testing"
"github.com/minio/pkg/v3/policy/condition"
)
func TestStatementIsAllowed(t *testing.T) {
case1Statement := NewStatement("",
Allow,
NewActionSet(GetBucketLocationAction, PutObjectAction),
NewResourceSet(NewResource("*")),
condition.NewFunctions(),
)
case2Statement := NewStatement("",
Allow,
NewActionSet(GetObjectAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
)
_, IPNet1, err := net.ParseCIDR("192.168.1.0/24")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func1, err := condition.NewIPAddressFunc(
condition.AWSSourceIP.ToKey(),
IPNet1,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case3Statement := NewStatement("",
Allow,
NewActionSet(GetObjectAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1),
)
case4Statement := NewStatement("",
Deny,
NewActionSet(GetObjectAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1),
)
case5Statement := NewStatementWithNotAction(
"",
Allow,
NewActionSet(GetObjectAction, CreateBucketAction),
NewResourceSet(NewResource("mybucket/myobject*"), NewResource("mybucket")),
condition.NewFunctions(),
)
case6Statement := NewStatementWithNotAction(
"",
Deny,
NewActionSet(GetObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1),
)
anonGetBucketLocationArgs := Args{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: GetBucketLocationAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{},
}
anonPutObjectActionArgs := Args{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: PutObjectAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{
"x-amz-copy-source": {"mybucket/myobject"},
"SourceIp": {"192.168.1.10"},
},
ObjectName: "myobject",
}
anonGetObjectActionArgs := Args{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: GetObjectAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{},
ObjectName: "myobject",
}
getBucketLocationArgs := Args{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: GetBucketLocationAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{},
}
putObjectActionArgs := Args{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: PutObjectAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{
"x-amz-copy-source": {"mybucket/myobject"},
"SourceIp": {"192.168.1.10"},
},
ObjectName: "myobject",
}
getObjectActionArgs := Args{
AccountName: "Q3AM3UQ867SPQQA43P2F",
Action: GetObjectAction,
BucketName: "mybucket",
ConditionValues: map[string][]string{},
ObjectName: "myobject",
}
testCases := []struct {
statement Statement
args Args
expectedResult bool
}{
{case1Statement, anonGetBucketLocationArgs, true},
{case1Statement, anonPutObjectActionArgs, true},
{case1Statement, anonGetObjectActionArgs, false},
{case1Statement, getBucketLocationArgs, true},
{case1Statement, putObjectActionArgs, true},
{case1Statement, getObjectActionArgs, false},
{case2Statement, anonGetBucketLocationArgs, false},
{case2Statement, anonPutObjectActionArgs, true},
{case2Statement, anonGetObjectActionArgs, true},
{case2Statement, getBucketLocationArgs, false},
{case2Statement, putObjectActionArgs, true},
{case2Statement, getObjectActionArgs, true},
{case3Statement, anonGetBucketLocationArgs, false},
{case3Statement, anonPutObjectActionArgs, true},
{case3Statement, anonGetObjectActionArgs, false},
{case3Statement, getBucketLocationArgs, false},
{case3Statement, putObjectActionArgs, true},
{case3Statement, getObjectActionArgs, false},
{case4Statement, anonGetBucketLocationArgs, true},
{case4Statement, anonPutObjectActionArgs, false},
{case4Statement, anonGetObjectActionArgs, true},
{case4Statement, getBucketLocationArgs, true},
{case4Statement, putObjectActionArgs, false},
{case4Statement, getObjectActionArgs, true},
{case5Statement, anonGetBucketLocationArgs, true},
{case5Statement, anonPutObjectActionArgs, true},
{case5Statement, anonGetObjectActionArgs, false},
{case5Statement, getBucketLocationArgs, true},
{case5Statement, getObjectActionArgs, false},
{case5Statement, putObjectActionArgs, true},
{case6Statement, anonGetBucketLocationArgs, true},
{case6Statement, anonPutObjectActionArgs, false},
{case6Statement, anonGetObjectActionArgs, true},
{case6Statement, getBucketLocationArgs, true},
{case6Statement, putObjectActionArgs, false},
{case6Statement, getObjectActionArgs, true},
}
for i, testCase := range testCases {
result := testCase.statement.IsAllowed(testCase.args)
if result != testCase.expectedResult {
t.Fatalf("case %v: expected: %v, got: %v\n", i+1, testCase.expectedResult, result)
}
}
}
func TestStatementIsValid(t *testing.T) {
_, IPNet1, err := net.ParseCIDR("192.168.1.0/24")
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func1, err := condition.NewIPAddressFunc(
condition.AWSSourceIP.ToKey(),
IPNet1,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func2, err := condition.NewStringEqualsFunc(
"",
condition.S3XAmzCopySource.ToKey(),
"mybucket/myobject",
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func3, err := condition.NewStringEqualsFunc(
"",
condition.AWSUserAgent.ToKey(),
"NSPlayer",
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
testCases := []struct {
statement Statement
expectErr bool
}{
// Invalid effect error.
{NewStatement("",
Effect("foo"),
NewActionSet(GetBucketLocationAction, PutObjectAction),
NewResourceSet(NewResource("*")),
condition.NewFunctions(),
), true},
// Empty actions error.
{NewStatement("",
Allow,
NewActionSet(),
NewResourceSet(NewResource("*")),
condition.NewFunctions(),
), true},
// Empty resources error.
{NewStatement("",
Allow,
NewActionSet(GetBucketLocationAction, PutObjectAction),
NewResourceSet(),
condition.NewFunctions(),
), true},
// Unsupported conditions for GetObject
{NewStatement("",
Allow,
NewActionSet(GetObjectAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1, func2),
), true},
{NewStatement("",
Allow,
NewActionSet(GetBucketLocationAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
), false},
{NewStatement("",
Allow,
NewActionSet(GetBucketLocationAction, PutObjectAction),
NewResourceSet(NewResource("mybucket")),
condition.NewFunctions(),
), false},
{NewStatement("",
Deny,
NewActionSet(GetObjectAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1),
), false},
{NewStatement("",
Allow,
NewActionSet(CreateUserAdminAction, DeleteUserAdminAction),
nil,
condition.NewFunctions(func2, func3),
), true},
{NewStatement("",
Allow,
NewActionSet(CreateUserAdminAction, DeleteUserAdminAction),
nil,
condition.NewFunctions(),
), false},
{Statement{
SID: "",
Effect: Allow,
NotActions: NewActionSet(GetObjectAction),
Resources: NewResourceSet(NewResource("mybucket/myobject*")),
Conditions: condition.NewFunctions(),
}, false},
}
for i, testCase := range testCases {
err := testCase.statement.isValid()
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
}
}
func TestStatementUnmarshalJSONAndValidate(t *testing.T) {
case1Data := []byte(`{
"Sid": "SomeId1",
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
}`)
case1Statement := NewStatement("",
Allow,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
)
case1Statement.SID = "SomeId1"
case2Data := []byte(`{
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*",
"Condition": {
"Null": {
"s3:x-amz-copy-source": true
}
}
}`)
func1, err := condition.NewNullFunc(
condition.S3XAmzCopySource.ToKey(),
true,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Statement := NewStatement("",
Allow,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1),
)
case3Data := []byte(`{
"Effect": "Deny",
"Action": [
"s3:PutObject",
"s3:GetObject"
],
"Resource": "arn:aws:s3:::mybucket/myobject*",
"Condition": {
"Null": {
"s3:x-amz-server-side-encryption": "false"
}
}
}`)
func2, err := condition.NewNullFunc(
condition.S3XAmzServerSideEncryption.ToKey(),
false,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case3Statement := NewStatement("",
Deny,
NewActionSet(PutObjectAction, GetObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func2),
)
case4Data := []byte(`{
"Effect": "Allow",
"Action": "s3:PutObjec,
"Resource": "arn:aws:s3:::mybucket/myobject*"
}`)
case5Data := []byte(`{
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*"
}`)
case7Data := []byte(`{
"Effect": "Allow",
"Resource": "arn:aws:s3:::mybucket/myobject*"
}`)
case8Data := []byte(`{
"Effect": "Allow",
"Action": "s3:PutObject"
}`)
case9Data := []byte(`{
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::mybucket/myobject*",
"Condition": {
}
}`)
case10Data := []byte(`{
"Effect": "Deny",
"Action": [
"s3:PutObject",
"s3:GetObject"
],
"Resource": "arn:aws:s3:::mybucket/myobject*",
"Condition": {
"StringEquals": {
"s3:x-amz-copy-source": "yourbucket/myobject*"
}
}
}`)
case11Data := []byte(`{
"Effect": "Deny",
"NotAction": [
"s3:PutObject",
"s3:GetObject"
],
"Resource": "arn:aws:s3:::mybucket/myobject*"
}`)
case11Statement := Statement{
Effect: Deny,
NotActions: NewActionSet(GetObjectAction, PutObjectAction),
Resources: NewResourceSet(NewResource("mybucket/myobject*")),
Conditions: condition.NewFunctions(),
}
testCases := []struct {
data []byte
expectedResult Statement
expectUnmarshalErr bool
expectValidationErr bool
}{
{case1Data, case1Statement, false, false},
{case2Data, case2Statement, false, false},
{case3Data, case3Statement, false, false},
// JSON unmarshaling error.
{case4Data, Statement{}, true, true},
// Invalid effect error.
{case5Data, Statement{}, false, true},
// Empty action error.
{case7Data, Statement{}, false, true},
// Empty resource error.
{case8Data, Statement{}, false, true},
// Empty condition error.
{case9Data, Statement{}, true, false},
// Unsupported condition key error.
{case10Data, Statement{}, false, true},
{case11Data, case11Statement, false, false},
}
for i, testCase := range testCases {
var result Statement
expectErr := (json.Unmarshal(testCase.data, &result) != nil)
if expectErr != testCase.expectUnmarshalErr {
t.Fatalf("case %v: error during unmarshal: expected: %v, got: %v", i+1, testCase.expectUnmarshalErr, expectErr)
}
expectErr = (result.Validate() != nil)
if expectErr != testCase.expectValidationErr {
t.Fatalf("case %v: error during validation: expected: %v, got: %v", i+1, testCase.expectValidationErr, expectErr)
}
if !testCase.expectUnmarshalErr && !testCase.expectValidationErr {
if !reflect.DeepEqual(result, testCase.expectedResult) {
t.Fatalf("case %v: result: expected: %v, got: %v", i+1, testCase.expectedResult, result)
}
}
}
}
func TestStatementValidate(t *testing.T) {
case1Statement := NewStatement("",
Allow,
NewActionSet(PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(),
)
func1, err := condition.NewNullFunc(
condition.S3XAmzCopySource.ToKey(),
true,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
func2, err := condition.NewNullFunc(
condition.S3XAmzServerSideEncryption.ToKey(),
false,
)
if err != nil {
t.Fatalf("unexpected error. %v\n", err)
}
case2Statement := NewStatement("",
Allow,
NewActionSet(GetObjectAction, PutObjectAction),
NewResourceSet(NewResource("mybucket/myobject*")),
condition.NewFunctions(func1, func2),
)
testCases := []struct {
statement Statement
expectErr bool
}{
{case1Statement, false},
{case2Statement, true},
}
for i, testCase := range testCases {
err := testCase.statement.Validate()
expectErr := (err != nil)
if expectErr != testCase.expectErr {
t.Fatalf("case %v: error: expected: %v, got: %v", i+1, testCase.expectErr, expectErr)
}
}
}
golang-github-minio-pkg-3.0.10/policy/sts-action.go 0000664 0000000 0000000 00000003755 14655770405 0022153 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2024 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package policy
import (
"github.com/minio/pkg/v3/policy/condition"
)
// STSAction - STS policy action.
type STSAction string
const (
// AssumeRoleWithWebIdentityAction - STS action for AssumeRoleWithWebIdentity call
AssumeRoleWithWebIdentityAction = "sts:AssumeRoleWithWebIdentity"
// AllSTSActions - select all STS actions
AllSTSActions = "*"
)
// List of all supported sts actions.
var supportedSTSActions = map[STSAction]struct{}{
AssumeRoleWithWebIdentityAction: {},
AllSTSActions: {},
}
// IsValid - checks if action is valid or not.
func (action STSAction) IsValid() bool {
_, ok := supportedSTSActions[action]
return ok
}
func createSTSActionConditionKeyMap() map[Action]condition.KeySet {
allSupportedSTSKeys := []condition.Key{}
for _, keyName := range condition.AllSupportedSTSKeys {
allSupportedSTSKeys = append(allSupportedSTSKeys, keyName.ToKey())
}
return ActionConditionKeyMap{
AllSTSActions: condition.NewKeySet(allSupportedSTSKeys...),
AssumeRoleWithWebIdentityAction: condition.NewKeySet([]condition.Key{condition.STSDurationSeconds.ToKey()}...),
}
}
// stsActionConditionKeyMap - holds mapping of supported condition key for an action.
var stsActionConditionKeyMap = createSTSActionConditionKeyMap()
golang-github-minio-pkg-3.0.10/quick/ 0000775 0000000 0000000 00000000000 14655770405 0017343 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/quick/encoding.go 0000664 0000000 0000000 00000013121 14655770405 0021456 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package quick
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"
"time"
etcd "go.etcd.io/etcd/client/v3"
yaml "gopkg.in/yaml.v3"
)
// ConfigEncoding is a generic interface which
// marshal/unmarshal configuration.
type ConfigEncoding interface {
Unmarshal([]byte, interface{}) error
Marshal(interface{}) ([]byte, error)
}
// YAML encoding implements ConfigEncoding
type yamlEncoding struct{}
func (y yamlEncoding) Unmarshal(b []byte, v interface{}) error {
return yaml.Unmarshal(b, v)
}
func (y yamlEncoding) Marshal(v interface{}) ([]byte, error) {
return yaml.Marshal(v)
}
// JSON encoding implements ConfigEncoding
type jsonEncoding struct{}
func (j jsonEncoding) Unmarshal(b []byte, v interface{}) error {
err := json.Unmarshal(b, v)
if err != nil {
// Try to return a sophisticated json error message if possible
switch jerr := err.(type) {
case *json.SyntaxError:
return fmt.Errorf("Unable to parse JSON schema due to a syntax error at '%s'",
FormatJSONSyntaxError(bytes.NewReader(b), jerr.Offset))
case *json.UnmarshalTypeError:
return fmt.Errorf("Unable to parse JSON, type '%v' cannot be converted into the Go '%v' type",
jerr.Value, jerr.Type)
}
return err
}
return nil
}
func (j jsonEncoding) Marshal(v interface{}) ([]byte, error) {
return json.MarshalIndent(v, "", "\t")
}
// Convert a file extension to the appropriate struct capable
// to marshal/unmarshal data
func ext2EncFormat(fileExtension string) ConfigEncoding {
// Lower the file extension
ext := strings.ToLower(fileExtension)
ext = strings.TrimPrefix(ext, ".")
// Return the appropriate encoder/decoder according
// to the extension
switch ext {
case "yml", "yaml":
// YAML
return yamlEncoding{}
default:
// JSON
return jsonEncoding{}
}
}
// toMarshaller returns the right marshal function according
// to the given file extension
func toMarshaller(ext string) func(interface{}) ([]byte, error) {
return ext2EncFormat(ext).Marshal
}
// toUnmarshaller returns the right marshal function according
// to the given file extension
func toUnmarshaller(ext string) func([]byte, interface{}) error {
return ext2EncFormat(ext).Unmarshal
}
// saveFileConfig marshals with the right encoding format
// according to the filename extension, if no extension is
// provided, json will be selected.
func saveFileConfig(filename string, v interface{}) error {
// Fetch filename's extension
ext := filepath.Ext(filename)
// Marshal data
dataBytes, err := toMarshaller(ext)(v)
if err != nil {
return err
}
if runtime.GOOS == "windows" {
dataBytes = []byte(strings.Replace(string(dataBytes), "\n", "\r\n", -1))
}
// Save data.
return writeFile(filename, dataBytes)
}
func saveFileConfigEtcd(filename string, clnt *etcd.Client, v interface{}) error {
// Fetch filename's extension
ext := filepath.Ext(filename)
// Marshal data
dataBytes, err := toMarshaller(ext)(v)
if err != nil {
return err
}
if runtime.GOOS == "windows" {
dataBytes = []byte(strings.Replace(string(dataBytes), "\n", "\r\n", -1))
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
_, err = clnt.Put(ctx, filename, string(dataBytes))
if err == context.DeadlineExceeded {
return fmt.Errorf("etcd setup is unreachable, please check your endpoints %s", clnt.Endpoints())
} else if err != nil {
return fmt.Errorf("unexpected error %w returned by etcd setup, please check your endpoints %s", err, clnt.Endpoints())
}
return nil
}
func loadFileConfigEtcd(filename string, clnt *etcd.Client, v interface{}) error {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
resp, err := clnt.Get(ctx, filename)
if err != nil {
if err == context.DeadlineExceeded {
return fmt.Errorf("etcd setup is unreachable, please check your endpoints %s", clnt.Endpoints())
}
return fmt.Errorf("unexpected error %w returned by etcd setup, please check your endpoints %s", err, clnt.Endpoints())
}
if resp.Count == 0 {
return os.ErrNotExist
}
for _, ev := range resp.Kvs {
if string(ev.Key) == filename {
fileData := ev.Value
if runtime.GOOS == "windows" {
fileData = bytes.Replace(fileData, []byte("\r\n"), []byte("\n"), -1)
}
// Unmarshal file's content
return toUnmarshaller(filepath.Ext(filename))(fileData, v)
}
}
return os.ErrNotExist
}
// loadFileConfig unmarshals the file's content with the right
// decoder format according to the filename extension. If no
// extension is provided, json will be selected by default.
func loadFileConfig(filename string, v interface{}) error {
fileData, err := ioutil.ReadFile(filename)
if err != nil {
return err
}
if runtime.GOOS == "windows" {
fileData = []byte(strings.Replace(string(fileData), "\r\n", "\n", -1))
}
// Unmarshal file's content
return toUnmarshaller(filepath.Ext(filename))(fileData, v)
}
golang-github-minio-pkg-3.0.10/quick/errorutil.go 0000664 0000000 0000000 00000004117 14655770405 0021724 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package quick
import (
"bufio"
"bytes"
"fmt"
"io"
"github.com/cheggaaa/pb"
)
const errorFmt = "%5d: %s <<<<"
// FormatJSONSyntaxError generates a pretty printed json syntax error since
// golang doesn't provide an easy way to report the location of the error
func FormatJSONSyntaxError(data io.Reader, offset int64) (highlight string) {
var readLine bytes.Buffer
errLine := 1
var readBytes int64
bio := bufio.NewReader(data)
// termWidth is set to a default one to use when we are
// not able to calculate terminal width via OS syscalls
termWidth := 25
// errorShift is the length of the minimum needed place for
// error msg accessories, like <--, etc.. We calculate it
// dynamically to avoid an eventual bug after modifying errorFmt
errorShift := len(fmt.Sprintf(errorFmt, 1, ""))
if width, err := pb.GetTerminalWidth(); err == nil {
termWidth = width
}
for {
b, err := bio.ReadByte()
if err != nil {
break
}
readBytes++
if readBytes > offset {
break
}
if b == '\n' {
readLine.Reset()
errLine++
continue
} else if b == '\t' {
readLine.WriteByte(' ')
} else if b == '\r' {
break
}
readLine.WriteByte(b)
}
lineLen := readLine.Len()
idx := lineLen - termWidth + errorShift
if idx < 0 || idx > lineLen-1 {
idx = 0
}
return fmt.Sprintf(errorFmt, errLine, readLine.String()[idx:])
}
golang-github-minio-pkg-3.0.10/quick/quick.go 0000664 0000000 0000000 00000013644 14655770405 0021016 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package quick
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"reflect"
"sync"
"github.com/fatih/structs"
"github.com/minio/pkg/v3/safe"
etcd "go.etcd.io/etcd/client/v3"
)
// Config - generic config interface functions
type Config interface {
String() string
Version() string
Save(string) error
Load(string) error
Data() interface{}
Diff(Config) ([]structs.Field, error)
DeepDiff(Config) ([]structs.Field, error)
}
// config - implements quick.Config interface
type config struct {
data interface{}
clnt *etcd.Client
lock *sync.RWMutex
}
// Version returns the current config file format version
func (d config) Version() string {
st := structs.New(d.data)
f := st.Field("Version")
return f.Value().(string)
}
// String converts JSON config to printable string
func (d config) String() string {
configBytes, _ := json.MarshalIndent(d.data, "", "\t")
return string(configBytes)
}
// Save writes config data to a file. Data format
// is selected based on file extension or JSON if
// not provided.
func (d config) Save(filename string) error {
d.lock.Lock()
defer d.lock.Unlock()
if d.clnt != nil {
return saveFileConfigEtcd(filename, d.clnt, d.data)
}
// Backup if given file exists
oldData, err := ioutil.ReadFile(filename)
if err != nil {
// Ignore if file does not exist.
if !os.IsNotExist(err) {
return err
}
} else {
// Save read data to the backup file.
backupFilename := filename + ".old"
if err = writeFile(backupFilename, oldData); err != nil {
return err
}
}
// Save data.
return saveFileConfig(filename, d.data)
}
// Load - loads config from file and merge with currently set values
// File content format is guessed from the file name extension, if not
// available, consider that we have JSON.
func (d config) Load(filename string) error {
d.lock.Lock()
defer d.lock.Unlock()
if d.clnt != nil {
return loadFileConfigEtcd(filename, d.clnt, d.data)
}
return loadFileConfig(filename, d.data)
}
// Data - grab internal data map for reading
func (d config) Data() interface{} {
return d.data
}
// Diff - list fields that are in A but not in B
func (d config) Diff(c Config) ([]structs.Field, error) {
var fields []structs.Field
currFields := structs.Fields(d.Data())
newFields := structs.Fields(c.Data())
var found bool
for _, currField := range currFields {
found = false
for _, newField := range newFields {
if reflect.DeepEqual(currField.Name(), newField.Name()) {
found = true
}
}
if !found {
fields = append(fields, *currField)
}
}
return fields, nil
}
// DeepDiff - list fields in A that are missing or not equal to fields in B
func (d config) DeepDiff(c Config) ([]structs.Field, error) {
var fields []structs.Field
currFields := structs.Fields(d.Data())
newFields := structs.Fields(c.Data())
var found bool
for _, currField := range currFields {
found = false
for _, newField := range newFields {
if reflect.DeepEqual(currField.Value(), newField.Value()) {
found = true
}
}
if !found {
fields = append(fields, *currField)
}
}
return fields, nil
}
// CheckData - checks the validity of config data. Data should be of
// type struct and contain a string type field called "Version".
func CheckData(data interface{}) error {
if !structs.IsStruct(data) {
return fmt.Errorf("interface must be struct type")
}
st := structs.New(data)
f, ok := st.FieldOk("Version")
if !ok {
return fmt.Errorf("struct ‘%s’ must have field ‘Version’", st.Name())
}
if f.Kind() != reflect.String {
return fmt.Errorf("‘Version’ field in struct ‘%s’ must be a string type", st.Name())
}
return nil
}
// writeFile writes data to a file named by filename.
// If the file does not exist, writeFile creates it;
// otherwise writeFile truncates it before writing.
func writeFile(filename string, data []byte) error {
safeFile, err := safe.CreateFile(filename)
if err != nil {
return err
}
_, err = safeFile.Write(data)
if err != nil {
return err
}
return safeFile.Close()
}
// GetVersion - extracts the version information.
func GetVersion(filename string, clnt *etcd.Client) (version string, err error) {
var qc Config
qc, err = LoadConfig(filename, clnt, &struct {
Version string
}{})
if err != nil {
return "", err
}
return qc.Version(), nil
}
// LoadConfig - loads json config from filename for the a given struct data
func LoadConfig(filename string, clnt *etcd.Client, data interface{}) (qc Config, err error) {
qc, err = NewConfig(data, clnt)
if err != nil {
return nil, err
}
return qc, qc.Load(filename)
}
// SaveConfig - saves given configuration data into given file as JSON.
func SaveConfig(data interface{}, filename string, clnt *etcd.Client) (err error) {
if err = CheckData(data); err != nil {
return err
}
var qc Config
qc, err = NewConfig(data, clnt)
if err != nil {
return err
}
return qc.Save(filename)
}
// NewConfig loads config from etcd client if provided, otherwise loads from a local filename.
// fails when all else fails.
func NewConfig(data interface{}, clnt *etcd.Client) (cfg Config, err error) {
if err := CheckData(data); err != nil {
return nil, err
}
d := new(config)
d.data = data
d.clnt = clnt
d.lock = new(sync.RWMutex)
return d, nil
}
golang-github-minio-pkg-3.0.10/quick/quick_test.go 0000664 0000000 0000000 00000025761 14655770405 0022060 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package quick
import (
"bytes"
"encoding/json"
"io/ioutil"
"os"
"reflect"
"runtime"
"strings"
"testing"
)
func TestReadVersion(t *testing.T) {
type myStruct struct {
Version string
}
saveMe := myStruct{"1"}
config, err := NewConfig(&saveMe, nil)
if err != nil {
t.Fatal(err)
}
err = config.Save("test.json")
if err != nil {
t.Fatal(err)
}
version, err := GetVersion("test.json", nil)
if err != nil {
t.Fatal(err)
}
if version != "1" {
t.Fatalf("Expected version '1', got '%v'", version)
}
}
func TestReadVersionErr(t *testing.T) {
type myStruct struct {
Version int
}
saveMe := myStruct{1}
_, err := NewConfig(&saveMe, nil)
if err == nil {
t.Fatal("Unexpected should fail in initialization for bad input")
}
err = ioutil.WriteFile("test.json", []byte("{ \"version\":2,"), 0o644)
if err != nil {
t.Fatal(err)
}
_, err = GetVersion("test.json", nil)
if err == nil {
t.Fatal("Unexpected should fail to fetch version")
}
err = ioutil.WriteFile("test.json", []byte("{ \"version\":2 }"), 0o644)
if err != nil {
t.Fatal(err)
}
_, err = GetVersion("test.json", nil)
if err == nil {
t.Fatal("Unexpected should fail to fetch version")
}
}
func TestSaveFailOnDir(t *testing.T) {
defer os.RemoveAll("test-1.json")
err := os.MkdirAll("test-1.json", 0o644)
if err != nil {
t.Fatal(err)
}
type myStruct struct {
Version string
}
saveMe := myStruct{"1"}
config, err := NewConfig(&saveMe, nil)
if err != nil {
t.Fatal(err)
}
err = config.Save("test-1.json")
if err == nil {
t.Fatal("Unexpected should fail to save if test-1.json is a directory")
}
}
func TestCheckData(t *testing.T) {
err := CheckData(nil)
if err == nil {
t.Fatal("Unexpected should fail")
}
type myStructBadNoVersion struct {
User string
Password string
Directories []string
}
saveMeBadNoVersion := myStructBadNoVersion{"guest", "nopassword", []string{"Work", "Documents", "Music"}}
err = CheckData(&saveMeBadNoVersion)
if err == nil {
t.Fatal("Unexpected should fail if Version is not set")
}
type myStructBadVersionInt struct {
Version int
User string
Password string
}
saveMeBadVersionInt := myStructBadVersionInt{1, "guest", "nopassword"}
err = CheckData(&saveMeBadVersionInt)
if err == nil {
t.Fatal("Unexpected should fail if Version is integer")
}
type myStructGood struct {
Version string
User string
Password string
Directories []string
}
saveMeGood := myStructGood{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}}
err = CheckData(&saveMeGood)
if err != nil {
t.Fatal(err)
}
}
func TestLoadFile(t *testing.T) {
type myStruct struct {
Version string
User string
Password string
Directories []string
}
saveMe := myStruct{}
_, err := LoadConfig("test.json", nil, &saveMe)
if err == nil {
t.Fatal(err)
}
file, err := os.Create("test.json")
if err != nil {
t.Fatal(err)
}
if err = file.Close(); err != nil {
t.Fatal(err)
}
_, err = LoadConfig("test.json", nil, &saveMe)
if err == nil {
t.Fatal("Unexpected should fail to load empty JSON")
}
config, err := NewConfig(&saveMe, nil)
if err != nil {
t.Fatal(err)
}
err = config.Load("test-non-exist.json")
if err == nil {
t.Fatal("Unexpected should fail to Load non-existent config")
}
err = config.Load("test.json")
if err == nil {
t.Fatal("Unexpected should fail to load empty JSON")
}
saveMe = myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}}
config, err = NewConfig(&saveMe, nil)
if err != nil {
t.Fatal(err)
}
err = config.Save("test.json")
if err != nil {
t.Fatal(err)
}
saveMe1 := myStruct{}
_, err = LoadConfig("test.json", nil, &saveMe1)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(saveMe1, saveMe) {
t.Fatalf("Expected %v, got %v", saveMe1, saveMe)
}
saveMe2 := myStruct{}
err = json.Unmarshal([]byte(config.String()), &saveMe2)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(saveMe2, saveMe1) {
t.Fatalf("Expected %v, got %v", saveMe2, saveMe1)
}
}
func TestYAMLFormat(t *testing.T) {
testYAML := "test.yaml"
defer os.RemoveAll(testYAML)
type myStruct struct {
Version string
User string
Password string
Directories []string
}
plainYAML := `version: "1"
user: guest
password: nopassword
directories:
- Work
- Documents
- Music
`
if runtime.GOOS == "windows" {
plainYAML = strings.Replace(plainYAML, "\n", "\r\n", -1)
}
saveMe := myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}}
// Save format using
config, err := NewConfig(&saveMe, nil)
if err != nil {
t.Fatal(err)
}
err = config.Save(testYAML)
if err != nil {
t.Fatal(err)
}
// Check if the saved structure in actually an YAML format
b, err := ioutil.ReadFile(testYAML)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal([]byte(plainYAML), b) {
t.Fatalf("Expected %v, got %v", plainYAML, string(b))
}
// Check if the loaded data is the same as the saved one
loadMe := myStruct{}
config, err = NewConfig(&loadMe, nil)
if err != nil {
t.Fatal(err)
}
err = config.Load(testYAML)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(saveMe, loadMe) {
t.Fatalf("Expected %v, got %v", saveMe, loadMe)
}
}
func TestJSONFormat(t *testing.T) {
testJSON := "test.json"
defer os.RemoveAll(testJSON)
type myStruct struct {
Version string
User string
Password string
Directories []string
}
plainJSON := `{
"Version": "1",
"User": "guest",
"Password": "nopassword",
"Directories": [
"Work",
"Documents",
"Music"
]
}`
if runtime.GOOS == "windows" {
plainJSON = strings.Replace(plainJSON, "\n", "\r\n", -1)
}
saveMe := myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}}
// Save format using
config, err := NewConfig(&saveMe, nil)
if err != nil {
t.Fatal(err)
}
err = config.Save(testJSON)
if err != nil {
t.Fatal(err)
}
// Check if the saved structure in actually an JSON format
b, err := ioutil.ReadFile(testJSON)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal([]byte(plainJSON), b) {
t.Fatalf("Expected %v, got %v", plainJSON, string(b))
}
// Check if the loaded data is the same as the saved one
loadMe := myStruct{}
config, err = NewConfig(&loadMe, nil)
if err != nil {
t.Fatal(err)
}
err = config.Load(testJSON)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(saveMe, loadMe) {
t.Fatalf("Expected %v, got %v", saveMe, loadMe)
}
}
func TestSaveLoad(t *testing.T) {
defer os.RemoveAll("test.json")
type myStruct struct {
Version string
User string
Password string
Directories []string
}
saveMe := myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}}
config, err := NewConfig(&saveMe, nil)
if err != nil {
t.Fatal(err)
}
err = config.Save("test.json")
if err != nil {
t.Fatal(err)
}
loadMe := myStruct{Version: "1"}
newConfig, err := NewConfig(&loadMe, nil)
if err != nil {
t.Fatal(err)
}
err = newConfig.Load("test.json")
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(config.Data(), newConfig.Data()) {
t.Fatalf("Expected %v, got %v", config.Data(), newConfig.Data())
}
if !reflect.DeepEqual(config.Data(), &loadMe) {
t.Fatalf("Expected %v, got %v", config.Data(), &loadMe)
}
mismatch := myStruct{"1.1", "guest", "nopassword", []string{"Work", "Documents", "Music"}}
if reflect.DeepEqual(config.Data(), &mismatch) {
t.Fatal("Expected to mismatch but succeeded instead")
}
}
func TestSaveBackup(t *testing.T) {
defer os.RemoveAll("test.json")
defer os.RemoveAll("test.json.old")
type myStruct struct {
Version string
User string
Password string
Directories []string
}
saveMe := myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}}
config, err := NewConfig(&saveMe, nil)
if err != nil {
t.Fatal(err)
}
err = config.Save("test.json")
if err != nil {
t.Fatal(err)
}
loadMe := myStruct{Version: "1"}
newConfig, err := NewConfig(&loadMe, nil)
if err != nil {
t.Fatal(err)
}
err = newConfig.Load("test.json")
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(config.Data(), newConfig.Data()) {
t.Fatalf("Expected %v, got %v", config.Data(), newConfig.Data())
}
if !reflect.DeepEqual(config.Data(), &loadMe) {
t.Fatalf("Expected %v, got %v", config.Data(), &loadMe)
}
mismatch := myStruct{"1.1", "guest", "nopassword", []string{"Work", "Documents", "Music"}}
if reflect.DeepEqual(newConfig.Data(), &mismatch) {
t.Fatal("Expected to mismatch but succeeded instead")
}
config, err = NewConfig(&mismatch, nil)
if err != nil {
t.Fatal(err)
}
err = config.Save("test.json")
if err != nil {
t.Fatal(err)
}
}
func TestDiff(t *testing.T) {
type myStruct struct {
Version string
User string
Password string
Directories []string
}
saveMe := myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}}
config, err := NewConfig(&saveMe, nil)
if err != nil {
t.Fatal(err)
}
type myNewConfigStruct struct {
Version string
// User string
Password string
Directories []string
}
mismatch := myNewConfigStruct{"1", "nopassword", []string{"Work", "documents", "Music"}}
newConfig, err := NewConfig(&mismatch, nil)
if err != nil {
t.Fatal(err)
}
fields, err := config.Diff(newConfig)
if err != nil {
t.Fatal(err)
}
if len(fields) != 1 {
t.Fatalf("Expected len 1, got %v", len(fields))
}
// Uncomment for debugging
// for i, field := range fields {
// fmt.Printf("Diff[%d]: %s=%v\n", i, field.Name(), field.Value())
// }
}
func TestDeepDiff(t *testing.T) {
type myStruct struct {
Version string
User string
Password string
Directories []string
}
saveMe := myStruct{"1", "guest", "nopassword", []string{"Work", "Documents", "Music"}}
config, err := NewConfig(&saveMe, nil)
if err != nil {
t.Fatal(err)
}
mismatch := myStruct{"1", "Guest", "nopassword", []string{"Work", "documents", "Music"}}
newConfig, err := NewConfig(&mismatch, nil)
if err != nil {
t.Fatal(err)
}
fields, err := config.DeepDiff(newConfig)
if err != nil {
t.Fatal(err)
}
if len(fields) != 2 {
t.Fatalf("Expected len 2, got %v", len(fields))
}
// Uncomment for debugging
// for i, field := range fields {
// fmt.Printf("DeepDiff[%d]: %s=%v\n", i, field.Name(), field.Value())
// }
}
golang-github-minio-pkg-3.0.10/randreader/ 0000775 0000000 0000000 00000000000 14655770405 0020336 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/randreader/randreader.go 0000664 0000000 0000000 00000003011 14655770405 0022767 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package randreader
import (
"io"
"math/rand"
"time"
)
const bufferSize = 16 << 10
// New returns an infinite reader that will return pseudo-random data.
// Data should not be used for cryptographic functions.
// A random time based seed is used.
func New() io.Reader {
return NewSource(rand.NewSource(time.Now().UnixNano()))
}
// NewSource returns an infinite reader that will return pseudo-random data.
// Data should not be used for cryptographic functions.
// The data is seeded from the provided source.
func NewSource(src rand.Source) io.Reader {
rng := rand.New(src)
rngFull := func(data []byte) {
_, err := io.ReadFull(rng, data)
if err != nil {
panic(err)
}
}
data := make([]byte, bufferSize)
rngFull(data)
return newXorBuffer(data, rand.New(src))
}
golang-github-minio-pkg-3.0.10/randreader/rangreader_test.go 0000664 0000000 0000000 00000002742 14655770405 0024043 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package randreader
import (
"io"
"math/rand"
"testing"
)
func BenchmarkReader(b *testing.B) {
const size = 100000
buf := make([]byte, size)
src := New()
b.SetBytes(size)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
n, err := io.ReadFull(src, buf)
if err != nil {
b.Fatal(err)
}
if n != size {
b.Fatalf("want read size %d, got %d", size, n)
}
}
}
func BenchmarkMathRand(b *testing.B) {
const size = 100000
buf := make([]byte, size)
src := rand.New(rand.NewSource(0))
b.SetBytes(size)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
n, err := io.ReadFull(src, buf)
if err != nil {
b.Fatal(err)
}
if n != size {
b.Fatalf("want read size %d, got %d", size, n)
}
}
}
golang-github-minio-pkg-3.0.10/randreader/xor.go 0000664 0000000 0000000 00000003032 14655770405 0021473 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package randreader
import (
"errors"
"math/rand"
)
type xorBuffer struct {
data []byte
// left aliases the data at the current read position.
left []byte
tmp [16]byte
rng *rand.Rand
}
func newXorBuffer(data []byte, rng *rand.Rand) *xorBuffer {
return &xorBuffer{
data: data,
left: data,
rng: rng,
}
}
func (c *xorBuffer) Read(p []byte) (n int, err error) {
if len(c.data) == 0 {
return 0, errors.New("circularBuffer: no data")
}
for len(p) > 0 {
if len(c.left) == 0 {
// Read 16 random bytes for xor
c.rng.Read(c.tmp[:])
xorSlice(c.tmp[:], c.data)
c.left = c.data
}
// Make sure we don't overread.
toDo := c.left
copied := copy(p, toDo)
// Assign remaining back to c.left
c.left = toDo[copied:]
p = p[copied:]
n += copied
}
return n, nil
}
golang-github-minio-pkg-3.0.10/randreader/xor_amd64.go 0000664 0000000 0000000 00000001613 14655770405 0022471 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
//go:build !noasm && !appengine && !gccgo
// +build !noasm,!appengine,!gccgo
package randreader
//go:noescape
func xorSlice(rand, out []byte)
golang-github-minio-pkg-3.0.10/randreader/xor_amd64.s 0000664 0000000 0000000 00000002744 14655770405 0022334 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
//+build !noasm
//+build !appengine
//+build !gccgo
// func xorSlice(rand, out []byte)
TEXT ·xorSlice(SB), 7, $0
MOVQ rand+0(FP), SI // SI: &rand
MOVQ out+24(FP), DX // DX: &out
MOVQ out+32(FP), R9 // R9: len(out)
MOVOU (SI), X0 // in[x]
SHRQ $6, R9 // len(in) / 64
CMPQ R9, $0
JEQ done_xor_sse2_64
loopback_xor_sse2_64:
MOVOU (DX), X1 // out[x]
MOVOU 16(DX), X3 // out[x]
MOVOU 32(DX), X5 // out[x]
MOVOU 48(DX), X7 // out[x]
PXOR X0, X1
PXOR X0, X3
PXOR X0, X5
PXOR X0, X7
MOVOU X1, (DX)
MOVOU X3, 16(DX)
MOVOU X5, 32(DX)
MOVOU X7, 48(DX)
ADDQ $64, DX // out+=64
SUBQ $1, R9
JNZ loopback_xor_sse2_64
done_xor_sse2_64:
RET
golang-github-minio-pkg-3.0.10/randreader/xor_noasm.go 0000664 0000000 0000000 00000002657 14655770405 0022704 0 ustar 00root root 0000000 0000000 //go:build !amd64 || noasm || appengine || gccgo
// +build !amd64 noasm appengine gccgo
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package randreader
import "encoding/binary"
func xorSlice(rand, out []byte) {
var a0, a1 uint64
a0 = binary.LittleEndian.Uint64(rand)
a1 = binary.LittleEndian.Uint64(rand[8:])
for len(out) >= 32 {
v0 := binary.LittleEndian.Uint64(out[:]) ^ a0
v1 := binary.LittleEndian.Uint64(out[8:]) ^ a1
v2 := binary.LittleEndian.Uint64(out[16:]) ^ a0
v3 := binary.LittleEndian.Uint64(out[24:]) ^ a1
binary.LittleEndian.PutUint64(out[:], v0)
binary.LittleEndian.PutUint64(out[8:], v1)
binary.LittleEndian.PutUint64(out[16:], v2)
binary.LittleEndian.PutUint64(out[24:], v3)
out = out[32:]
}
}
golang-github-minio-pkg-3.0.10/safe/ 0000775 0000000 0000000 00000000000 14655770405 0017145 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/safe/safe.go 0000664 0000000 0000000 00000006643 14655770405 0020423 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package safe
import (
"errors"
"io/ioutil"
"os"
"path/filepath"
)
// File represents safe file descriptor.
type File struct {
name string
tmpfile *os.File
closed bool
aborted bool
}
// Write writes len(b) bytes to the temporary File. In case of error, the temporary file is removed.
func (file *File) Write(b []byte) (n int, err error) {
if file.closed {
err = errors.New("write on closed file")
return
}
if file.aborted {
err = errors.New("write on aborted file")
return
}
defer func() {
if err != nil {
os.Remove(file.tmpfile.Name())
file.aborted = true
}
}()
n, err = file.tmpfile.Write(b)
return
}
// Close closes the temporary File and renames to the named file. In case of error, the temporary file is removed.
func (file *File) Close() (err error) {
defer func() {
if err != nil {
os.Remove(file.tmpfile.Name())
file.aborted = true
}
}()
if file.closed {
err = errors.New("close on closed file")
return
}
if file.aborted {
err = errors.New("close on aborted file")
return
}
if err = file.tmpfile.Close(); err != nil {
return
}
err = os.Rename(file.tmpfile.Name(), file.name)
file.closed = true
return
}
// Abort aborts the temporary File by closing and removing the temporary file.
func (file *File) Abort() (err error) {
if file.closed {
err = errors.New("abort on closed file")
return
}
if file.aborted {
err = errors.New("abort on aborted file")
return
}
file.tmpfile.Close()
err = os.Remove(file.tmpfile.Name())
file.aborted = true
return
}
// CreateFile creates the named file safely from unique temporary file.
// The temporary file is renamed to the named file upon successful close
// to safeguard intermediate state in the named file. The temporary file
// is created in the name of the named file with suffixed unique number
// and prefixed "$tmpfile" string. While creating the temporary file,
// missing parent directories are also created. The temporary file is
// removed if case of any intermediate failure. Not removed temporary
// files can be cleaned up by identifying them using "$tmpfile" prefix
// string.
func CreateFile(name string) (*File, error) {
// ioutil.TempFile() fails if parent directory is missing.
// Create parent directory to avoid such error.
dname := filepath.Dir(name)
if err := os.MkdirAll(dname, 0o700); err != nil {
return nil, err
}
fname := filepath.Base(name)
tmpfile, err := ioutil.TempFile(dname, "$tmpfile."+fname+".")
if err != nil {
return nil, err
}
if err = os.Chmod(tmpfile.Name(), 0o600); err != nil {
if rerr := os.Remove(tmpfile.Name()); rerr != nil {
err = rerr
}
return nil, err
}
return &File{name: name, tmpfile: tmpfile}, nil
}
golang-github-minio-pkg-3.0.10/safe/safe_test.go 0000664 0000000 0000000 00000007063 14655770405 0021457 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package safe
import (
"io/ioutil"
"os"
"path"
"testing"
)
type MySuite struct {
root string
}
func (s *MySuite) SetUpSuite(t *testing.T) {
root, err := ioutil.TempDir(os.TempDir(), "safe_test.go.")
if err != nil {
t.Fatal(err)
}
s.root = root
}
func (s *MySuite) TearDownSuite(t *testing.T) {
err := os.RemoveAll(s.root)
if err != nil {
t.Fatal(err)
}
}
func TestSafeAbort(t *testing.T) {
s := &MySuite{}
s.SetUpSuite(t)
defer s.TearDownSuite(t)
f, err := CreateFile(path.Join(s.root, "testfile-abort"))
if err != nil {
t.Fatal(err)
}
_, err = os.Stat(path.Join(s.root, "testfile-abort"))
if !os.IsNotExist(err) {
t.Fatal(err)
}
err = f.Abort()
if err != nil {
t.Fatal(err)
}
err = f.Close()
if err != nil {
if err.Error() != "close on aborted file" {
t.Fatal(err)
}
}
}
func TestSafeClose(t *testing.T) {
s := &MySuite{}
s.SetUpSuite(t)
defer s.TearDownSuite(t)
f, err := CreateFile(path.Join(s.root, "testfile-close"))
if err != nil {
t.Fatal(err)
}
_, err = os.Stat(path.Join(s.root, "testfile-close"))
if !os.IsNotExist(err) {
t.Fatal(err)
}
err = f.Close()
if err != nil {
t.Fatal(err)
}
_, err = os.Stat(path.Join(s.root, "testfile-close"))
if err != nil {
t.Fatal(err)
}
err = os.Remove(path.Join(s.root, "testfile-close"))
if err != nil {
t.Fatal(err)
}
err = f.Abort()
if err != nil {
if err.Error() != "abort on closed file" {
t.Fatal(err)
}
}
}
func TestSafe(t *testing.T) {
s := &MySuite{}
s.SetUpSuite(t)
defer s.TearDownSuite(t)
f, err := CreateFile(path.Join(s.root, "testfile-safe"))
if err != nil {
t.Fatal(err)
}
_, err = os.Stat(path.Join(s.root, "testfile-safe"))
if !os.IsNotExist(err) {
t.Fatal(err)
}
err = f.Close()
if err != nil {
t.Fatal(err)
}
_, err = f.Write([]byte("Test"))
if err != nil {
if err.Error() != "write on closed file" {
t.Fatal(err)
}
}
err = f.Close()
if err != nil {
if err.Error() != "close on closed file" {
t.Fatal(err)
}
}
_, err = os.Stat(path.Join(s.root, "testfile-safe"))
if err != nil {
t.Fatal(err)
}
err = os.Remove(path.Join(s.root, "testfile-safe"))
if err != nil {
t.Fatal(err)
}
}
func TestSafeAbortWrite(t *testing.T) {
s := &MySuite{}
s.SetUpSuite(t)
defer s.TearDownSuite(t)
f, err := CreateFile(path.Join(s.root, "purgefile-abort"))
if err != nil {
t.Fatal(err)
}
_, err = os.Stat(path.Join(s.root, "purgefile-abort"))
if !os.IsNotExist(err) {
t.Fatal(err)
}
err = f.Abort()
if err != nil {
t.Fatal(err)
}
_, err = os.Stat(path.Join(s.root, "purgefile-abort"))
if !os.IsNotExist(err) {
t.Fatal(err)
}
err = f.Abort()
if err != nil {
if err.Error() != "abort on aborted file" {
t.Fatal(err)
}
}
_, err = f.Write([]byte("Test"))
if err != nil {
if err.Error() != "write on aborted file" {
t.Fatal(err)
}
}
}
golang-github-minio-pkg-3.0.10/sftp/ 0000775 0000000 0000000 00000000000 14655770405 0017203 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/sftp/sftp.go 0000664 0000000 0000000 00000020340 14655770405 0020505 0 ustar 00root root 0000000 0000000 // Copyright (c) 2022-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package sftp
import (
"context"
"errors"
"fmt"
"net"
"strconv"
"time"
"golang.org/x/crypto/ssh"
)
type (
// LogType defined various types of logs and errors
// that can happen within the SFTP implementation
LogType string
)
var (
// ErrMissingConnectionHandlerFunction ...
ErrMissingConnectionHandlerFunction = errors.New("new connection handler is not defined")
// ErrMissingSSHConfig ...
ErrMissingSSHConfig = errors.New("ssh Config is not defined")
// ErrMissingLoggerInterface ...
ErrMissingLoggerInterface = errors.New("logger interface is not defined")
// ErrInvalidPort ...
ErrInvalidPort = errors.New("port must not be 0 or bigger then 65535")
)
const (
// ServerStarted is logged when the SFTP server is first launched.
ServerStarted LogType = "server-started"
// ChannelNotSession is logged when the SFTP receives a request for a new channel which is NOT of type 'session'.
ChannelNotSession LogType = "channel-not-session"
// AcceptNetworkError is logged when there is an error accepting network connections within the listener.
AcceptNetworkError LogType = "accept-network-error"
// SSHKeyExchangeError is logged when there is an error performing a key exchange between the SFTP client and server.
SSHKeyExchangeError LogType = "ssh-key-exchange-error"
// AcceptChannelError is logged when there is an error while trying to accept the new request channel.
AcceptChannelError LogType = "accept-channel-error"
)
// Logger implements a basic logging interface
// for the SFTP server.
type Logger interface {
Info(tag LogType, msg string)
Error(tag LogType, err error)
}
// Server implements a composable SFTP Server.
type Server struct {
quit chan struct{}
port int
publicIP string
sshConfig ssh.ServerConfig
sshHandshakeDeadline time.Duration
logger Logger
beforeHandle func(conn net.Conn, err error) (acceptConn bool)
handleSFTPSession func(channel ssh.Channel, sconn *ssh.ServerConn)
listener net.Listener
}
// ShutDown calls the cancel context and shuts
// down the SFTP server.
func (s *Server) ShutDown() (err error) {
close(s.quit)
err = s.listener.Close()
return
}
// Options defines required configurations
// used when calling NewServer().
type Options struct {
Port int
PublicIP string
Logger Logger
SSHConfig *ssh.ServerConfig
// ConnectionKeepAlive controls how long the connection keep-alive duration is set to.
ConnectionKeepAlive time.Duration
// SSHHandshakeDeadline controls the time.Duration which ssh session
// have to complete their handshake. This option is not a part of the
// ssh.ServerConfig so we had to implement it separately.
SSHHandshakeDeadline time.Duration
// BeforeHandle will be executed before `HandleSFTPSession` and before
// error checking happens during the socket listener.Accept().
//
// if acceptConn is true the connection will be accepted, if not
// the .Close() method is called and the connection dropped.
BeforeHandle func(conn net.Conn, err error) (acceptConn bool)
// HandleSFTPSession is executed when a new SFTP session is requested.
HandleSFTPSession func(channel ssh.Channel, sconn *ssh.ServerConn)
}
// NewServer composes a new Server{} object from the options given.
//
// It is recommended to use (2*time.Minute) as the SSHHandshakeDeadline.
// 2 minutes is the default deadline for OpenSSH servers/clients.
func NewServer(options *Options) (sftpServer *Server, err error) {
if options.HandleSFTPSession == nil {
return nil, ErrMissingConnectionHandlerFunction
}
if options.SSHConfig == nil {
return nil, ErrMissingSSHConfig
}
if options.Logger == nil {
return nil, ErrMissingLoggerInterface
}
if options.Port < 1 || options.Port > 65535 {
return nil, ErrInvalidPort
}
// It is recommended to use (2*time.Minute) as the SSHHandshakeDeadline.
// 2 minutes is the default deadline for OpenSSH servers/clients.
if options.SSHHandshakeDeadline == 0 {
options.SSHHandshakeDeadline = time.Minute * 2
}
lc := new(net.ListenConfig)
if options.ConnectionKeepAlive != 0 {
lc.KeepAlive = options.ConnectionKeepAlive
}
sftpServer = new(Server)
// net.Listener does not respect the context cancelFunc.
// Hence we just pass it a normal context.Background()
sftpServer.listener, err = lc.Listen(
context.Background(),
"tcp",
net.JoinHostPort(options.PublicIP, strconv.Itoa(options.Port)),
)
if err != nil {
return
}
sftpServer.publicIP = options.PublicIP
sftpServer.port = options.Port
sftpServer.sshConfig = *options.SSHConfig
sftpServer.sshHandshakeDeadline = options.SSHHandshakeDeadline
sftpServer.beforeHandle = options.BeforeHandle
sftpServer.handleSFTPSession = options.HandleSFTPSession
sftpServer.logger = options.Logger
sftpServer.quit = make(chan struct{})
return
}
// Listen starts the SFTP server
func (s *Server) Listen() (err error) {
s.logger.Info(ServerStarted,
"SFTP Server listening on "+
net.JoinHostPort(s.publicIP, strconv.Itoa(s.port)),
)
for {
conn, err := s.listener.Accept()
if s.beforeHandle != nil && !s.beforeHandle(conn, err) {
if conn != nil {
conn.Close()
}
continue
}
if err != nil {
select {
case <-s.quit:
return nil
default:
}
// Temporary() is deprecated but since it's been deployed to
// current production builds I do not want to simply switch it out.
// ISSUE: https://github.com/golang/go/issues/45729
// According to golang updates the Temporary() functionality was
// not changed but the method simply marked deprecated, hence
// it is alright to keep it implemented for consistency.
// UPDATE: https://go-review.googlesource.com/c/go/+/340261
ne, ok := err.(net.Error)
if ok && (ne.Timeout() || ne.Temporary()) {
s.logger.Error(
AcceptNetworkError,
fmt.Errorf("error accepting connections: %w", err),
)
continue
}
return err
}
go s.handleConnection(conn)
}
}
func (s *Server) handleConnection(conn net.Conn) {
// Before use, a handshake must be performed on the incoming net.Conn.
conn.SetDeadline(time.Now().Add(s.sshHandshakeDeadline))
sconn, chans, reqs, err := ssh.NewServerConn(conn, &s.sshConfig)
if err != nil {
s.logger.Error(SSHKeyExchangeError, err)
return
}
// Once we are done with SSH handshake, remove deadline.
conn.SetDeadline(time.Time{})
// The incoming Request channel must be serviced.
go ssh.DiscardRequests(reqs)
// Service the incoming Channel channel.
for newChannel := range chans {
// Channels have a type, depending on the application level
// protocol intended. In the case of an SFTP session, this is "subsystem"
// with a payload string of "sftp"
if newChannel.ChannelType() != "session" {
s.logger.Info(
ChannelNotSession,
"Channel type is not a session",
)
continue
}
channel, requests, err := newChannel.Accept()
if err != nil {
s.logger.Error(
AcceptChannelError,
fmt.Errorf("unable to accept request from channel: %w", err),
)
continue
}
// Sessions have out-of-band requests such as "shell",
// "pty-req" and "env". Here we handle only the
// "subsystem" request.
go func(in <-chan *ssh.Request) {
for req := range in {
ok := false
if req.Type == "subsystem" {
if len(req.Payload) > 4 && string(req.Payload[4:]) == "sftp" {
ok = true
go s.handleSFTPSession(channel, sconn)
}
}
if req.WantReply {
// We only reply to SSH packets that have `sftp` payload, all other
// packets are rejected
req.Reply(ok, nil)
}
}
}(requests)
}
}
golang-github-minio-pkg-3.0.10/subnet/ 0000775 0000000 0000000 00000000000 14655770405 0017527 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/subnet/license.go 0000664 0000000 0000000 00000017462 14655770405 0021512 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package subnet
import (
"bytes"
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"os"
"strings"
"time"
"github.com/lestrrat-go/jwx/jwt"
"github.com/minio/pkg/v3/licverifier"
)
const (
publicKeyPath = "/downloads/license-pubkey.pem"
// https://subnet.min.io/downloads/license-pubkey.pem
publicKeyProd = `-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEaK31xujr6/rZ7ZfXZh3SlwovjC+X8wGq
qkltaKyTLRENd4w3IRktYYCRgzpDLPn/nrf7snV/ERO5qcI7fkEES34IVEr+2Uff
JkO2PfyyAYEO/5dBlPh1Undu9WQl6J7B
-----END PUBLIC KEY-----`
// https://localhost:9000/downloads/license-pubkey.pem
publicKeyDev = `-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEbo+e1wpBY4tBq9AONKww3Kq7m6QP/TBQ
mr/cKCUyBL7rcAvg0zNq1vcSrUSGlAmY3SEDCu3GOKnjG/U4E7+p957ocWSV+mQU
9NKlTdQFGF3+aO6jbQ4hX/S5qPyF+a3z
-----END PUBLIC KEY-----`
)
// LicenseValidator validates the MinIO license.
type LicenseValidator struct {
Client http.Client
LicenseFilePath string
pubKeyURL string
offlinePubKey []byte
LicenseToken string
ExpiryGracePeriod time.Duration
}
// LicenseValidatorParams holds parameters for creating a new LicenseValidator.
type LicenseValidatorParams struct {
TLSClientConfig *tls.Config
LicenseFilePath string
LicenseToken string
ExpiryGracePeriod time.Duration
DevMode bool
}
// BaseURL returns the base URL for subnet.
func BaseURL(devMode bool) string {
if devMode {
subnetURLDev := os.Getenv("SUBNET_URL_DEV")
if len(subnetURLDev) > 0 {
return subnetURLDev
}
return "http://localhost:9000"
}
return "https://subnet.min.io"
}
// NewLicenseValidator returns a new LicenseValidator using the provided tls client Config,
// and license file path. If the path is empty, it will look for minio.license in the
// current working directory. If `devMode` is true, the validator will connect to locally
// running SUBNET instance to download the public key or use the bundled dev key.
func NewLicenseValidator(params LicenseValidatorParams) (*LicenseValidator, error) {
licPath := params.LicenseFilePath
licToken := params.LicenseToken
if licToken == "" {
licToken = os.Getenv("MINIO_LICENSE")
}
if licPath == "" && licToken == "" {
// if license file path is not provided, and also
// not set in env variable, expect it to be present
// in the current working directory
pwd, err := os.Getwd()
if err != nil {
return nil, err
}
licPath = pwd + "/minio.license"
}
client := http.Client{
Timeout: 0,
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 10 * time.Second,
}).DialContext,
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: params.TLSClientConfig,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 10 * time.Second,
},
}
lv := LicenseValidator{
Client: client,
LicenseFilePath: licPath,
LicenseToken: licToken,
ExpiryGracePeriod: params.ExpiryGracePeriod,
}
lv.Init(params.DevMode)
return &lv, nil
}
// Init initializes the LicenseValidator.
func (lv *LicenseValidator) Init(devMode bool) {
lv.pubKeyURL = fmt.Sprintf("%s%s", BaseURL(devMode), publicKeyPath)
lv.offlinePubKey = []byte(publicKeyProd)
if devMode {
lv.offlinePubKey = []byte(publicKeyDev)
}
}
// downloadSubnetPublicKey will download the current subnet public key.
func (lv *LicenseValidator) downloadSubnetPublicKey() ([]byte, error) {
resp, err := lv.Client.Get(lv.pubKeyURL)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to download public key from %s. response [%d:%s]", lv.pubKeyURL, resp.StatusCode, resp.Status)
}
defer resp.Body.Close()
buf := new(bytes.Buffer)
_, err = buf.ReadFrom(resp.Body)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// ParseLicense parses the license with the public key and return it's information.
// Public key is downloaded from subnet. If there is an error downloading the public key
// it will use the bundled public key instead.
func (lv *LicenseValidator) ParseLicense(license string) (*licverifier.LicenseInfo, error) {
publicKey, e := lv.downloadSubnetPublicKey()
if e != nil {
// there was an issue getting the subnet public key
// use hardcoded public keys instead
publicKey = lv.offlinePubKey
}
lvr, e := licverifier.NewLicenseVerifier(publicKey)
if e != nil {
return nil, e
}
li, e := lvr.Verify(license, jwt.WithAcceptableSkew(lv.ExpiryGracePeriod))
return &li, e
}
// ValidateLicense validates the license file.
func (lv *LicenseValidator) ValidateLicense() (*licverifier.LicenseInfo, error) {
if lv.LicenseToken == "" && lv.LicenseFilePath == "" {
return nil, fmt.Errorf("MinIO license not found")
}
if lv.LicenseToken == "" {
licData, err := os.ReadFile(lv.LicenseFilePath)
if err != nil {
return nil, err
}
lv.LicenseToken = strings.TrimSpace(string(licData))
}
return lv.ParseLicense(lv.LicenseToken)
}
func getDurationForNextLicenseCheck(li *licverifier.LicenseInfo) time.Duration {
if li.ExpiresAt.Before(time.Now()) {
// expired, within grace period. schedule daily
return time.Hour * 24
}
// not expired, schedule to check just after expiry
return time.Until(li.ExpiresAt.Add(time.Second))
}
func (lv *LicenseValidator) scheduleNextLicenseCheck(li *licverifier.LicenseInfo, acceptedPlans []string, licExpiredChan chan<- string) {
duration := getDurationForNextLicenseCheck(li)
timer := time.NewTimer(duration)
defer timer.Stop()
ctxt := context.Background()
for {
select {
case <-timer.C:
li, err := lv.ValidateEnterpriseLicense(acceptedPlans, licExpiredChan)
if err != nil {
licExpiredChan <- err.Error()
return
}
timer.Reset(getDurationForNextLicenseCheck(li))
case <-ctxt.Done():
return
}
}
}
// ValidateEnterpriseLicense validates the ENTERPRISE license file.
// Since there are multiple variants of ENTERPRISE licenses, ones
// accepted by the application can be passed as `acceptedPlans`.
// TRIAL licenses do not get grace period after expiry.
func (lv *LicenseValidator) ValidateEnterpriseLicense(acceptedPlans []string, licExpiredChan chan<- string) (*licverifier.LicenseInfo, error) {
li, err := lv.ValidateLicense()
if err != nil {
return nil, err
}
accepted := false
for _, plan := range acceptedPlans {
if plan == li.Plan {
accepted = true
break
}
}
if !accepted {
return nil, fmt.Errorf("this software is only available for license plans %v", strings.Join(acceptedPlans, ", "))
}
if li.ExpiresAt.Before(time.Now()) {
if li.IsTrial || li.Plan == "TRIAL" {
// no grace period for trial
return nil, fmt.Errorf("trial license has expired on %v", li.ExpiresAt)
}
}
// validation successful. start a background routine to validate the license
// - daily if already expired (within grace period)
// - just after expiry if not expired
if licExpiredChan != nil {
// if the expiry channel is nil, it means client doesn't want to be notified
// when license expires. In that case we don't schedule the background check.
go lv.scheduleNextLicenseCheck(li, acceptedPlans, licExpiredChan)
}
return li, nil
}
golang-github-minio-pkg-3.0.10/sync/ 0000775 0000000 0000000 00000000000 14655770405 0017203 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/sync/errgroup/ 0000775 0000000 0000000 00000000000 14655770405 0021050 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/sync/errgroup/errgroup.go 0000664 0000000 0000000 00000007355 14655770405 0023256 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package errgroup
import (
"context"
"sync"
"sync/atomic"
)
// A Group is a collection of goroutines working on subtasks that are part of
// the same overall task.
//
// A zero Group can be used if errors should not be tracked.
type Group struct {
firstErr int64 // ref: https://golang.org/pkg/sync/atomic/#pkg-note-BUG
wg sync.WaitGroup
bucket chan struct{}
errs []error
cancel context.CancelFunc
ctxCancel <-chan struct{} // nil if no context.
ctxErr func() error
}
// WithNErrs returns a new Group with length of errs slice upto nerrs,
// upon Wait() errors are returned collected from all tasks.
func WithNErrs(nerrs int) *Group {
return &Group{errs: make([]error, nerrs), firstErr: -1}
}
// Wait blocks until all function calls from the Go method have returned, then
// returns the slice of errors from all function calls.
func (g *Group) Wait() []error {
g.wg.Wait()
if g.cancel != nil {
g.cancel()
}
return g.errs
}
// WaitErr blocks until all function calls from the Go method have returned, then
// returns the first error returned.
func (g *Group) WaitErr() error {
g.wg.Wait()
if g.cancel != nil {
g.cancel()
}
if g.firstErr >= 0 && len(g.errs) > int(g.firstErr) {
// len(g.errs) > int(g.firstErr) is for then used uninitialized.
return g.errs[g.firstErr]
}
return nil
}
// WithConcurrency allows to limit the concurrency of the group.
// This must be called before starting any async processes.
// There is no order to which functions are allowed to run.
// If n <= 0 no concurrency limits are enforced.
// g is modified and returned as well.
func (g *Group) WithConcurrency(n int) *Group {
if n <= 0 {
g.bucket = nil
return g
}
// Fill bucket with tokens
g.bucket = make(chan struct{}, n)
for i := 0; i < n; i++ {
g.bucket <- struct{}{}
}
return g
}
// WithCancelOnError will return a context that is canceled
// as soon as an error occurs.
// The returned CancelFunc must always be called similar to context.WithCancel.
// If the supplied context is canceled any goroutines waiting for execution are also canceled.
func (g *Group) WithCancelOnError(ctx context.Context) (context.Context, context.CancelFunc) {
ctx, g.cancel = context.WithCancel(ctx)
g.ctxCancel = ctx.Done()
g.ctxErr = ctx.Err
return ctx, g.cancel
}
// Go calls the given function in a new goroutine.
//
// The errors will be collected in errs slice and returned by Wait().
func (g *Group) Go(f func() error, index int) {
g.wg.Add(1)
go func() {
defer g.wg.Done()
if g.bucket != nil {
// Wait for token
select {
case <-g.bucket:
defer func() {
// Put back token..
g.bucket <- struct{}{}
}()
case <-g.ctxCancel:
if len(g.errs) > index {
atomic.CompareAndSwapInt64(&g.firstErr, -1, int64(index))
g.errs[index] = g.ctxErr()
}
return
}
}
if err := f(); err != nil {
if len(g.errs) > index {
atomic.CompareAndSwapInt64(&g.firstErr, -1, int64(index))
g.errs[index] = err
}
if g.cancel != nil {
g.cancel()
}
}
}()
}
golang-github-minio-pkg-3.0.10/sync/errgroup/errgroup_test.go 0000664 0000000 0000000 00000002655 14655770405 0024313 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package errgroup
import (
"fmt"
"reflect"
"testing"
)
func TestGroupWithNErrs(t *testing.T) {
err1 := fmt.Errorf("errgroup_test: 1")
err2 := fmt.Errorf("errgroup_test: 2")
cases := []struct {
errs []error
}{
{errs: []error{nil}},
{errs: []error{err1}},
{errs: []error{err1, nil}},
{errs: []error{err1, nil, err2}},
}
for j, tc := range cases {
t.Run(fmt.Sprintf("Test%d", j+1), func(t *testing.T) {
g := WithNErrs(len(tc.errs))
for i, err := range tc.errs {
err := err
g.Go(func() error { return err }, i)
}
gotErrs := g.Wait()
if !reflect.DeepEqual(gotErrs, tc.errs) {
t.Errorf("Expected %#v, got %#v", tc.errs, gotErrs)
}
})
}
}
golang-github-minio-pkg-3.0.10/sys/ 0000775 0000000 0000000 00000000000 14655770405 0017045 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/sys/rlimit-file_bsd.go 0000664 0000000 0000000 00000002757 14655770405 0022454 0 ustar 00root root 0000000 0000000 //go:build freebsd || dragonfly
// +build freebsd dragonfly
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package sys
import (
"syscall"
)
// GetMaxOpenFileLimit - returns maximum file descriptor number that can be opened by this process.
func GetMaxOpenFileLimit() (curLimit, maxLimit uint64, err error) {
var rlimit syscall.Rlimit
if err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit); err == nil {
curLimit = uint64(rlimit.Cur)
maxLimit = uint64(rlimit.Max)
}
return curLimit, maxLimit, err
}
// SetMaxOpenFileLimit - sets maximum file descriptor number that can be opened by this process.
func SetMaxOpenFileLimit(curLimit, maxLimit uint64) error {
rlimit := syscall.Rlimit{Cur: int64(curLimit), Max: int64(curLimit)}
return syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlimit)
}
golang-github-minio-pkg-3.0.10/sys/rlimit-file_nix.go 0000664 0000000 0000000 00000003416 14655770405 0022473 0 ustar 00root root 0000000 0000000 //go:build linux || darwin || openbsd || netbsd || solaris
// +build linux darwin openbsd netbsd solaris
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package sys
import (
"runtime"
"syscall"
)
// GetMaxOpenFileLimit - returns maximum file descriptor number that can be opened by this process.
func GetMaxOpenFileLimit() (curLimit, maxLimit uint64, err error) {
var rlimit syscall.Rlimit
if err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit); err == nil {
curLimit = rlimit.Cur
maxLimit = rlimit.Max
}
return curLimit, maxLimit, err
}
// SetMaxOpenFileLimit - sets maximum file descriptor number that can be opened by this process.
func SetMaxOpenFileLimit(curLimit, maxLimit uint64) error {
if runtime.GOOS == "darwin" && curLimit > 10240 {
// The max file limit is 10240, even though
// the max returned by Getrlimit is 1<<63-1.
// This is OPEN_MAX in sys/syslimits.h.
// refer https://github.com/golang/go/issues/30401
curLimit = 10240
}
rlimit := syscall.Rlimit{Cur: curLimit, Max: maxLimit}
return syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlimit)
}
golang-github-minio-pkg-3.0.10/sys/rlimit-file_test.go 0000664 0000000 0000000 00000002405 14655770405 0022651 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package sys
import "testing"
// Test get max open file limit.
func TestGetMaxOpenFileLimit(t *testing.T) {
_, _, err := GetMaxOpenFileLimit()
if err != nil {
t.Errorf("expected: nil, got: %v", err)
}
}
// Test set open file limit
func TestSetMaxOpenFileLimit(t *testing.T) {
curLimit, maxLimit, err := GetMaxOpenFileLimit()
if err != nil {
t.Fatalf("Unable to get max open file limit. %v", err)
}
err = SetMaxOpenFileLimit(curLimit, maxLimit)
if err != nil {
t.Errorf("expected: nil, got: %v", err)
}
}
golang-github-minio-pkg-3.0.10/sys/rlimit-file_windows.go 0000664 0000000 0000000 00000002342 14655770405 0023364 0 ustar 00root root 0000000 0000000 //go:build windows
// +build windows
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package sys
// GetMaxOpenFileLimit - returns maximum file descriptor number that can be opened by this process.
func GetMaxOpenFileLimit() (curLimit, maxLimit uint64, err error) {
// Nothing to do for windows.
return curLimit, maxLimit, err
}
// SetMaxOpenFileLimit - sets maximum file descriptor number that can be opened by this process.
func SetMaxOpenFileLimit(curLimit, maxLimit uint64) error {
// Nothing to do for windows.
return nil
}
golang-github-minio-pkg-3.0.10/sys/rlimit-memory_bsd.go 0000664 0000000 0000000 00000002756 14655770405 0023044 0 ustar 00root root 0000000 0000000 //go:build freebsd || dragonfly
// +build freebsd dragonfly
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package sys
import "syscall"
// GetMaxMemoryLimit - returns the maximum size of the process's virtual memory (address space) in bytes.
func GetMaxMemoryLimit() (curLimit, maxLimit uint64, err error) {
var rlimit syscall.Rlimit
if err = syscall.Getrlimit(syscall.RLIMIT_DATA, &rlimit); err == nil {
curLimit = uint64(rlimit.Cur)
maxLimit = uint64(rlimit.Max)
}
return curLimit, maxLimit, err
}
// SetMaxMemoryLimit - sets the maximum size of the process's virtual memory (address space) in bytes.
func SetMaxMemoryLimit(curLimit, maxLimit uint64) error {
rlimit := syscall.Rlimit{Cur: int64(curLimit), Max: int64(maxLimit)}
return syscall.Setrlimit(syscall.RLIMIT_DATA, &rlimit)
}
golang-github-minio-pkg-3.0.10/sys/rlimit-memory_nix.go 0000664 0000000 0000000 00000002746 14655770405 0023071 0 ustar 00root root 0000000 0000000 //go:build linux || darwin || netbsd || solaris
// +build linux darwin netbsd solaris
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package sys
import "syscall"
// GetMaxMemoryLimit - returns the maximum size of the process's virtual memory (address space) in bytes.
func GetMaxMemoryLimit() (curLimit, maxLimit uint64, err error) {
var rlimit syscall.Rlimit
if err = syscall.Getrlimit(syscall.RLIMIT_AS, &rlimit); err == nil {
curLimit = rlimit.Cur
maxLimit = rlimit.Max
}
return curLimit, maxLimit, err
}
// SetMaxMemoryLimit - sets the maximum size of the process's virtual memory (address space) in bytes.
func SetMaxMemoryLimit(curLimit, maxLimit uint64) error {
rlimit := syscall.Rlimit{Cur: curLimit, Max: maxLimit}
return syscall.Setrlimit(syscall.RLIMIT_AS, &rlimit)
}
golang-github-minio-pkg-3.0.10/sys/rlimit-memory_openbsd.go 0000664 0000000 0000000 00000002671 14655770405 0023722 0 ustar 00root root 0000000 0000000 //go:build openbsd
// +build openbsd
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package sys
import "syscall"
// GetMaxMemoryLimit - returns the maximum size of the process's virtual memory (address space) in bytes.
func GetMaxMemoryLimit() (curLimit, maxLimit uint64, err error) {
var rlimit syscall.Rlimit
if err = syscall.Getrlimit(syscall.RLIMIT_DATA, &rlimit); err == nil {
curLimit = rlimit.Cur
maxLimit = rlimit.Max
}
return curLimit, maxLimit, err
}
// SetMaxMemoryLimit - sets the maximum size of the process's virtual memory (address space) in bytes.
func SetMaxMemoryLimit(curLimit, maxLimit uint64) error {
rlimit := syscall.Rlimit{Cur: curLimit, Max: maxLimit}
return syscall.Setrlimit(syscall.RLIMIT_DATA, &rlimit)
}
golang-github-minio-pkg-3.0.10/sys/rlimit-memory_test.go 0000664 0000000 0000000 00000002362 14655770405 0023244 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package sys
import "testing"
// Test get max memory limit.
func TestGetMaxMemoryLimit(t *testing.T) {
_, _, err := GetMaxMemoryLimit()
if err != nil {
t.Errorf("expected: nil, got: %v", err)
}
}
// Test set memory limit
func TestSetMaxMemoryLimit(t *testing.T) {
curLimit, maxLimit, err := GetMaxMemoryLimit()
if err != nil {
t.Fatalf("Unable to get max memory limit. %v", err)
}
err = SetMaxMemoryLimit(curLimit, maxLimit)
if err != nil {
t.Errorf("expected: nil, got: %v", err)
}
}
golang-github-minio-pkg-3.0.10/sys/rlimit-memory_windows.go 0000664 0000000 0000000 00000002352 14655770405 0023756 0 ustar 00root root 0000000 0000000 //go:build windows
// +build windows
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package sys
// GetMaxMemoryLimit - returns the maximum size of the process's virtual memory (address space) in bytes.
func GetMaxMemoryLimit() (curLimit, maxLimit uint64, err error) {
// Nothing to do for windows.
return curLimit, maxLimit, err
}
// SetMaxMemoryLimit - sets the maximum size of the process's virtual memory (address space) in bytes.
func SetMaxMemoryLimit(curLimit, maxLimit uint64) error {
// Nothing to do for windows.
return nil
}
golang-github-minio-pkg-3.0.10/sys/stats.go 0000664 0000000 0000000 00000001554 14655770405 0020537 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package sys
// Stats - system statistics.
type Stats struct {
TotalRAM uint64 // Physical RAM size in bytes,
}
golang-github-minio-pkg-3.0.10/sys/stats_bsd.go 0000664 0000000 0000000 00000002615 14655770405 0021366 0 ustar 00root root 0000000 0000000 //go:build openbsd || freebsd || dragonfly
// +build openbsd freebsd dragonfly
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package sys
import (
"encoding/binary"
"syscall"
)
func getHwPhysmem() (uint64, error) {
totalString, err := syscall.Sysctl("hw.physmem")
if err != nil {
return 0, err
}
// syscall.sysctl() helpfully assumes the result is a null-terminated string and
// removes the last byte of the result if it's 0 :/
totalString += "\x00"
total := uint64(binary.LittleEndian.Uint64([]byte(totalString)))
return total, nil
}
// GetStats - return system statistics for bsd.
func GetStats() (stats Stats, err error) {
stats.TotalRAM, err = getHwPhysmem()
return stats, err
}
golang-github-minio-pkg-3.0.10/sys/stats_darwin.go 0000664 0000000 0000000 00000002512 14655770405 0022076 0 ustar 00root root 0000000 0000000 //go:build darwin
// +build darwin
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package sys
import (
"encoding/binary"
"syscall"
)
func getHwMemsize() (uint64, error) {
totalString, err := syscall.Sysctl("hw.memsize")
if err != nil {
return 0, err
}
// syscall.sysctl() helpfully assumes the result is a null-terminated string and
// removes the last byte of the result if it's 0 :/
totalString += "\x00"
return binary.LittleEndian.Uint64([]byte(totalString)), nil
}
// GetStats - return system statistics for macOS.
func GetStats() (stats Stats, err error) {
stats.TotalRAM, err = getHwMemsize()
return stats, err
}
golang-github-minio-pkg-3.0.10/sys/stats_linux.go 0000664 0000000 0000000 00000005063 14655770405 0021755 0 ustar 00root root 0000000 0000000 //go:build linux && !arm && !386
// +build linux,!arm,!386
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package sys
import (
"os"
"syscall"
"github.com/minio/madmin-go/v3/cgroup"
)
// Get the final system memory limit chosen by the user.
// by default without any configuration on a vanilla Linux
// system you would see physical RAM limit. If cgroup
// is configured at some point in time this function
// would return the memory limit chosen for the given pid.
func getMemoryLimit() (sysLimit uint64, err error) {
if sysLimit, err = getSysinfoMemoryLimit(); err != nil {
// Physical memory info is not accessible, just exit here.
return 0, err
}
// Following code is deliberately ignoring the error.
cGroupLimit, gerr := cgroup.GetMemoryLimit(os.Getpid())
if gerr != nil {
// Upon error just return system limit.
return sysLimit, nil
}
// cgroup limit is lesser than system limit means
// user wants to limit the memory usage further
// treat cgroup limit as the system limit.
if cGroupLimit <= sysLimit {
sysLimit = cGroupLimit
}
// Final system limit.
return sysLimit, nil
}
// Get physical RAM size of the node.
func getSysinfoMemoryLimit() (limit uint64, err error) {
var si syscall.Sysinfo_t
if err = syscall.Sysinfo(&si); err != nil {
return 0, err
}
// Some fields in syscall.Sysinfo_t have different integer sizes
// in different platform architectures. Cast all fields to uint64.
unit := si.Unit
totalRAM := si.Totalram
// Total RAM is always the multiplicative value
// of unit size and total ram.
//nolint:unconvert
return uint64(unit) * uint64(totalRAM), nil
}
// GetStats - return system statistics, currently only
// supported value is TotalRAM.
func GetStats() (stats Stats, err error) {
var limit uint64
limit, err = getMemoryLimit()
if err != nil {
return Stats{}, err
}
stats.TotalRAM = limit
return stats, nil
}
golang-github-minio-pkg-3.0.10/sys/stats_linux_32bit.go 0000664 0000000 0000000 00000005056 14655770405 0022762 0 ustar 00root root 0000000 0000000 //go:build (linux && arm) || (linux && 386)
// +build linux,arm linux,386
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package sys
import (
"os"
"syscall"
"github.com/minio/madmin-go/v3/cgroup"
)
// Get the final system memory limit chosen by the user.
// by default without any configuration on a vanilla Linux
// system you would see physical RAM limit. If cgroup
// is configured at some point in time this function
// would return the memory limit chosen for the given pid.
func getMemoryLimit() (sysLimit uint64, err error) {
if sysLimit, err = getSysinfoMemoryLimit(); err != nil {
// Physical memory info is not accessible, just exit here.
return 0, err
}
// Following code is deliberately ignoring the error.
cGroupLimit, gerr := cgroup.GetMemoryLimit(os.Getpid())
if gerr != nil {
// Upon error just return system limit.
return sysLimit, nil
}
// cgroup limit is lesser than system limit means
// user wants to limit the memory usage further
// treat cgroup limit as the system limit.
if cGroupLimit <= sysLimit {
sysLimit = cGroupLimit
}
// Final system limit.
return sysLimit, nil
}
// Get physical RAM size of the node.
func getSysinfoMemoryLimit() (limit uint64, err error) {
var si syscall.Sysinfo_t
if err = syscall.Sysinfo(&si); err != nil {
return 0, err
}
// Some fields in syscall.Sysinfo_t have different integer sizes
// in different platform architectures. Cast all fields to uint64.
unit := si.Unit
totalRAM := si.Totalram
// Total RAM is always the multiplicative value
// of unit size and total ram.
return uint64(unit) * uint64(totalRAM), nil
}
// GetStats - return system statistics, currently only
// supported value is TotalRAM.
func GetStats() (stats Stats, err error) {
var limit uint64
limit, err = getMemoryLimit()
if err != nil {
return Stats{}, err
}
stats.TotalRAM = limit
return stats, nil
}
golang-github-minio-pkg-3.0.10/sys/stats_netbsd.go 0000664 0000000 0000000 00000002543 14655770405 0022075 0 ustar 00root root 0000000 0000000 //go:build netbsd
// +build netbsd
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package sys
import (
"encoding/binary"
"syscall"
)
func getHwPhysmem() (uint64, error) {
totalString, err := syscall.Sysctl("hw.physmem64")
if err != nil {
return 0, err
}
// syscall.sysctl() helpfully assumes the result is a null-terminated string and
// removes the last byte of the result if it's 0 :/
totalString += "\x00"
total := uint64(binary.LittleEndian.Uint64([]byte(totalString)))
return total, nil
}
// GetStats - return system statistics for bsd.
func GetStats() (stats Stats, err error) {
stats.TotalRAM, err = getHwPhysmem()
return stats, err
}
golang-github-minio-pkg-3.0.10/sys/stats_solaris.go 0000664 0000000 0000000 00000001772 14655770405 0022275 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package sys
import "errors"
// GetStats - stub implementation for Solaris, this will not give us
// complete functionality but will enable fs setups on Solaris.
func GetStats() (stats Stats, err error) {
return Stats{}, errors.New("Not implemented")
}
golang-github-minio-pkg-3.0.10/sys/stats_test.go 0000664 0000000 0000000 00000002022 14655770405 0021565 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package sys
import "testing"
// Test get stats result.
func TestGetStats(t *testing.T) {
stats, err := GetStats()
if err != nil {
t.Errorf("Tests: Expected `nil`, Got %s", err)
}
if stats.TotalRAM == 0 {
t.Errorf("Tests: Expected `n > 0`, Got %d", stats.TotalRAM)
}
}
golang-github-minio-pkg-3.0.10/sys/stats_windows.go 0000664 0000000 0000000 00000003302 14655770405 0022302 0 ustar 00root root 0000000 0000000 //go:build windows
// +build windows
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package sys
import (
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
var (
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
procGlobalMemoryStatusEx = modkernel32.NewProc("GlobalMemoryStatusEx")
)
type memoryStatusEx struct {
cbSize uint32
dwMemoryLoad uint32
ullTotalPhys uint64 // in bytes
ullAvailPhys uint64
ullTotalPageFile uint64
ullAvailPageFile uint64
ullTotalVirtual uint64
ullAvailVirtual uint64
ullAvailExtendedVirtual uint64
}
// GetStats - return system statistics for windows.
func GetStats() (stats Stats, err error) {
var memInfo memoryStatusEx
memInfo.cbSize = uint32(unsafe.Sizeof(memInfo))
if mem, _, _ := procGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&memInfo))); mem == 0 {
err = syscall.GetLastError()
} else {
stats.TotalRAM = memInfo.ullTotalPhys
}
return stats, err
}
golang-github-minio-pkg-3.0.10/sys/threads.go 0000664 0000000 0000000 00000002320 14655770405 0021023 0 ustar 00root root 0000000 0000000 //go:build linux
// +build linux
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package sys
import (
"io/ioutil"
"strconv"
"strings"
)
// GetMaxThreads returns the maximum number of threads that the system can create.
func GetMaxThreads() (int, error) {
sysMaxThreadsStr, err := ioutil.ReadFile("/proc/sys/kernel/threads-max")
if err != nil {
return 0, err
}
sysMaxThreads, err := strconv.Atoi(strings.TrimSpace(string(sysMaxThreadsStr)))
if err != nil {
return 0, err
}
return sysMaxThreads, nil
}
golang-github-minio-pkg-3.0.10/sys/threads_other.go 0000664 0000000 0000000 00000001764 14655770405 0022237 0 ustar 00root root 0000000 0000000 //go:build !linux
// +build !linux
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package sys
import "errors"
// GetMaxThreads returns the maximum number of threads that the system can create.
func GetMaxThreads() (int, error) {
return 0, errors.New("getting max threads is not supported")
}
golang-github-minio-pkg-3.0.10/trie/ 0000775 0000000 0000000 00000000000 14655770405 0017172 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/trie/trie.go 0000664 0000000 0000000 00000005054 14655770405 0020470 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
// Package trie implements a simple trie tree for minio server/tools borrows
// idea from - https://godoc.org/golang.org/x/text/internal/triegen.
package trie
// Node trie tree node container carries value and children.
type Node struct {
exists bool
value string
child map[rune]*Node // runes as child.
}
// newNode create a new trie node.
func newNode() *Node {
return &Node{
exists: false,
value: "",
child: make(map[rune]*Node),
}
}
// Trie is a trie container.
type Trie struct {
root *Node
size int
}
// Root returns root node.
func (t *Trie) Root() *Node {
return t.root
}
// Insert insert a key.
func (t *Trie) Insert(key string) {
curNode := t.root
for _, v := range key {
if curNode.child[v] == nil {
curNode.child[v] = newNode()
}
curNode = curNode.child[v]
}
if !curNode.exists {
// increment when new rune child is added.
t.size++
curNode.exists = true
}
// value is stored for retrieval in future.
curNode.value = key
}
// PrefixMatch - prefix match.
func (t *Trie) PrefixMatch(key string) []string {
node, _ := t.findNode(key)
if node == nil {
return nil
}
return t.Walk(node)
}
// Walk the tree.
func (t *Trie) Walk(node *Node) (ret []string) {
if node.exists {
ret = append(ret, node.value)
}
for _, v := range node.child {
ret = append(ret, t.Walk(v)...)
}
return
}
// find nodes corresponding to key.
func (t *Trie) findNode(key string) (node *Node, index int) {
curNode := t.root
f := false
for k, v := range key {
if f {
index = k
f = false
}
if curNode.child[v] == nil {
return nil, index
}
curNode = curNode.child[v]
if curNode.exists {
f = true
}
}
if curNode.exists {
index = len(key)
}
return curNode, index
}
// NewTrie create a new trie.
func NewTrie() *Trie {
return &Trie{
root: newNode(),
size: 0,
}
}
golang-github-minio-pkg-3.0.10/trie/trie_test.go 0000664 0000000 0000000 00000003657 14655770405 0021536 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package trie
import (
"testing"
)
// Simply make sure creating a new tree works.
func TestNewTrie(t *testing.T) {
trie := NewTrie()
if trie.size != 0 {
t.Errorf("expected size 0, got: %d", trie.size)
}
}
// Ensure that we can insert new keys into the tree, then check the size.
func TestInsert(t *testing.T) {
trie := NewTrie()
// We need to have an empty tree to begin with.
if trie.size != 0 {
t.Errorf("expected size 0, got: %d", trie.size)
}
trie.Insert("key")
trie.Insert("keyy")
// After inserting, we should have a size of two.
if trie.size != 2 {
t.Errorf("expected size 2, got: %d", trie.size)
}
}
// Ensure that PrefixMatch gives us the correct two keys in the tree.
func TestPrefixMatch(t *testing.T) {
trie := NewTrie()
// Feed it some fodder: only 'minio' and 'miny-os' should trip the matcher.
trie.Insert("minio")
trie.Insert("amazon")
trie.Insert("cheerio")
trie.Insert("miny-o's")
matches := trie.PrefixMatch("min")
if len(matches) != 2 {
t.Errorf("expected two matches, got: %d", len(matches))
}
if matches[0] != "minio" && matches[1] != "minio" {
t.Errorf("expected one match to be 'minio', got: '%s' and '%s'", matches[0], matches[1])
}
}
golang-github-minio-pkg-3.0.10/wildcard/ 0000775 0000000 0000000 00000000000 14655770405 0020020 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/wildcard/match.go 0000664 0000000 0000000 00000006256 14655770405 0021454 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package wildcard
// MatchSimple - finds whether the text matches/satisfies the pattern string.
// supports '*' wildcard in the pattern and ? for single characters.
// Only difference to Match is that `?` at the end is optional,
// meaning `a?` pattern will match name `a`.
func MatchSimple(pattern, name string) bool {
if pattern == "" {
return name == pattern
}
if pattern == "*" {
return true
}
// Do an extended wildcard '*' and '?' match.
return deepMatchRune([]rune(name), []rune(pattern), true)
}
// Match - finds whether the text matches/satisfies the pattern string.
// supports '*' and '?' wildcards in the pattern string.
// unlike path.Match(), considers a path as a flat name space while matching the pattern.
// The difference is illustrated in the example here https://play.golang.org/p/Ega9qgD4Qz .
func Match(pattern, name string) (matched bool) {
if pattern == "" {
return name == pattern
}
if pattern == "*" {
return true
}
// Do an extended wildcard '*' and '?' match.
return deepMatchRune([]rune(name), []rune(pattern), false)
}
func deepMatchRune(str, pattern []rune, simple bool) bool {
for len(pattern) > 0 {
switch pattern[0] {
default:
if len(str) == 0 || str[0] != pattern[0] {
return false
}
case '?':
if len(str) == 0 {
return simple
}
case '*':
return deepMatchRune(str, pattern[1:], simple) ||
(len(str) > 0 && deepMatchRune(str[1:], pattern, simple))
}
str = str[1:]
pattern = pattern[1:]
}
return len(str) == 0 && len(pattern) == 0
}
// MatchAsPatternPrefix matches text as a prefix of the given pattern. Examples:
//
// | Pattern | Text | Match Result |
// ====================================
// | abc* | ab | True |
// | abc* | abd | False |
// | abc*c | abcd | True |
// | ab*??d | abxxc | True |
// | ab*??d | abxc | True |
// | ab??d | abxc | True |
// | ab??d | abc | True |
// | ab??d | abcxdd | False |
//
// This function is only useful in some special situations.
func MatchAsPatternPrefix(pattern, text string) bool {
return matchAsPatternPrefix([]rune(pattern), []rune(text))
}
func matchAsPatternPrefix(pattern, text []rune) bool {
for i := 0; i < len(text) && i < len(pattern); i++ {
if pattern[i] == '*' {
return true
}
if pattern[i] == '?' {
continue
}
if pattern[i] != text[i] {
return false
}
}
return len(text) <= len(pattern)
}
golang-github-minio-pkg-3.0.10/wildcard/match_test.go 0000664 0000000 0000000 00000035106 14655770405 0022507 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package wildcard
import (
"testing"
)
// TestMatch - Tests validate the logic of wild card matching.
// `Match` supports '*' and '?' wildcards.
// Sample usage: In resource matching for bucket policy validation.
func TestMatch(t *testing.T) {
testCases := []struct {
pattern string
text string
matched bool
}{
// Test case - 1.
// Test case with pattern "*". Expected to match any text.
{
pattern: "*",
text: "s3:GetObject",
matched: true,
},
// Test case - 2.
// Test case with empty pattern. This only matches empty string.
{
pattern: "",
text: "s3:GetObject",
matched: false,
},
// Test case - 3.
// Test case with empty pattern. This only matches empty string.
{
pattern: "",
text: "",
matched: true,
},
// Test case - 4.
// Test case with single "*" at the end.
{
pattern: "s3:*",
text: "s3:ListMultipartUploadParts",
matched: true,
},
// Test case - 5.
// Test case with a no "*". In this case the pattern and text should be the same.
{
pattern: "s3:ListBucketMultipartUploads",
text: "s3:ListBucket",
matched: false,
},
// Test case - 6.
// Test case with a no "*". In this case the pattern and text should be the same.
{
pattern: "s3:ListBucket",
text: "s3:ListBucket",
matched: true,
},
// Test case - 7.
// Test case with a no "*". In this case the pattern and text should be the same.
{
pattern: "s3:ListBucketMultipartUploads",
text: "s3:ListBucketMultipartUploads",
matched: true,
},
// Test case - 8.
// Test case with pattern containing key name with a prefix. Should accept the same text without a "*".
{
pattern: "my-bucket/oo*",
text: "my-bucket/oo",
matched: true,
},
// Test case - 9.
// Test case with "*" at the end of the pattern.
{
pattern: "my-bucket/In*",
text: "my-bucket/India/Karnataka/",
matched: true,
},
// Test case - 10.
// Test case with prefixes shuffled.
// This should fail.
{
pattern: "my-bucket/In*",
text: "my-bucket/Karnataka/India/",
matched: false,
},
// Test case - 11.
// Test case with text expanded to the wildcards in the pattern.
{
pattern: "my-bucket/In*/Ka*/Ban",
text: "my-bucket/India/Karnataka/Ban",
matched: true,
},
// Test case - 12.
// Test case with the keyname part is repeated as prefix several times.
// This is valid.
{
pattern: "my-bucket/In*/Ka*/Ban",
text: "my-bucket/India/Karnataka/Ban/Ban/Ban/Ban/Ban",
matched: true,
},
// Test case - 13.
// Test case to validate that `*` can be expanded into multiple prefixes.
{
pattern: "my-bucket/In*/Ka*/Ban",
text: "my-bucket/India/Karnataka/Area1/Area2/Area3/Ban",
matched: true,
},
// Test case - 14.
// Test case to validate that `*` can be expanded into multiple prefixes.
{
pattern: "my-bucket/In*/Ka*/Ban",
text: "my-bucket/India/State1/State2/Karnataka/Area1/Area2/Area3/Ban",
matched: true,
},
// Test case - 15.
// Test case where the keyname part of the pattern is expanded in the text.
{
pattern: "my-bucket/In*/Ka*/Ban",
text: "my-bucket/India/Karnataka/Bangalore",
matched: false,
},
// Test case - 16.
// Test case with prefixes and wildcard expanded for all "*".
{
pattern: "my-bucket/In*/Ka*/Ban*",
text: "my-bucket/India/Karnataka/Bangalore",
matched: true,
},
// Test case - 17.
// Test case with keyname part being a wildcard in the pattern.
{
pattern: "my-bucket/*",
text: "my-bucket/India",
matched: true,
},
// Test case - 18.
{
pattern: "my-bucket/oo*",
text: "my-bucket/odo",
matched: false,
},
// Test case with pattern containing wildcard '?'.
// Test case - 19.
// "my-bucket?/" matches "my-bucket1/", "my-bucket2/", "my-bucket3" etc...
// doesn't match "mybucket/".
{
pattern: "my-bucket?/abc*",
text: "mybucket/abc",
matched: false,
},
// Test case - 20.
{
pattern: "my-bucket?/abc*",
text: "my-bucket1/abc",
matched: true,
},
// Test case - 21.
{
pattern: "my-?-bucket/abc*",
text: "my--bucket/abc",
matched: false,
},
// Test case - 22.
{
pattern: "my-?-bucket/abc*",
text: "my-1-bucket/abc",
matched: true,
},
// Test case - 23.
{
pattern: "my-?-bucket/abc*",
text: "my-k-bucket/abc",
matched: true,
},
// Test case - 24.
{
pattern: "my??bucket/abc*",
text: "mybucket/abc",
matched: false,
},
// Test case - 25.
{
pattern: "my??bucket/abc*",
text: "my4abucket/abc",
matched: true,
},
// Test case - 26.
{
pattern: "my-bucket?abc*",
text: "my-bucket/abc",
matched: true,
},
// Test case 27-28.
// '?' matches '/' too. (works with s3).
// This is because the namespace is considered flat.
// "abc?efg" matches both "abcdefg" and "abc/efg".
{
pattern: "my-bucket/abc?efg",
text: "my-bucket/abcdefg",
matched: true,
},
{
pattern: "my-bucket/abc?efg",
text: "my-bucket/abc/efg",
matched: true,
},
// Test case - 29.
{
pattern: "my-bucket/abc????",
text: "my-bucket/abc",
matched: false,
},
// Test case - 30.
{
pattern: "my-bucket/abc????",
text: "my-bucket/abcde",
matched: false,
},
// Test case - 31.
{
pattern: "my-bucket/abc????",
text: "my-bucket/abcdefg",
matched: true,
},
// Test case 32-34.
// test case with no '*'.
{
pattern: "my-bucket/abc?",
text: "my-bucket/abc",
matched: false,
},
{
pattern: "my-bucket/abc?",
text: "my-bucket/abcd",
matched: true,
},
{
pattern: "my-bucket/abc?",
text: "my-bucket/abcde",
matched: false,
},
// Test case 35.
{
pattern: "my-bucket/mnop*?",
text: "my-bucket/mnop",
matched: false,
},
// Test case 36.
{
pattern: "my-bucket/mnop*?",
text: "my-bucket/mnopqrst/mnopqr",
matched: true,
},
// Test case 37.
{
pattern: "my-bucket/mnop*?",
text: "my-bucket/mnopqrst/mnopqrs",
matched: true,
},
// Test case 38.
{
pattern: "my-bucket/mnop*?",
text: "my-bucket/mnop",
matched: false,
},
// Test case 39.
{
pattern: "my-bucket/mnop*?",
text: "my-bucket/mnopq",
matched: true,
},
// Test case 40.
{
pattern: "my-bucket/mnop*?",
text: "my-bucket/mnopqr",
matched: true,
},
// Test case 41.
{
pattern: "my-bucket/mnop*?and",
text: "my-bucket/mnopqand",
matched: true,
},
// Test case 42.
{
pattern: "my-bucket/mnop*?and",
text: "my-bucket/mnopand",
matched: false,
},
// Test case 43.
{
pattern: "my-bucket/mnop*?and",
text: "my-bucket/mnopqand",
matched: true,
},
// Test case 44.
{
pattern: "my-bucket/mnop*?",
text: "my-bucket/mn",
matched: false,
},
// Test case 45.
{
pattern: "my-bucket/mnop*?",
text: "my-bucket/mnopqrst/mnopqrs",
matched: true,
},
// Test case 46.
{
pattern: "my-bucket/mnop*??",
text: "my-bucket/mnopqrst",
matched: true,
},
// Test case 47.
{
pattern: "my-bucket/mnop*qrst",
text: "my-bucket/mnopabcdegqrst",
matched: true,
},
// Test case 48.
{
pattern: "my-bucket/mnop*?and",
text: "my-bucket/mnopqand",
matched: true,
},
// Test case 49.
{
pattern: "my-bucket/mnop*?and",
text: "my-bucket/mnopand",
matched: false,
},
// Test case 50.
{
pattern: "my-bucket/mnop*?and?",
text: "my-bucket/mnopqanda",
matched: true,
},
// Test case 51.
{
pattern: "my-bucket/mnop*?and",
text: "my-bucket/mnopqanda",
matched: false,
},
// Test case 52.
{
pattern: "my-?-bucket/abc*",
text: "my-bucket/mnopqanda",
matched: false,
},
// Test case - 53:
{
pattern: "a?",
text: "a",
matched: false,
},
}
// Iterating over the test cases, call the function under test and assert the output.
for i, testCase := range testCases {
actualResult := Match(testCase.pattern, testCase.text)
if testCase.matched != actualResult {
t.Errorf("Test %d: Expected the result to be `%v`, but instead found it to be `%v`", i+1, testCase.matched, actualResult)
}
}
}
// TestMatchSimple - Tests validate the logic of wild card matching.
// `MatchSimple` supports matching for only '*' in the pattern string.
func TestMatchSimple(t *testing.T) {
testCases := []struct {
pattern string
text string
matched bool
}{
// Test case - 1.
// Test case with pattern "*". Expected to match any text.
{
pattern: "*",
text: "s3:GetObject",
matched: true,
},
// Test case - 2.
// Test case with empty pattern. This only matches empty string.
{
pattern: "",
text: "s3:GetObject",
matched: false,
},
// Test case - 3.
// Test case with empty pattern. This only matches empty string.
{
pattern: "",
text: "",
matched: true,
},
// Test case - 4.
// Test case with single "*" at the end.
{
pattern: "s3:*",
text: "s3:ListMultipartUploadParts",
matched: true,
},
// Test case - 5.
// Test case with a no "*". In this case the pattern and text should be the same.
{
pattern: "s3:ListBucketMultipartUploads",
text: "s3:ListBucket",
matched: false,
},
// Test case - 6.
// Test case with a no "*". In this case the pattern and text should be the same.
{
pattern: "s3:ListBucket",
text: "s3:ListBucket",
matched: true,
},
// Test case - 7.
// Test case with a no "*". In this case the pattern and text should be the same.
{
pattern: "s3:ListBucketMultipartUploads",
text: "s3:ListBucketMultipartUploads",
matched: true,
},
// Test case - 8.
// Test case with pattern containing key name with a prefix. Should accept the same text without a "*".
{
pattern: "my-bucket/oo*",
text: "my-bucket/oo",
matched: true,
},
// Test case - 9.
// Test case with "*" at the end of the pattern.
{
pattern: "my-bucket/In*",
text: "my-bucket/India/Karnataka/",
matched: true,
},
// Test case - 10.
// Test case with prefixes shuffled.
// This should fail.
{
pattern: "my-bucket/In*",
text: "my-bucket/Karnataka/India/",
matched: false,
},
// Test case - 11.
// Test case with text expanded to the wildcards in the pattern.
{
pattern: "my-bucket/In*/Ka*/Ban",
text: "my-bucket/India/Karnataka/Ban",
matched: true,
},
// Test case - 12.
// Test case with the keyname part is repeated as prefix several times.
// This is valid.
{
pattern: "my-bucket/In*/Ka*/Ban",
text: "my-bucket/India/Karnataka/Ban/Ban/Ban/Ban/Ban",
matched: true,
},
// Test case - 13.
// Test case to validate that `*` can be expanded into multiple prefixes.
{
pattern: "my-bucket/In*/Ka*/Ban",
text: "my-bucket/India/Karnataka/Area1/Area2/Area3/Ban",
matched: true,
},
// Test case - 14.
// Test case to validate that `*` can be expanded into multiple prefixes.
{
pattern: "my-bucket/In*/Ka*/Ban",
text: "my-bucket/India/State1/State2/Karnataka/Area1/Area2/Area3/Ban",
matched: true,
},
// Test case - 15.
// Test case where the keyname part of the pattern is expanded in the text.
{
pattern: "my-bucket/In*/Ka*/Ban",
text: "my-bucket/India/Karnataka/Bangalore",
matched: false,
},
// Test case - 16.
// Test case with prefixes and wildcard expanded for all "*".
{
pattern: "my-bucket/In*/Ka*/Ban*",
text: "my-bucket/India/Karnataka/Bangalore",
matched: true,
},
// Test case - 17.
// Test case with keyname part being a wildcard in the pattern.
{
pattern: "my-bucket/*",
text: "my-bucket/India",
matched: true,
},
// Test case - 18.
{
pattern: "my-bucket/oo*",
text: "my-bucket/odo",
matched: false,
},
// Test case - 19.
{
pattern: "my-bucket/oo?*",
text: "my-bucket/oo???",
matched: true,
},
// Test case - 20:
{
pattern: "my-bucket/oo??*",
text: "my-bucket/odo",
matched: false,
},
// Test case - 21:
{
pattern: "?h?*",
text: "?h?hello",
matched: true,
},
// Test case - 22:
{
pattern: "a?",
text: "a",
matched: true,
},
}
// Iterating over the test cases, call the function under test and assert the output.
for i, testCase := range testCases {
actualResult := MatchSimple(testCase.pattern, testCase.text)
if testCase.matched != actualResult {
t.Errorf("Test %d: Expected the result to be `%v`, but instead found it to be `%v`", i+1, testCase.matched, actualResult)
}
}
}
func TestMatchAsPatternPrefix(t *testing.T) {
testCases := []struct {
pattern string
text string
matched bool
}{
{
pattern: "",
text: "",
matched: true,
},
{
pattern: "a",
text: "",
matched: true,
},
{ // case 3
pattern: "a",
text: "b",
matched: false,
},
{
pattern: "",
text: "b",
matched: false,
},
{
pattern: "abc",
text: "ab",
matched: true,
},
{ // case 6
pattern: "ab*",
text: "ab",
matched: true,
},
{
pattern: "abc*",
text: "ab",
matched: true,
},
{
pattern: "abc?",
text: "ab",
matched: true,
},
{
pattern: "abc*",
text: "abd",
matched: false,
},
{ // case 10
pattern: "abc*c",
text: "abcd",
matched: true,
},
{
pattern: "ab*??d",
text: "abxxc",
matched: true,
},
{
pattern: "ab*??",
text: "abxc",
matched: true,
},
{
pattern: "ab??",
text: "abxc",
matched: true,
},
{
pattern: "ab??",
text: "abx",
matched: true,
},
{ // case 15
pattern: "ab??d",
text: "abcxd",
matched: true,
},
{
pattern: "ab??d",
text: "abcxdd",
matched: false,
},
}
for i, testCase := range testCases {
actualResult := MatchAsPatternPrefix(testCase.pattern, testCase.text)
if testCase.matched != actualResult {
t.Errorf("Test %d: Expected the result to be `%v`, but instead found it to be `%v`", i+1, testCase.matched, actualResult)
}
}
}
golang-github-minio-pkg-3.0.10/words/ 0000775 0000000 0000000 00000000000 14655770405 0017365 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/words/damerau-levenshtein.go 0000664 0000000 0000000 00000003443 14655770405 0023660 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package words
import "math"
// Returns the minimum value of a slice of integers
func minimum(integers []int) (minVal int) {
minVal = math.MaxInt32
for _, v := range integers {
if v < minVal {
minVal = v
}
}
return
}
// DamerauLevenshteinDistance calculates distance between two strings using an algorithm
// described in https://en.wikipedia.org/wiki/Damerau-Levenshtein_distance
func DamerauLevenshteinDistance(a, b string) int {
var cost int
d := make([][]int, len(a)+1)
for i := 1; i <= len(a)+1; i++ {
d[i-1] = make([]int, len(b)+1)
}
for i := 0; i <= len(a); i++ {
d[i][0] = i
}
for j := 0; j <= len(b); j++ {
d[0][j] = j
}
for i := 1; i <= len(a); i++ {
for j := 1; j <= len(b); j++ {
if a[i-1] == b[j-1] {
cost = 0
} else {
cost = 1
}
d[i][j] = minimum([]int{
d[i-1][j] + 1,
d[i][j-1] + 1,
d[i-1][j-1] + cost,
})
if i > 1 && j > 1 && a[i-1] == b[j-2] && a[i-2] == b[j-1] {
d[i][j] = minimum([]int{d[i][j], d[i-2][j-2] + cost}) // transposition
}
}
}
return d[len(a)][len(b)]
}
golang-github-minio-pkg-3.0.10/words/damerau-levenshtein_test.go 0000664 0000000 0000000 00000003750 14655770405 0024720 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package words
import (
"math"
"testing"
)
// Test minimum function which calculates the minimal value in a list of integers
func TestMinimum(t *testing.T) {
type testCase struct {
listval []int
expected int
}
testCases := []testCase{
{listval: []int{3, 4, 15}, expected: 3},
{listval: []int{}, expected: math.MaxInt32},
}
// Validate all the test cases.
for i, tt := range testCases {
val := minimum(tt.listval)
if val != tt.expected {
t.Errorf("Test %d:, Expected %d, got %d", i+1, tt.expected, val)
}
}
}
// Test DamerauLevenshtein which calculates the difference distance between two words
func TestDamerauLevenshtein(t *testing.T) {
type testCase struct {
word1 string
word2 string
distance int
}
testCases := []testCase{
{word1: "", word2: "", distance: 0},
{word1: "a", word2: "a", distance: 0},
{word1: "a", word2: "b", distance: 1},
{word1: "rm", word2: "tm", distance: 1},
{word1: "version", word2: "evrsion", distance: 1},
{word1: "version", word2: "bersio", distance: 2},
}
// Validate all the test cases.
for i, tt := range testCases {
d := DamerauLevenshteinDistance(tt.word1, tt.word2)
if d != tt.distance {
t.Errorf("Test %d:, Expected %d, got %d", i+1, tt.distance, d)
}
}
}
golang-github-minio-pkg-3.0.10/workers/ 0000775 0000000 0000000 00000000000 14655770405 0017723 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/workers/workers.go 0000664 0000000 0000000 00000003164 14655770405 0021752 0 ustar 00root root 0000000 0000000 // Copyright (c) 2022-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package workers
import (
"errors"
"sync"
)
// Workers provides a bounded semaphore with the ability to wait until all
// concurrent jobs finish.
type Workers struct {
wg sync.WaitGroup
queue chan struct{}
}
// New creates a Workers object which allows up to n jobs to proceed
// concurrently. n must be > 0.
func New(n int) (*Workers, error) {
if n <= 0 {
return nil, errors.New("n must be > 0")
}
queue := make(chan struct{}, n)
for i := 0; i < n; i++ {
queue <- struct{}{}
}
return &Workers{
queue: queue,
}, nil
}
// Take is how a job (goroutine) can Take its turn.
func (jt *Workers) Take() {
jt.wg.Add(1)
<-jt.queue
}
// Give is how a job (goroutine) can give back its turn once done.
func (jt *Workers) Give() {
jt.queue <- struct{}{}
jt.wg.Done()
}
// Wait waits for all ongoing concurrent jobs to complete
func (jt *Workers) Wait() {
jt.wg.Wait()
}
golang-github-minio-pkg-3.0.10/workers/workers_test.go 0000664 0000000 0000000 00000005765 14655770405 0023022 0 ustar 00root root 0000000 0000000 // Copyright (c) 2022-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package workers
import (
"fmt"
"sync"
"testing"
)
func TestWorkers(t *testing.T) {
tests := []struct {
n int
jobs int
mustFail bool
}{
{
n: 0,
jobs: 5,
mustFail: true,
},
{
n: -1,
jobs: 5,
mustFail: true,
},
{
n: 1,
jobs: 5,
},
{
n: 2,
jobs: 5,
},
{
n: 5,
jobs: 10,
},
{
n: 10,
jobs: 5,
},
}
testFn := func(t *testing.T, n, jobs int, mustFail bool) {
var mu sync.Mutex
var jobsDone int
// Create workers for n concurrent workers
jt, err := New(n)
if err == nil && mustFail {
t.Fatal("Expected test to return error")
}
if err != nil && mustFail {
return
}
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
for i := 0; i < jobs; i++ {
jt.Take()
go func() { // Launch a worker after acquiring a token
defer jt.Give() // Give token back once done
mu.Lock()
jobsDone++
mu.Unlock()
}()
}
jt.Wait() // Wait for all workers to complete
if jobsDone != jobs {
t.Fatalf("Expected %d jobs to be done but only %d were done", jobs, jobsDone)
}
}
for i, test := range tests {
t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
testFn(t, test.n, test.jobs, test.mustFail)
})
}
// Verify that workers can be reused after full drain
t.Run("test-workers-reuse", func(t *testing.T) {
var mu sync.Mutex
jt, _ := New(5)
for reuse := 0; reuse < 3; reuse++ {
var jobsDone int
for i := 0; i < 10; i++ {
jt.Take()
go func() {
defer jt.Give()
mu.Lock()
jobsDone++
mu.Unlock()
}()
}
jt.Wait()
if jobsDone != 10 {
t.Fatalf("Expected %d jobs to be complete but only %d were", 10, jobsDone)
}
}
})
}
func benchmarkWorkers(b *testing.B, n, jobs int) {
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
var mu sync.Mutex
var jobsDone int
jt, _ := New(n)
for i := 0; i < jobs; i++ {
jt.Take()
go func() {
defer jt.Give()
mu.Lock()
jobsDone++
mu.Unlock()
}()
}
jt.Wait()
if jobsDone != jobs {
b.Fail()
}
}
})
}
func BenchmarkWorkers_N5_J10(b *testing.B) {
benchmarkWorkers(b, 5, 10)
}
func BenchmarkWorkers_N5_J100(b *testing.B) {
benchmarkWorkers(b, 5, 100)
}
golang-github-minio-pkg-3.0.10/xtime/ 0000775 0000000 0000000 00000000000 14655770405 0017355 5 ustar 00root root 0000000 0000000 golang-github-minio-pkg-3.0.10/xtime/time.go 0000664 0000000 0000000 00000007211 14655770405 0020643 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2024 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package xtime
import (
"fmt"
"time"
"github.com/tinylib/msgp/msgp"
"gopkg.in/yaml.v3"
)
// Additional durations, a day is considered to be 24 hours
const (
Day time.Duration = time.Hour * 24
Week = Day * 7
)
var unitMap = map[string]int64{
"ns": int64(time.Nanosecond),
"us": int64(time.Microsecond),
"µs": int64(time.Microsecond), // U+00B5 = micro symbol
"μs": int64(time.Microsecond), // U+03BC = Greek letter mu
"ms": int64(time.Millisecond),
"s": int64(time.Second),
"m": int64(time.Minute),
"h": int64(time.Hour),
"d": int64(Day),
"w": int64(Week),
}
// ParseDuration parses a duration string.
// The following code is borrowed from time.ParseDuration
// https://cs.opensource.google/go/go/+/refs/tags/go1.22.5:src/time/format.go;l=1589
// This function extends this function by allowing support for days and weeks.
// This function must only be used when days and weeks are necessary inputs
// in all other cases it is preferred that a user uses Go's time.ParseDuration
func ParseDuration(s string) (time.Duration, error) {
dur, err := time.ParseDuration(s) // Parse via standard Go, if success return right away.
if err == nil {
return dur, nil
}
return parseDuration(s)
}
// Duration is a wrapper around time.Duration that supports YAML and JSON
type Duration time.Duration
// D will return as a time.Duration.
func (d Duration) D() time.Duration {
return time.Duration(d)
}
// UnmarshalYAML implements yaml.Unmarshaler
func (d *Duration) UnmarshalYAML(value *yaml.Node) error {
if value.Kind == yaml.ScalarNode {
dur, err := ParseDuration(value.Value)
if err != nil {
return err
}
*d = Duration(dur)
return nil
}
return fmt.Errorf("unable to unmarshal %s", value.Tag)
}
// UnmarshalJSON implements json.Unmarshaler
func (d *Duration) UnmarshalJSON(bs []byte) error {
if len(bs) <= 2 {
return nil
}
dur, err := ParseDuration(string(bs[1 : len(bs)-1]))
if err != nil {
return err
}
*d = Duration(dur)
return nil
}
// MarshalMsg appends the marshaled form of the object to the provided
// byte slice, returning the extended slice and any errors encountered.
func (d Duration) MarshalMsg(bytes []byte) ([]byte, error) {
return msgp.AppendInt64(bytes, int64(d)), nil
}
// UnmarshalMsg unmarshals the object from binary,
// returing any leftover bytes and any errors encountered.
func (d *Duration) UnmarshalMsg(b []byte) ([]byte, error) {
i, rem, err := msgp.ReadInt64Bytes(b)
*d = Duration(i)
return rem, err
}
// EncodeMsg writes itself as MessagePack using a *msgp.Writer.
func (d Duration) EncodeMsg(w *msgp.Writer) error {
return w.WriteInt64(int64(d))
}
// DecodeMsg decodes itself as MessagePack using a *msgp.Reader.
func (d *Duration) DecodeMsg(reader *msgp.Reader) error {
i, err := reader.ReadInt64()
*d = Duration(i)
return err
}
// Msgsize returns the maximum serialized size in bytes.
func (d Duration) Msgsize() int {
return msgp.Int64Size
}
golang-github-minio-pkg-3.0.10/xtime/time_contrib.go 0000664 0000000 0000000 00000007210 14655770405 0022362 0 ustar 00root root 0000000 0000000 // Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the go.dev/LICENSE file.
package xtime
import (
"errors"
"strconv"
"time"
)
// function borrowed from https://cs.opensource.google/go/go/+/refs/tags/go1.22.5:src/time/format.go;l=1589
// supports days and weeks such as '1d1ms', '1w1ms'
func parseDuration(s string) (time.Duration, error) {
// [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+
orig := s
var d int64
neg := false
// Consume [-+]?
if s != "" {
c := s[0]
if c == '-' || c == '+' {
neg = c == '-'
s = s[1:]
}
}
// Special case: if all that is left is "0", this is zero.
if s == "0" {
return 0, nil
}
if s == "" {
return 0, errors.New("invalid duration " + strconv.Quote(orig))
}
for s != "" {
var (
v, f int64 // integers before, after decimal point
scale float64 = 1 // value = v + f/scale
)
var err error
// The next character must be [0-9.]
if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') {
return 0, errors.New("invalid duration " + strconv.Quote(orig))
}
// Consume [0-9]*
pl := len(s)
v, s, err = leadingInt(s)
if err != nil {
return 0, errors.New("invalid duration " + strconv.Quote(orig))
}
pre := pl != len(s) // whether we consumed anything before a period
// Consume (\.[0-9]*)?
post := false
if s != "" && s[0] == '.' {
s = s[1:]
pl := len(s)
f, scale, s = leadingFraction(s)
post = pl != len(s)
}
if !pre && !post {
// no digits (e.g. ".s" or "-.s")
return 0, errors.New("invalid duration " + strconv.Quote(orig))
}
// Consume unit.
i := 0
for ; i < len(s); i++ {
c := s[i]
if c == '.' || '0' <= c && c <= '9' {
break
}
}
if i == 0 {
return 0, errors.New("missing unit in duration " + strconv.Quote(orig))
}
u := s[:i]
s = s[i:]
unit, ok := unitMap[u]
if !ok {
return 0, errors.New("unknown unit " + strconv.Quote(u) + " in duration " + strconv.Quote(orig))
}
if v > (1<<63-1)/unit {
// overflow
return 0, errors.New("invalid duration " + strconv.Quote(orig))
}
v *= unit
if f > 0 {
// float64 is needed to be nanosecond accurate for fractions of hours.
// v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit)
v += int64(float64(f) * (float64(unit) / scale))
if v < 0 {
// overflow
return 0, errors.New("invalid duration " + strconv.Quote(orig))
}
}
d += v
if d < 0 {
// overflow
return 0, errors.New("invalid duration " + strconv.Quote(orig))
}
}
if neg {
d = -d
}
return time.Duration(d), nil
}
var errLeadingInt = errors.New("bad [0-9]*") // never printed
// leadingInt consumes the leading [0-9]* from s.
func leadingInt(s string) (x int64, rem string, err error) {
i := 0
for ; i < len(s); i++ {
c := s[i]
if c < '0' || c > '9' {
break
}
if x > (1<<63-1)/10 {
// overflow
return 0, "", errLeadingInt
}
x = x*10 + int64(c) - '0'
if x < 0 {
// overflow
return 0, "", errLeadingInt
}
}
return x, s[i:], nil
}
// leadingFraction consumes the leading [0-9]* from s.
// It is used only for fractions, so does not return an error on overflow,
// it just stops accumulating precision.
func leadingFraction(s string) (x int64, scale float64, rem string) {
i := 0
scale = 1
overflow := false
for ; i < len(s); i++ {
c := s[i]
if c < '0' || c > '9' {
break
}
if overflow {
continue
}
if x > (1<<63-1)/10 {
// It's possible for overflow to give a positive number, so take care.
overflow = true
continue
}
y := x*10 + int64(c) - '0'
if y < 0 {
overflow = true
continue
}
x = y
scale *= 10
}
return x, scale, s[i:]
}
golang-github-minio-pkg-3.0.10/xtime/time_contrib_test.go 0000664 0000000 0000000 00000010407 14655770405 0023423 0 ustar 00root root 0000000 0000000 // Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the go.dev/LICENSE file.
package xtime
import (
"fmt"
"strings"
"testing"
"time"
)
var parseDurationTests = []struct {
in string
want time.Duration
}{
// simple
{"0", 0},
{"5s", 5 * time.Second},
{"30s", 30 * time.Second},
{"1478s", 1478 * time.Second},
// sign
{"-5s", -5 * time.Second},
{"+5s", 5 * time.Second},
{"-0", 0},
{"+0", 0},
// decimal
{"5.0s", 5 * time.Second},
{"5.6s", 5*time.Second + 600*time.Millisecond},
{"5.s", 5 * time.Second},
{".5s", 500 * time.Millisecond},
{"1.0s", 1 * time.Second},
{"1.00s", 1 * time.Second},
{"1.004s", 1*time.Second + 4*time.Millisecond},
{"1.0040s", 1*time.Second + 4*time.Millisecond},
{"100.00100s", 100*time.Second + 1*time.Millisecond},
// different units
{"10ns", 10 * time.Nanosecond},
{"11us", 11 * time.Microsecond},
{"12µs", 12 * time.Microsecond}, // U+00B5
{"12μs", 12 * time.Microsecond}, // U+03BC
{"13ms", 13 * time.Millisecond},
{"14s", 14 * time.Second},
{"15m", 15 * time.Minute},
{"16h", 16 * time.Hour},
// composite durations
{"3h30m", 3*time.Hour + 30*time.Minute},
{"10.5s4m", 4*time.Minute + 10*time.Second + 500*time.Millisecond},
{"-2m3.4s", -(2*time.Minute + 3*time.Second + 400*time.Millisecond)},
{"1h2m3s4ms5us6ns", 1*time.Hour + 2*time.Minute + 3*time.Second + 4*time.Millisecond + 5*time.Microsecond + 6*time.Nanosecond},
{"39h9m14.425s", 39*time.Hour + 9*time.Minute + 14*time.Second + 425*time.Millisecond},
// large value
{"52763797000ns", 52763797000 * time.Nanosecond},
// more than 9 digits after decimal point, see https://golang.org/issue/6617
{"0.3333333333333333333h", 20 * time.Minute},
// 9007199254740993 = 1<<53+1 cannot be stored precisely in a float64
{"9007199254740993ns", (1<<53 + 1) * time.Nanosecond},
// largest duration that can be represented by int64 in nanoseconds
{"9223372036854775807ns", (1<<63 - 1) * time.Nanosecond},
{"9223372036854775.807us", (1<<63 - 1) * time.Nanosecond},
{"9223372036s854ms775us807ns", (1<<63 - 1) * time.Nanosecond},
{"-9223372036854775808ns", -1 << 63 * time.Nanosecond},
{"-9223372036854775.808us", -1 << 63 * time.Nanosecond},
{"-9223372036s854ms775us808ns", -1 << 63 * time.Nanosecond},
// largest negative value
{"-9223372036854775808ns", -1 << 63 * time.Nanosecond},
// largest negative round trip value, see https://golang.org/issue/48629
{"-2562047h47m16.854775808s", -1 << 63 * time.Nanosecond},
// huge string; issue 15011.
{"0.100000000000000000000h", 6 * time.Minute},
// This value tests the first overflow check in leadingFraction.
{"0.830103483285477580700h", 49*time.Minute + 48*time.Second + 372539827*time.Nanosecond},
{"1w1d1h", 1*7*24*time.Hour + 1*24*time.Hour + 1*time.Hour},
{"0.1w0.1d0.1h", time.Hour*19 + time.Minute*18},
}
func TestParseDuration(t *testing.T) {
for _, tc := range parseDurationTests {
d, err := ParseDuration(tc.in)
if err != nil || d != tc.want {
t.Errorf("ParseDuration(%q) = %v, %v, want %v, nil", tc.in, d, err, tc.want)
}
}
}
var parseDurationErrorTests = []struct {
in string
expect string
}{
// invalid
{"", `""`},
{"3", `"3"`},
{"-", `"-"`},
{"s", `"s"`},
{".", `"."`},
{"-.", `"-."`},
{".s", `".s"`},
{"+.s", `"+.s"`},
{"\x85\x85", `"\x85\x85"`},
{"\xffff", `"\xffff"`},
{"hello \xffff world", `"hello \xffff world"`},
{"\uFFFD", `"�"`}, // utf8.RuneError
{"\uFFFD hello \uFFFD world", `"� hello � world"`}, // utf8.RuneError
// overflow
{"9223372036854775810ns", `"9223372036854775810ns"`},
{"9223372036854775808ns", `"9223372036854775808ns"`},
{"-9223372036854775809ns", `"-9223372036854775809ns"`},
{"9223372036854776us", `"9223372036854776us"`},
{"3000000h", `"3000000h"`},
{"9223372036854775.808us", `"9223372036854775.808us"`},
{"9223372036854ms775us808ns", `"9223372036854ms775us808ns"`},
}
func TestParseDurationErrors(t *testing.T) {
for _, tc := range parseDurationErrorTests {
_, err := ParseDuration(tc.in)
if err == nil {
t.Errorf("ParseDuration(%q) = _, nil, want _, non-nil", tc.in)
} else if !strings.Contains(err.Error(), tc.expect) {
fmt.Println(err)
t.Errorf("ParseDuration(%q) = _, %q, error does not contain %q", tc.in, err, tc.expect)
}
}
}
golang-github-minio-pkg-3.0.10/xtime/time_unmarshal_test.go 0000664 0000000 0000000 00000005266 14655770405 0023764 0 ustar 00root root 0000000 0000000 // Copyright (c) 2015-2024 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
package xtime
import (
"bytes"
"encoding/json"
"testing"
"time"
"github.com/tinylib/msgp/msgp"
"gopkg.in/yaml.v3"
)
type testDuration struct {
A string `yaml:"a" json:"a"`
Dur Duration `yaml:"dur" json:"dur"`
DurationPointer *Duration `yaml:"durationPointer" json:"durationPointer"`
}
func TestDuration_Unmarshal(t *testing.T) {
jsonData := []byte(`{"a":"1s","dur":"1w1s","durationPointer":"7d1s"}`)
yamlData := []byte(`a: 1s
dur: 1w1s
durationPointer: 7d1s`)
yamlTest := testDuration{}
if err := yaml.Unmarshal(yamlData, &yamlTest); err != nil {
t.Fatal(err)
}
jsonTest := testDuration{}
if err := json.Unmarshal(jsonData, &jsonTest); err != nil {
t.Fatal(err)
}
jsonData = []byte(`{"a":"1s","dur":"1w1s"}`)
yamlData = []byte(`a: 1s
dur: 1w1s`)
if err := yaml.Unmarshal(yamlData, &yamlTest); err != nil {
t.Fatal(err)
}
if err := json.Unmarshal(jsonData, &jsonTest); err != nil {
t.Fatal(err)
}
}
func TestMarshalUnmarshalDuration(t *testing.T) {
v := Duration(time.Hour)
var vn Duration
bts, err := v.MarshalMsg(nil)
if err != nil {
t.Fatal(err)
}
left, err := vn.UnmarshalMsg(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
}
if vn != v {
t.Errorf("v=%#v; want=%#v", vn, v)
}
left, err = msgp.Skip(bts)
if err != nil {
t.Fatal(err)
}
if len(left) > 0 {
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
}
}
func TestEncodeDecodeDuration(t *testing.T) {
v := Duration(time.Hour)
var buf bytes.Buffer
msgp.Encode(&buf, &v)
m := v.Msgsize()
if buf.Len() > m {
t.Log("WARNING: TestEncodeDecodeDuration Msgsize() is inaccurate")
}
var vn Duration
err := msgp.Decode(&buf, &vn)
if err != nil {
t.Error(err)
}
if vn != v {
t.Errorf("v=%#v; want=%#v", vn, v)
}
buf.Reset()
msgp.Encode(&buf, &v)
err = msgp.NewReader(&buf).Skip()
if err != nil {
t.Error(err)
}
}