pax_global_header00006660000000000000000000000064146557704050014527gustar00rootroot0000000000000052 comment=2afc006bc71c9054e71b34f0c7a137669ab349de golang-github-minio-pkg-3.0.10/000077500000000000000000000000001465577040500162275ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/.github/000077500000000000000000000000001465577040500175675ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/.github/workflows/000077500000000000000000000000001465577040500216245ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/.github/workflows/go.yml000066400000000000000000000013451465577040500227570ustar00rootroot00000000000000name: 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.yaml000066400000000000000000000024661465577040500234400ustar00rootroot00000000000000name: 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.yml000066400000000000000000000012031465577040500243250ustar00rootroot00000000000000name: 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/.gitignore000066400000000000000000000003451465577040500202210ustar00rootroot00000000000000**/*.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.yml000066400000000000000000000010611465577040500206110ustar00rootroot00000000000000linters-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/LICENSE000066400000000000000000001033331465577040500172370ustar00rootroot00000000000000 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/Makefile000066400000000000000000000014321465577040500176670ustar00rootroot00000000000000GOPATH := $(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.md000066400000000000000000000003421465577040500175050ustar00rootroot00000000000000# 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/000077500000000000000000000000001465577040500173475ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/certs/ca-certs.go000066400000000000000000000052611465577040500214030ustar00rootroot00000000000000// 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.go000066400000000000000000000044221465577040500224400ustar00rootroot00000000000000// 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.go000066400000000000000000000044731465577040500225520ustar00rootroot00000000000000//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.go000066400000000000000000000032511465577040500234370ustar00rootroot00000000000000//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.go000066400000000000000000000124561465577040500210260ustar00rootroot00000000000000// 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.go000066400000000000000000000057301465577040500220620ustar00rootroot00000000000000// 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.go000066400000000000000000000017741465577040500210300ustar00rootroot00000000000000// 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.go000066400000000000000000000017101465577040500222350ustar00rootroot00000000000000//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.go000066400000000000000000000017221465577040500224050ustar00rootroot00000000000000//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.go000066400000000000000000000304621465577040500213150ustar00rootroot00000000000000// 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.key000066400000000000000000000032501465577040500223220ustar00rootroot00000000000000-----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.crt000066400000000000000000000023211465577040500221240ustar00rootroot00000000000000-----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.key000066400000000000000000000032501465577040500233350ustar00rootroot00000000000000-----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.crt000066400000000000000000000024621465577040500231450ustar00rootroot00000000000000-----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.key000066400000000000000000000032501465577040500215330ustar00rootroot00000000000000-----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.crt000066400000000000000000000024621465577040500213430ustar00rootroot00000000000000-----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.crt000066400000000000000000000024621465577040500213730ustar00rootroot00000000000000-----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.key000066400000000000000000000032501465577040500213670ustar00rootroot00000000000000-----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/000077500000000000000000000000001465577040500176715ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/console/console.go000066400000000000000000000331351465577040500216670ustar00rootroot00000000000000// 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.go000066400000000000000000000020711465577040500227210ustar00rootroot00000000000000// 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.go000066400000000000000000000036341465577040500215130ustar00rootroot00000000000000// 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/000077500000000000000000000000001465577040500171755ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/cors/cors.go000066400000000000000000000140461465577040500204770ustar00rootroot00000000000000// 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.go000066400000000000000000000165051465577040500215400ustar00rootroot00000000000000// 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.go000066400000000000000000000050511465577040500210410ustar00rootroot00000000000000// 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/000077500000000000000000000000001465577040500210065ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/cors/testdata/example1.xml000066400000000000000000000013631465577040500232470ustar00rootroot00000000000000 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.xml000066400000000000000000000011371465577040500232470ustar00rootroot00000000000000 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.xml000066400000000000000000000014671465577040500232560ustar00rootroot00000000000000 *PUTPOSTDELETEhttp://www.example1.com*PUTPOSTDELETEhttp://www.example2.*GET*x-amz-id-26000POSThttps://www.example3.com golang-github-minio-pkg-3.0.10/ellipses/000077500000000000000000000000001465577040500200475ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/ellipses/ellipses.go000066400000000000000000000153021465577040500222170ustar00rootroot00000000000000// 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.go000066400000000000000000002126161465577040500232650ustar00rootroot00000000000000// 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.go000066400000000000000000000044471465577040500213620ustar00rootroot00000000000000// 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.go000066400000000000000000000074211465577040500224140ustar00rootroot00000000000000// 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/000077500000000000000000000000001465577040500170175ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/env/env.go000066400000000000000000000055031465577040500201410ustar00rootroot00000000000000// 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.go000066400000000000000000000072071465577040500212030ustar00rootroot00000000000000// 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.go000066400000000000000000000125111465577040500207730ustar00rootroot00000000000000// 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/000077500000000000000000000000001465577040500173505ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/event/errors.go000066400000000000000000000017501465577040500212160ustar00rootroot00000000000000// 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.go000066400000000000000000000057671465577040500210370ustar00rootroot00000000000000// 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.go000066400000000000000000000227311465577040500206240ustar00rootroot00000000000000// 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.mod000066400000000000000000000063051465577040500173410ustar00rootroot00000000000000module 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.sum000066400000000000000000000635051465577040500173730ustar00rootroot00000000000000github.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/000077500000000000000000000000001465577040500171475ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/ldap/decode_dn_contrib.go000066400000000000000000000074671465577040500231400ustar00rootroot00000000000000// 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.go000066400000000000000000000053161465577040500224460ustar00rootroot00000000000000// 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.go000066400000000000000000000273561465577040500204330ustar00rootroot00000000000000// 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.go000066400000000000000000000362021465577040500216330ustar00rootroot00000000000000// 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.go000066400000000000000000000212351465577040500226720ustar00rootroot00000000000000// 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/000077500000000000000000000000001465577040500205325ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/licverifier/verifier.go000066400000000000000000000130631465577040500226770ustar00rootroot00000000000000// 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.go000066400000000000000000000116661465577040500237450ustar00rootroot00000000000000// 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/000077500000000000000000000000001465577040500175065ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/logger/message/000077500000000000000000000000001465577040500211325ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/logger/message/audit/000077500000000000000000000000001465577040500222405ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/logger/message/audit/entry.go000066400000000000000000000060421465577040500237320ustar00rootroot00000000000000// 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/000077500000000000000000000000001465577040500217135ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/logger/message/log/entry.go000066400000000000000000000056211465577040500234070ustar00rootroot00000000000000// 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/000077500000000000000000000000001465577040500174645ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/mimedb/Makefile000066400000000000000000000010401465577040500211170ustar00rootroot00000000000000# 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.go000066400000000000000000002765071465577040500204210ustar00rootroot00000000000000// 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.go000066400000000000000000000031641465577040500214430ustar00rootroot00000000000000// 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.go000066400000000000000000000022161465577040500220560ustar00rootroot00000000000000// 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/000077500000000000000000000000001465577040500204415ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/mimedb/util/gen-db.go000066400000000000000000000103251465577040500221250ustar00rootroot00000000000000// 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/000077500000000000000000000000001465577040500170155ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/net/health.go000066400000000000000000000066221465577040500206170ustar00rootroot00000000000000// 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.go000066400000000000000000000100101465577040500203110ustar00rootroot00000000000000// 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.go000066400000000000000000000204341465577040500213630ustar00rootroot00000000000000// 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.go000066400000000000000000000032561465577040500203360ustar00rootroot00000000000000// 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.go000066400000000000000000000036751465577040500214020ustar00rootroot00000000000000// 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.go000066400000000000000000000156361465577040500201610ustar00rootroot00000000000000// 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.go000066400000000000000000000222201465577040500212030ustar00rootroot00000000000000// 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/000077500000000000000000000000001465577040500175265ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/policy/action.go000066400000000000000000000523421465577040500213400ustar00rootroot00000000000000// 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.go000066400000000000000000000033411465577040500223720ustar00rootroot00000000000000// 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.go000066400000000000000000000130131465577040500220440ustar00rootroot00000000000000// 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.go000066400000000000000000000125231465577040500231100ustar00rootroot00000000000000// 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.go000066400000000000000000000273711465577040500224320ustar00rootroot00000000000000// 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.go000066400000000000000000000117521465577040500246370ustar00rootroot00000000000000// 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.go000066400000000000000000000360321465577040500256740ustar00rootroot00000000000000// 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.go000066400000000000000000000120141465577040500226250ustar00rootroot00000000000000// 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.go000066400000000000000000000740321465577040500236740ustar00rootroot00000000000000// 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/000077500000000000000000000000001465577040500215145ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/policy/condition/boolfunc.go000066400000000000000000000060301465577040500236510ustar00rootroot00000000000000// 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.go000066400000000000000000000142201465577040500247100ustar00rootroot00000000000000// 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.go000066400000000000000000000127241465577040500236420ustar00rootroot00000000000000// 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.go000066400000000000000000000256451465577040500247070ustar00rootroot00000000000000// 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.go000066400000000000000000000142651465577040500230060ustar00rootroot00000000000000// 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.go000066400000000000000000000272351465577040500240460ustar00rootroot00000000000000// 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.go000066400000000000000000000115341465577040500241660ustar00rootroot00000000000000// 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.go000066400000000000000000000225741465577040500252330ustar00rootroot00000000000000// 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.go000066400000000000000000000077471465577040500226520ustar00rootroot00000000000000// 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.go000066400000000000000000000134201465577040500236720ustar00rootroot00000000000000// 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.go000066400000000000000000000255531465577040500235060ustar00rootroot00000000000000// 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.go000066400000000000000000000104331465577040500227640ustar00rootroot00000000000000// 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.go000066400000000000000000000071731465577040500240320ustar00rootroot00000000000000// 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.go000066400000000000000000000061011465577040500236670ustar00rootroot00000000000000// 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.go000066400000000000000000000144551465577040500247410ustar00rootroot00000000000000// 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.go000066400000000000000000000144311465577040500243640ustar00rootroot00000000000000// 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.go000066400000000000000000000236211465577040500254240ustar00rootroot00000000000000// 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.go000066400000000000000000000246741465577040500242420ustar00rootroot00000000000000// 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.go000066400000000000000000000723261465577040500252760ustar00rootroot00000000000000// 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.go000066400000000000000000000076521465577040500231710ustar00rootroot00000000000000// 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.go000066400000000000000000000147021465577040500242220ustar00rootroot00000000000000// 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.go000066400000000000000000000044231465577040500236760ustar00rootroot00000000000000// 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.go000066400000000000000000000072251465577040500247400ustar00rootroot00000000000000// 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.go000066400000000000000000000063711465577040500221000ustar00rootroot00000000000000// 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.go000066400000000000000000000023531465577040500213140ustar00rootroot00000000000000// 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.go000066400000000000000000000032211465577040500223460ustar00rootroot00000000000000// 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.go000066400000000000000000000025261465577040500212130ustar00rootroot00000000000000// 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.go000066400000000000000000000016721465577040500204570ustar00rootroot00000000000000// 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.go000066400000000000000000000022671465577040500215170ustar00rootroot00000000000000// 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.go000066400000000000000000000077031465577040500221310ustar00rootroot00000000000000// 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.go000066400000000000000000000210001465577040500213450ustar00rootroot00000000000000// 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.go000066400000000000000000001047171465577040500224250ustar00rootroot00000000000000// 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.go000066400000000000000000000052271465577040500220440ustar00rootroot00000000000000// 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.go000066400000000000000000000105371465577040500231030ustar00rootroot00000000000000// 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.go000066400000000000000000000134141465577040500217070ustar00rootroot00000000000000// 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.go000066400000000000000000000204121465577040500227420ustar00rootroot00000000000000// 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.go000066400000000000000000000124401465577040500224210ustar00rootroot00000000000000// 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.go000066400000000000000000000230161465577040500234610ustar00rootroot00000000000000// 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.go000066400000000000000000000150711465577040500220650ustar00rootroot00000000000000// 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.go000066400000000000000000000334031465577040500231230ustar00rootroot00000000000000// 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.go000066400000000000000000000037551465577040500221530ustar00rootroot00000000000000// 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/000077500000000000000000000000001465577040500173435ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/quick/encoding.go000066400000000000000000000131211465577040500214560ustar00rootroot00000000000000// 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.go000066400000000000000000000041171465577040500217240ustar00rootroot00000000000000// 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.go000066400000000000000000000136441465577040500210160ustar00rootroot00000000000000// 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.go000066400000000000000000000257611465577040500220600ustar00rootroot00000000000000// 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/000077500000000000000000000000001465577040500203365ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/randreader/randreader.go000066400000000000000000000030111465577040500227670ustar00rootroot00000000000000// 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.go000066400000000000000000000027421465577040500240430ustar00rootroot00000000000000// 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.go000066400000000000000000000030321465577040500214730ustar00rootroot00000000000000// 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.go000066400000000000000000000016131465577040500224710ustar00rootroot00000000000000// 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.s000066400000000000000000000027441465577040500223340ustar00rootroot00000000000000// 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.go000066400000000000000000000026571465577040500227040ustar00rootroot00000000000000//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/000077500000000000000000000000001465577040500171455ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/safe/safe.go000066400000000000000000000066431465577040500204230ustar00rootroot00000000000000// 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.go000066400000000000000000000070631465577040500214570ustar00rootroot00000000000000// 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/000077500000000000000000000000001465577040500172035ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/sftp/sftp.go000066400000000000000000000203401465577040500205050ustar00rootroot00000000000000// 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/000077500000000000000000000000001465577040500175275ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/subnet/license.go000066400000000000000000000174621465577040500215120ustar00rootroot00000000000000// 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/000077500000000000000000000000001465577040500172035ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/sync/errgroup/000077500000000000000000000000001465577040500210505ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/sync/errgroup/errgroup.go000066400000000000000000000073551465577040500232560ustar00rootroot00000000000000// 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.go000066400000000000000000000026551465577040500243130ustar00rootroot00000000000000// 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/000077500000000000000000000000001465577040500170455ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/sys/rlimit-file_bsd.go000066400000000000000000000027571465577040500224540ustar00rootroot00000000000000//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.go000066400000000000000000000034161465577040500224730ustar00rootroot00000000000000//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.go000066400000000000000000000024051465577040500226510ustar00rootroot00000000000000// 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.go000066400000000000000000000023421465577040500233640ustar00rootroot00000000000000//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.go000066400000000000000000000027561465577040500230440ustar00rootroot00000000000000//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.go000066400000000000000000000027461465577040500230710ustar00rootroot00000000000000//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.go000066400000000000000000000026711465577040500237220ustar00rootroot00000000000000//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.go000066400000000000000000000023621465577040500232440ustar00rootroot00000000000000// 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.go000066400000000000000000000023521465577040500237560ustar00rootroot00000000000000//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.go000066400000000000000000000015541465577040500205370ustar00rootroot00000000000000// 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.go000066400000000000000000000026151465577040500213660ustar00rootroot00000000000000//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.go000066400000000000000000000025121465577040500220760ustar00rootroot00000000000000//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.go000066400000000000000000000050631465577040500217550ustar00rootroot00000000000000//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.go000066400000000000000000000050561465577040500227620ustar00rootroot00000000000000//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.go000066400000000000000000000025431465577040500220750ustar00rootroot00000000000000//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.go000066400000000000000000000017721465577040500222750ustar00rootroot00000000000000// 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.go000066400000000000000000000020221465577040500215650ustar00rootroot00000000000000// 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.go000066400000000000000000000033021465577040500223020ustar00rootroot00000000000000//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.go000066400000000000000000000023201465577040500210230ustar00rootroot00000000000000//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.go000066400000000000000000000017641465577040500222370ustar00rootroot00000000000000//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/000077500000000000000000000000001465577040500171725ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/trie/trie.go000066400000000000000000000050541465577040500204700ustar00rootroot00000000000000// 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.go000066400000000000000000000036571465577040500215360ustar00rootroot00000000000000// 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/000077500000000000000000000000001465577040500200205ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/wildcard/match.go000066400000000000000000000062561465577040500214540ustar00rootroot00000000000000// 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.go000066400000000000000000000351061465577040500225070ustar00rootroot00000000000000// 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/000077500000000000000000000000001465577040500173655ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/words/damerau-levenshtein.go000066400000000000000000000034431465577040500236600ustar00rootroot00000000000000// 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.go000066400000000000000000000037501465577040500247200ustar00rootroot00000000000000// 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/000077500000000000000000000000001465577040500177235ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/workers/workers.go000066400000000000000000000031641465577040500217520ustar00rootroot00000000000000// 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.go000066400000000000000000000057651465577040500230220ustar00rootroot00000000000000// 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/000077500000000000000000000000001465577040500173555ustar00rootroot00000000000000golang-github-minio-pkg-3.0.10/xtime/time.go000066400000000000000000000072111465577040500206430ustar00rootroot00000000000000// 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.go000066400000000000000000000072101465577040500223620ustar00rootroot00000000000000// 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.go000066400000000000000000000104071465577040500234230ustar00rootroot00000000000000// 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.go000066400000000000000000000052661465577040500237640ustar00rootroot00000000000000// 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) } }