Showing preview only (1,066K chars total). Download the full file or copy to clipboard to get everything.
Repository: andrewgaul/s3proxy
Branch: master
Commit: dc0df5caac8b
Files: 125
Total size: 1014.3 KB
Directory structure:
gitextract_jwamyj7y/
├── .dockerignore
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ └── ci-main.yml
├── .gitignore
├── .gitmodules
├── .mailmap
├── .mvn/
│ └── maven.config
├── .releaserc
├── Dockerfile
├── LICENSE
├── README.md
├── docs/
│ ├── Encryption.md
│ └── Logging.md
├── pom.xml
└── src/
├── main/
│ ├── assembly/
│ │ └── jar-with-dependencies.xml
│ ├── config/
│ │ └── logback.xml
│ ├── java/
│ │ └── org/
│ │ └── gaul/
│ │ └── s3proxy/
│ │ ├── AccessControlPolicy.java
│ │ ├── AliasBlobStore.java
│ │ ├── AuthenticationType.java
│ │ ├── AwsHttpHeaders.java
│ │ ├── AwsSignature.java
│ │ ├── BlobStoreLocator.java
│ │ ├── CaseInsensitiveImmutableMultimap.java
│ │ ├── ChunkedInputStream.java
│ │ ├── CompleteMultipartUploadRequest.java
│ │ ├── CreateBucketRequest.java
│ │ ├── CrossOriginResourceSharing.java
│ │ ├── DeleteMultipleObjectsRequest.java
│ │ ├── EncryptedBlobStore.java
│ │ ├── EventualBlobStore.java
│ │ ├── GlobBlobStoreLocator.java
│ │ ├── LatencyBlobStore.java
│ │ ├── Main.java
│ │ ├── MetricsHandler.java
│ │ ├── NoCacheBlobStore.java
│ │ ├── NullBlobStore.java
│ │ ├── PrefixBlobStore.java
│ │ ├── PutOptions2.java
│ │ ├── Quirks.java
│ │ ├── ReadOnlyBlobStore.java
│ │ ├── RegexBlobStore.java
│ │ ├── S3AuthorizationHeader.java
│ │ ├── S3ErrorCode.java
│ │ ├── S3Exception.java
│ │ ├── S3Operation.java
│ │ ├── S3Proxy.java
│ │ ├── S3ProxyConstants.java
│ │ ├── S3ProxyHandler.java
│ │ ├── S3ProxyHandlerJetty.java
│ │ ├── S3ProxyMetrics.java
│ │ ├── ShardedBlobStore.java
│ │ ├── StorageClassBlobStore.java
│ │ ├── ThrottledInputStream.java
│ │ ├── UserMetadataReplacerBlobStore.java
│ │ ├── awssdk/
│ │ │ ├── AwsS3SdkApiMetadata.java
│ │ │ ├── AwsS3SdkBlobStore.java
│ │ │ ├── AwsS3SdkBlobStoreContextModule.java
│ │ │ └── AwsS3SdkProviderMetadata.java
│ │ ├── azureblob/
│ │ │ ├── AzureBlobApiMetadata.java
│ │ │ ├── AzureBlobProviderMetadata.java
│ │ │ ├── AzureBlobStore.java
│ │ │ └── AzureBlobStoreContextModule.java
│ │ ├── crypto/
│ │ │ ├── Constants.java
│ │ │ ├── Decryption.java
│ │ │ ├── DecryptionInputStream.java
│ │ │ ├── Encryption.java
│ │ │ ├── EncryptionInputStream.java
│ │ │ └── PartPadding.java
│ │ ├── gcloudsdk/
│ │ │ ├── GCloudApiMetadata.java
│ │ │ ├── GCloudBlobStore.java
│ │ │ ├── GCloudBlobStoreContextModule.java
│ │ │ └── GCloudProviderMetadata.java
│ │ ├── junit/
│ │ │ ├── S3ProxyExtension.java
│ │ │ ├── S3ProxyJunitCore.java
│ │ │ └── S3ProxyRule.java
│ │ └── nio2blob/
│ │ ├── AbstractNio2BlobStore.java
│ │ ├── FilesystemNio2BlobApiMetadata.java
│ │ ├── FilesystemNio2BlobProviderMetadata.java
│ │ ├── FilesystemNio2BlobStore.java
│ │ ├── FilesystemNio2BlobStoreContextModule.java
│ │ ├── TransientNio2BlobApiMetadata.java
│ │ ├── TransientNio2BlobProviderMetadata.java
│ │ ├── TransientNio2BlobStore.java
│ │ └── TransientNio2BlobStoreContextModule.java
│ └── resources/
│ ├── checkstyle.xml
│ ├── copyright_header.txt
│ └── run-docker-container.sh
└── test/
├── java/
│ └── org/
│ └── gaul/
│ └── s3proxy/
│ ├── AliasBlobStoreTest.java
│ ├── AwsS3SdkBlobStoreTest.java
│ ├── AwsSdk2Test.java
│ ├── AwsSdkAnonymousTest.java
│ ├── AwsSdkTest.java
│ ├── CrossOriginResourceSharingAllowAllResponseTest.java
│ ├── CrossOriginResourceSharingResponseTest.java
│ ├── CrossOriginResourceSharingRuleTest.java
│ ├── EncryptedBlobStoreTest.java
│ ├── EventualBlobStoreTest.java
│ ├── GlobBlobStoreLocatorTest.java
│ ├── LatencyBlobStoreTest.java
│ ├── NoCacheBlobStoreTest.java
│ ├── NullBlobStoreTest.java
│ ├── PrefixBlobStoreTest.java
│ ├── ReadOnlyBlobStoreTest.java
│ ├── RegexBlobStoreTest.java
│ ├── ShardedBlobStoreTest.java
│ ├── TestUtils.java
│ ├── TierBlobStoreTest.java
│ ├── UserMetadataReplacerBlobStoreTest.java
│ └── junit/
│ ├── S3ProxyExtensionTest.java
│ └── S3ProxyRuleTest.java
└── resources/
├── keystore.jks
├── logback.xml
├── run-s3-tests.sh
├── s3-tests.conf
├── s3proxy-anonymous.conf
├── s3proxy-azurite.conf
├── s3proxy-cors-allow-all.conf
├── s3proxy-cors.conf
├── s3proxy-encryption.conf
├── s3proxy-fake-gcs-server.conf
├── s3proxy-filesystem-nio2.conf
├── s3proxy-localstack-aws-s3-sdk.conf
├── s3proxy-localstack-s3.conf
├── s3proxy-transient-nio2.conf
└── s3proxy.conf
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
# Exclude everything from context that is not used by COPY steps in the Dockerfile
*
!/target/s3proxy
!/src/main/resources/run-docker-container.sh
================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
- package-ecosystem: "maven"
directory: "/" # Location of package manifests
schedule:
interval: "monthly"
open-pull-requests-limit: 20
================================================
FILE: .github/workflows/ci-main.yml
================================================
name: Main CI
on:
push:
branches:
- "master"
tags:
- "*"
pull_request:
branches:
- "*"
permissions:
contents: read
env:
dockerhub_publish: ${{ secrets.DOCKER_PASS != '' }}
jobs:
meta:
runs-on: ubuntu-24.04-arm
outputs:
container_tags: ${{ steps.docker_action_meta.outputs.tags }}
container_labels: ${{ steps.docker_action_meta.outputs.labels }}
container_buildtime: ${{ fromJSON(steps.docker_action_meta.outputs.json).labels['org.opencontainers.image.created'] }}
container_version: ${{ fromJSON(steps.docker_action_meta.outputs.json).labels['org.opencontainers.image.version'] }}
container_revision: ${{ fromJSON(steps.docker_action_meta.outputs.json).labels['org.opencontainers.image.revision'] }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: false
persist-credentials: false
- name: Docker meta
id: docker_action_meta
uses: docker/metadata-action@v6.0.0
with:
images: |
name=ghcr.io/${{ github.repository }}/container
name=andrewgaul/s3proxy,enable=${{ env.dockerhub_publish }}
flavor: |
latest=auto
tags: |
type=sha,format=long
type=sha
type=match,pattern=s3proxy-(.*),group=1
type=ref,event=branch
type=ref,event=pr
type=ref,event=tag
labels: |
org.opencontainers.image.licenses=Apache-2.0
runTests:
runs-on: ubuntu-24.04-arm
needs: [meta]
steps:
- uses: actions/checkout@v6
with:
submodules: "recursive"
- uses: actions/setup-java@v5
with:
distribution: "temurin"
java-version: "17"
cache: "maven"
- uses: actions/setup-python@v6
with:
python-version: "3.11"
cache: "pip"
#Run tests
- name: Maven Set version
run: |
mvn versions:set -DnewVersion=${{ needs.meta.outputs.version }}
- name: Maven Package
run: |
mvn verify -DskipTests
- name: Maven Test
run: |
mvn test
- name: Maven Test with transient-nio2
run: |
# TODO: run other test classes
mvn test -Ds3proxy.test.conf=s3proxy-transient-nio2.conf -Dtest=AwsSdkTest
- name: Maven Test with filesystem-nio2
run: |
# TODO: run other test classes
mkdir /tmp/blobstore
mvn test -Ds3proxy.test.conf=s3proxy-filesystem-nio2.conf -Dtest=AwsSdkTest
- name: Install s3-tests
run: |
python -m pip install --upgrade pip
pip install tox tox-gh-actions
- name: Run s3-tests
run: |
./src/test/resources/run-s3-tests.sh
- name: Run s3-tests with transient-nio2
run: |
./src/test/resources/run-s3-tests.sh s3proxy-transient-nio2.conf
#Store the target
- uses: actions/upload-artifact@v7
with:
name: s3proxy
path: target/s3proxy
- uses: actions/upload-artifact@v7
with:
name: pom
path: pom.xml
azuriteTests:
runs-on: ubuntu-24.04-arm
needs: [meta]
steps:
- uses: actions/checkout@v6
with:
submodules: "recursive"
- uses: actions/setup-java@v5
with:
distribution: "temurin"
java-version: "17"
cache: "maven"
- uses: actions/setup-python@v6
with:
python-version: "3.11"
cache: "pip"
- name: Maven Package
run: |
mvn package -DskipTests
- name: Install Azurite
run: npx --yes --loglevel info azurite@3.35 --version
- name: Start Azurite
shell: bash
run: npx --yes --package azurite@3.35 azurite-blob &
- name: Maven Test with Azurite
run: |
# TODO: run other test classes
mvn test -Ds3proxy.test.conf=s3proxy-azurite.conf -Dtest=AwsSdkTest
- name: Install s3-tests
run: |
python -m pip install --upgrade pip
pip install tox tox-gh-actions
- name: Run s3-tests with Azurite
run: |
./src/test/resources/run-s3-tests.sh s3proxy-azurite.conf
kill $(pidof node)
localstackTests:
runs-on: ubuntu-24.04-arm
needs: [meta]
steps:
- uses: actions/checkout@v6
with:
submodules: "recursive"
- uses: actions/setup-java@v5
with:
distribution: "temurin"
java-version: "17"
cache: "maven"
- uses: actions/setup-python@v6
with:
python-version: "3.11"
cache: "pip"
- name: Maven Package
run: |
mvn package -DskipTests
- name: Install LocalStack
run: docker pull localstack/localstack:4.11.1
- name: Start LocalStack
run: |
docker run -d --name localstack -p 4566:4566 localstack/localstack:4.11.1
# Wait for LocalStack to be ready
for i in $(seq 30); do
if curl -s http://127.0.0.1:4566/_localstack/health | grep -q '"s3"'; then
break
fi
sleep 1
done
- name: Maven Test with LocalStack (s3)
run: |
# TODO: run other test classes
mvn test -Ds3proxy.test.conf=s3proxy-localstack-s3.conf -Dtest=AwsSdkTest
- name: Maven Test with LocalStack (aws-s3-sdk)
run: |
# TODO: run other test classes
mvn test -Ds3proxy.test.conf=s3proxy-localstack-aws-s3-sdk.conf -Dtest=AwsSdkTest
- name: Install s3-tests
run: |
python -m pip install --upgrade pip
pip install tox tox-gh-actions
- name: Run s3-tests with LocalStack (s3)
run: |
./src/test/resources/run-s3-tests.sh s3proxy-localstack-s3.conf
- name: Run s3-tests with LocalStack (aws-s3-sdk)
run: |
./src/test/resources/run-s3-tests.sh s3proxy-localstack-aws-s3-sdk.conf
docker stop localstack
fakeGcsServerTests:
runs-on: ubuntu-24.04-arm
needs: [meta]
steps:
- uses: actions/checkout@v6
with:
submodules: "recursive"
- uses: actions/setup-java@v5
with:
distribution: "temurin"
java-version: "17"
cache: "maven"
- uses: actions/setup-python@v6
with:
python-version: "3.11"
cache: "pip"
- name: Maven Package
run: |
mvn package -DskipTests
- name: Install fake-gcs-server
run: go install github.com/fsouza/fake-gcs-server@latest
- name: Start fake-gcs-server
run: $HOME/go/bin/fake-gcs-server -backend memory -scheme http -host 127.0.0.1 &
- name: Maven Test with fake-gcs-server
run: |
# TODO: run other test classes
STORAGE_EMULATOR_HOST=http://localhost:4443 mvn test -Ds3proxy.test.conf=s3proxy-fake-gcs-server.conf -Dtest=AwsSdkTest
- name: Install s3-tests
run: |
python -m pip install --upgrade pip
pip install tox tox-gh-actions
- name: Run s3-tests with fake-gcs-server
run: |
# TODO:
#STORAGE_EMULATOR_HOST=http://localhost:4443 ./src/test/resources/run-s3-tests.sh s3proxy-fake-gcs-server.conf
kill $(pidof fake-gcs-server)
Containerize:
runs-on: ubuntu-24.04-arm
needs: [runTests, azuriteTests, localstackTests, fakeGcsServerTests, meta]
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v6
- uses: actions/download-artifact@v8
with:
name: s3proxy
path: target
- uses: actions/download-artifact@v8
with:
name: pom
path: .
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Login to DockerHub
uses: docker/login-action@v4
if: github.event_name != 'pull_request' && env.dockerhub_publish == 'true'
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_PASS }}
- name: Login to GHCR
uses: docker/login-action@v4
if: github.event_name != 'pull_request'
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ needs.meta.outputs.container_tags }}
labels: ${{ needs.meta.outputs.container_labels }}
build-args: |
BUILDTIME=${{ needs.meta.outputs.container_buildtime }}
VERSION=${{ needs.meta.outputs.container_version }}
REVISION=${{ needs.meta.outputs.container_revision }}
cache-from: type=gha
cache-to: type=gha,mode=max
================================================
FILE: .gitignore
================================================
s3proxy.iml
.idea/
# Eclipse project configuration files
.classpath
.project
.settings
# MAC stuff
.DS_Store
# below is default github .ignore for java
*.class
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.ear
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
target/
# files created during tests
__blobstorage__/
AzuriteConfig
__azurite_db*
================================================
FILE: .gitmodules
================================================
[submodule "s3-tests"]
path = s3-tests
url = https://github.com/gaul/s3-tests.git
================================================
FILE: .mailmap
================================================
Hironao Sekine <phant.acc+github@gmail.com>
Sheng Hu <s.huhot@gmail.com>
================================================
FILE: .mvn/maven.config
================================================
-Daether.checksums.algorithms=SHA-512,SHA-256,SHA-1,MD5
================================================
FILE: .releaserc
================================================
{
"tagFormat": 's3proxy-${version}',
"branches": [
{
"name": 'master',
prerelease: false
},
{
"name": 'releases\/+([0-9])?(\.\d+)(\.\d+|z|$)',
prerelease: false
},
{
"name": 'next',
prerelease: false
},
{
name: 'next-major',
prerelease: true
},
{
name: 'develop',
prerelease: true
},
{
name: 'develop\/.*',
prerelease: true
}
]
}
================================================
FILE: Dockerfile
================================================
FROM docker.io/library/eclipse-temurin:21-jre
LABEL maintainer="Andrew Gaul <andrew@gaul.org>"
WORKDIR /opt/s3proxy
RUN apt-get update && \
apt-get install -y dumb-init && \
rm -rf /var/lib/apt/lists/*
COPY \
target/s3proxy \
src/main/resources/run-docker-container.sh \
/opt/s3proxy/
ENV \
LOG_LEVEL="info" \
S3PROXY_AUTHORIZATION="aws-v2-or-v4" \
S3PROXY_ENDPOINT="http://0.0.0.0:80" \
S3PROXY_IDENTITY="local-identity" \
S3PROXY_CREDENTIAL="local-credential" \
S3PROXY_VIRTUALHOST="" \
S3PROXY_KEYSTORE_PATH="keystore.jks" \
S3PROXY_KEYSTORE_PASSWORD="password" \
S3PROXY_CORS_ALLOW_ALL="false" \
S3PROXY_CORS_ALLOW_ORIGINS="" \
S3PROXY_CORS_ALLOW_METHODS="" \
S3PROXY_CORS_ALLOW_HEADERS="" \
S3PROXY_CORS_ALLOW_CREDENTIAL="" \
S3PROXY_V4_MAX_CHUNK_SIZE="16777216" \
S3PROXY_IGNORE_UNKNOWN_HEADERS="false" \
S3PROXY_ENCRYPTED_BLOBSTORE="" \
S3PROXY_ENCRYPTED_BLOBSTORE_PASSWORD="" \
S3PROXY_ENCRYPTED_BLOBSTORE_SALT="" \
S3PROXY_READ_ONLY_BLOBSTORE="false" \
S3PROXY_METRICS_ENABLED="false" \
S3PROXY_METRICS_PORT="9090" \
S3PROXY_METRICS_HOST="0.0.0.0" \
JCLOUDS_PROVIDER="filesystem-nio2" \
JCLOUDS_ENDPOINT="" \
JCLOUDS_REGION="" \
JCLOUDS_REGIONS="us-east-1" \
JCLOUDS_IDENTITY="remote-identity" \
JCLOUDS_CREDENTIAL="remote-credential" \
JCLOUDS_KEYSTONE_VERSION="" \
JCLOUDS_KEYSTONE_SCOPE="" \
JCLOUDS_KEYSTONE_PROJECT_DOMAIN_NAME="" \
JCLOUDS_FILESYSTEM_BASEDIR="/data"
EXPOSE 80 443
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["/opt/s3proxy/run-docker-container.sh"]
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
# S3Proxy
[](https://github.com/gaul/s3proxy/releases/)
[](https://hub.docker.com/r/andrewgaul/s3proxy/)
[](https://search.maven.org/#search%7Cga%7C1%7Ca%3A%22s3proxy%22)
[](https://twitter.com/S3Proxy)
S3Proxy implements the
[S3 API](https://en.wikipedia.org/wiki/Amazon_S3#S3_API_and_competing_services)
and *proxies* requests, enabling several use cases:
* translation from S3 to Backblaze B2, EMC Atmos, Google Cloud, Microsoft Azure, and OpenStack Swift
* testing without Amazon by using the local filesystem
* extension via middlewares
* embedding into Java applications
## Usage with Docker
[Docker Hub](https://hub.docker.com/r/andrewgaul/s3proxy/) hosts a Docker image
and has instructions on how to run it.
## Usage without Docker
Users can [download releases](https://github.com/gaul/s3proxy/releases)
from GitHub. Developers can build the project by running `mvn package` which
produces a binary at `target/s3proxy`. S3Proxy requires Java 17 or newer to
run.
Configure S3Proxy via a properties file. An example using the local
file system as the storage backend with anonymous access:
```
s3proxy.authorization=none
s3proxy.endpoint=http://127.0.0.1:8080
jclouds.provider=filesystem
jclouds.filesystem.basedir=/tmp/s3proxy
```
First create the filesystem basedir:
```
mkdir /tmp/s3proxy
```
Next run S3Proxy. Linux and Mac OS X users can run the executable jar:
```
chmod +x s3proxy
s3proxy --properties s3proxy.conf
```
Windows users must explicitly invoke java:
```
java -jar s3proxy --properties s3proxy.conf
```
Finally test by creating a bucket then listing all the buckets:
```
$ curl --request PUT http://localhost:8080/testbucket
$ curl http://localhost:8080/
<?xml version="1.0" ?><ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Owner><ID>75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a</ID><DisplayName>CustomersName@amazon.com</DisplayName></Owner><Buckets><Bucket><Name>testbucket</Name><CreationDate>2015-08-05T22:16:24.000Z</CreationDate></Bucket></Buckets></ListAllMyBucketsResult>
```
## Usage with Java
Maven Central hosts S3Proxy artifacts and the wiki has
[instructions on Java use](https://github.com/gaul/s3proxy/wiki/Using-S3Proxy-in-Java-projects).
## Supported storage backends
* atmos
* aws-s3 (Amazon-only, deprecated)
* aws-s3-sdk (S3-compatible backends via AWS SDK, recommended)
* azureblob (deprecated)
* azureblob-sdk (recommended)
* b2
* filesystem (on-disk storage, deprecated)
* filesystem-nio2 (on-disk storage, recommended)
* google-cloud-storage (deprecated)
* google-cloud-storage-sdk (recommended)
* openstack-swift
* rackspace-cloudfiles-uk and rackspace-cloudfiles-us
* s3 (non-Amazon, deprecated)
* transient (in-memory storage, deprecated)
* transient-nio2 (in-memory storage, recommended)
See the wiki for [examples of configurations](https://github.com/gaul/s3proxy/wiki/Storage-backend-examples).
## Assigning buckets to backends
S3Proxy can be configured to assign buckets to different backends with the same
credentials. The configuration in the properties file is as follows:
```
s3proxy.bucket-locator.1=bucket
s3proxy.bucket-locator.2=another-bucket
```
In addition to the explicit names, [glob syntax](https://docs.oracle.com/javase/tutorial/essential/io/fileOps.html#glob) can be used to configure many
buckets for a given backend.
A bucket (or a glob) cannot be assigned to multiple backends.
## Middlewares
S3Proxy can modify its behavior based on middlewares:
* [bucket aliasing](https://github.com/gaul/s3proxy/wiki/Middleware-alias-blobstore)
* [bucket prefix scoping](https://github.com/gaul/s3proxy/wiki/Middleware-prefix-blobstore)
* [bucket locator](https://github.com/gaul/s3proxy/wiki/Middleware-bucket-locator)
* [eventual consistency modeling](https://github.com/gaul/s3proxy/wiki/Middleware---eventual-consistency)
* [large object mocking](https://github.com/gaul/s3proxy/wiki/Middleware-large-object-mocking)
* [latency](https://github.com/gaul/s3proxy/wiki/Middleware-latency)
* [read-only](https://github.com/gaul/s3proxy/wiki/Middleware-read-only)
* [regex rename blobs](https://github.com/gaul/s3proxy/wiki/Middleware-regex)
* [sharded backend containers](https://github.com/gaul/s3proxy/wiki/Middleware-sharded-backend)
* [storage class override](https://github.com/gaul/s3proxy/wiki/Middleware-storage-class-override)
* [user metadata replacer](https://github.com/gaul/s3proxy/wiki/Middleware-user-metadata-replacer)
* [no cache override](https://github.com/gaul/s3proxy/wiki/Middleware-no-cache)
## SSL Support
S3Proxy can listen on HTTPS by setting the `secure-endpoint` and [configuring a keystore](http://wiki.eclipse.org/Jetty/Howto/Configure_SSL#Generating_Keys_and_Certificates_with_JDK_keytool). You can read more about how configure S3Proxy for SSL Support in [the dedicated wiki page](https://github.com/gaul/s3proxy/wiki/SSL-support) with Docker, Kubernetes or simply Java.
## Limitations
S3Proxy has broad compatibility with the S3 API, however, it does not support:
* ACLs other than private and public-read
* BitTorrent hosting
* bucket logging
* bucket policies
* [CORS bucket operations](https://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html#how-do-i-enable-cors) like getting or setting the CORS configuration for a bucket. S3Proxy only supports a static configuration (see below).
* hosting static websites
* object server-side encryption
* object tagging
* object versioning, see [#74](https://github.com/gaul/s3proxy/issues/74)
* POST upload policies, see [#73](https://github.com/gaul/s3proxy/issues/73)
* requester pays buckets
* [select object content](https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectSELECTContent.html)
S3Proxy emulates the following operations:
* conditional PUT object when using If-Match or If-None-Match, unless the `azureblob-sdk` provider is used
* copy multi-part objects, see [#76](https://github.com/gaul/s3proxy/issues/76)
S3Proxy has basic CORS preflight and actual request/response handling. It can be configured within the properties
file (and corresponding ENV variables for Docker):
```
s3proxy.cors-allow-origins=https://example\.com https://.+\.example\.com https://example\.cloud
s3proxy.cors-allow-methods=GET PUT
s3proxy.cors-allow-headers=Accept Content-Type
s3proxy.cors-allow-credential=true
```
CORS cannot be configured per bucket. `s3proxy.cors-allow-all=true` will accept any origin and header.
Actual CORS requests are supported for GET, PUT, POST, HEAD and DELETE methods.
The wiki collects
[compatibility notes](https://github.com/gaul/s3proxy/wiki/Storage-backend-compatibility)
for specific storage backends.
## Support
* [GitHub issues](https://github.com/gaul/s3proxy/issues)
* [Stack Overflow](https://stackoverflow.com/questions/tagged/s3proxy)
* [commercial support](mailto:andrew@gaul.org)
## References
* [Apache jclouds](https://jclouds.apache.org/) provides storage backend support for S3Proxy
* [Ceph s3-tests](https://github.com/ceph/s3-tests) help maintain and improve compatibility with the S3 API
* [fake-s3](https://github.com/jubos/fake-s3), [gofakes3](https://github.com/johannesboyne/gofakes3), [minio](https://github.com/minio/minio), [S3 ninja](https://github.com/scireum/s3ninja), and [s3rver](https://github.com/jamhall/s3rver) provide functionality similar to S3Proxy when using the filesystem backend
* [GlacierProxy](https://github.com/bouncestorage/glacier-proxy) and [SwiftProxy](https://github.com/bouncestorage/swiftproxy) provide similar functionality for the Amazon Glacier and OpenStack Swift APIs
* [s3mock](https://github.com/adobe/S3Mock) - Adobe's s3 mock implementation
* [sbt-s3](https://github.com/localytics/sbt-s3) runs S3Proxy via the Scala Build Tool
* [swift3](https://github.com/openstack/swift3) provides an S3 middleware for OpenStack Swift
* [Zenko](https://www.zenko.io/) provide similar multi-cloud functionality
## License
Copyright (C) 2014-2026 Andrew Gaul
Licensed under the Apache License, Version 2.0
================================================
FILE: docs/Encryption.md
================================================
S3Proxy
# Encryption
## Motivation
The motivation behind this implementation is to provide a fully transparent and secure encryption to the s3 client while having the ability to write into different clouds.
## Cipher mode
The chosen cipher is ```AES/CFB/NoPadding``` because it provides the ability to read from an offset like in the middle of a ```Blob```.
While reading from an offset the decryption process needs to consider the previous 16 bytes of the AES block.
### Key generation
The encryption uses a 128-bit key that will be derived from a given password and salt in combination with random initialization vector that will be stored in each part padding.
## How a blob is encrypted
Every uploaded part get a padding of 64 bytes that includes the necessary information for decryption. The input stream from a s3 client is passed through ```CipherInputStream``` and piped to append the 64 byte part padding at the end the encrypted stream. The encrypted input stream is then processed by the ```BlobStore``` to save the ```Blob```.
| Name | Byte size | Description |
|-----------|-----------|----------------------------------------------------------------|
| Delimiter | 8 byte | The delimiter is used to detect if the ```Blob``` is encrypted |
| IV | 16 byte | AES initialization vector |
| Part | 4 byte | The part number |
| Size | 8 byte | The unencrypted size of the ```Blob``` |
| Version | 2 byte | Version can be used in the future if changes are necessary |
| Reserved | 26 byte | Reserved for future use |
### Multipart handling
A single ```Blob``` can be uploaded by the client into multiple parts. After the completion all parts are concatenated into a single ```Blob```.
This procedure will result in multiple parts and paddings being held by a single ```Blob```.
### Single blob example
```
-------------------------------------
| ENCRYPTED BYTES | PADDING |
-------------------------------------
```
### Multipart blob example
```
-------------------------------------------------------------------------------------
| ENCRYPTED BYTES | PADDING | ENCRYPTED BYTES | PADDING | ENCRYPTED BYTES | PADDING |
-------------------------------------------------------------------------------------
```
## How a blob is decrypted
The decryption is way more complex than the encryption. Decryption process needs to take care of the following circumstances:
- decryption of the entire ```Blob```
- decryption from a specific offset by skipping initial bytes
- decryption of bytes by reading from the end (tail)
- decryption of a specific byte range like middle of the ```Blob```
- decryption of all previous situation by considering a underlying multipart ```Blob```
### Single blob decryption
First the ```BlobMetadata``` is requested to get the encrypted ```Blob``` size. The last 64 bytes of ```PartPadding``` are fetched and inspected to detect if a decryption is necessary.
The cipher is than initialized with the IV and the key.
### Multipart blob decryption
The process is similar to the single ```Blob``` decryption but with the difference that a list of parts is computed by fetching all ```PartPadding``` from end to the beginning.
## Blob suffix
Each stored ```Blob``` will get a suffix named ```.s3enc``` this helps to determine if a ```Blob``` is encrypted. For the s3 client the ```.s3enc``` suffix is not visible and the ```Blob``` size will always show the unencrypted size.
## Tested jClouds provider
- S3
- Minio
- OBS from OpenTelekomCloud
- AWS S3
- Azure
- GCP
- Local
## Limitation
- All blobs are encrypted with the same key that is derived from a given password
- No support for re-encryption
- Returned eTag always differs therefore clients should not verify it
- Decryption of a ```Blob``` will always result in multiple calls against the backend for instance a GET will result in a HEAD + GET because the size of the blob needs to be determined
================================================
FILE: docs/Logging.md
================================================
# Logging
## Configuration
The following environment variables can be used to configure logging
* LOG_LEVEL default value "info" used to configure log level
* LOG_APPENDER default value "STDOUT" produce string formatted logs "CONTAINER" used to produce json formatted logs
================================================
FILE: pom.xml
================================================
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.gaul</groupId>
<artifactId>s3proxy</artifactId>
<version>3.2.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>S3Proxy</name>
<url>https://github.com/gaul/s3proxy</url>
<description>Access other storage backends via the S3 API</description>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<scm>
<connection>scm:git:git@github.com:gaul/s3proxy.git</connection>
<developerConnection>scm:git:git@github.com:gaul/s3proxy.git</developerConnection>
<url>git@github.com:gaul/s3proxy.git</url>
</scm>
<developers>
<developer>
<name>Andrew Gaul</name>
<id>gaul</id>
<email>andrew@gaul.org</email>
</developer>
</developers>
<distributionManagement>
<snapshotRepository>
<id>sonatype-central-portal</id>
<name>Sonatype Central Portal</name>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
</snapshotRepository>
<repository>
<id>sonatype-central-portal</id>
<name>Sonatype Central Portal</name>
<url>https://repo.maven.apache.org/maven2/</url>
</repository>
</distributionManagement>
<profiles>
<profile>
<id>release</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>3.2.8</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<build>
<extensions>
<extension>
<groupId>eu.maveniverse.maven.njord</groupId>
<artifactId>extension</artifactId>
<version>${njord.version}</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>eu.maveniverse.maven.plugins</groupId>
<artifactId>njord</artifactId>
<version>${njord.version}</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.6.2</version>
<executions>
<execution>
<id>enforce-maven</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireMavenVersion>
<version>3.6.3</version>
</requireMavenVersion>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.5.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId>
<version>3.1.4</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>3.1.4</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<id>check</id>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
<configuration>
<configLocation>src/main/resources/checkstyle.xml</configLocation>
<headerLocation>src/main/resources/copyright_header.txt</headerLocation>
<includeTestSourceDirectory>true</includeTestSourceDirectory>
<violationSeverity>warning</violationSeverity>
<failOnViolation>true</failOnViolation>
</configuration>
<dependencies>
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>12.3.1</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.5.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.15.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<showDeprecation>true</showDeprecation>
<showWarnings>true</showWarnings>
<fork>true</fork>
<compilerArgs>
<arg>-Xlint</arg>
<arg>-XDcompilePolicy=simple</arg>
<arg>--should-stop=ifError=FLOW</arg>
<arg>-Xplugin:ErrorProne -Xep:JavaUtilDate:OFF -Xep:DefaultCharset:OFF -Xep:StringCaseLocaleUsage:OFF -Xep:ProtectedMembersInFinalClass:OFF -Xep:JavaTimeDefaultTimeZone:OFF</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.1.1</version>
</path>
<path>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
<version>2.36.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>io.github.git-commit-id</groupId>
<artifactId>git-commit-id-maven-plugin</artifactId>
<version>9.0.2</version>
<executions>
<execution>
<goals>
<goal>revision</goal>
</goals>
</execution>
</executions>
<configuration>
<generateGitPropertiesFile>true</generateGitPropertiesFile>
<includeOnlyProperties>
<includeOnlyProperty>git.commit.id.abbrev</includeOnlyProperty>
<includeOnlyProperty>git.commit.id</includeOnlyProperty>
</includeOnlyProperties>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.12.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
<configuration>
<doclint>all,-missing</doclint>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.2</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<filters>
<filter>
<artifact>org.eclipse.jetty:*</artifact>
<excludes>
<exclude>META-INF/MANIFEST.MF</exclude>
<exclude>META-INF/LICENSE</exclude>
<exclude>META-INF/NOTICE.txt</exclude>
<exclude>about.html</exclude>
</excludes>
</filter>
<filter>
<artifact>org.eclipse.jetty.ee10:*</artifact>
<excludes>
<exclude>META-INF/MANIFEST.MF</exclude>
<exclude>META-INF/LICENSE</exclude>
<exclude>META-INF/NOTICE.txt</exclude>
<exclude>about.html</exclude>
</excludes>
</filter>
</filters>
<artifactSet>
<includes>
<include>org.eclipse.jetty:*</include>
<include>org.eclipse.jetty.ee10:*</include>
</includes>
</artifactSet>
<relocations>
<relocation>
<pattern>org.eclipse.jetty</pattern>
<shadedPattern>${shade.prefix}.org.eclipse.jetty</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<descriptors>
<descriptor>src/main/assembly/jar-with-dependencies.xml</descriptor>
</descriptors>
<archive>
<manifest>
<mainClass>org.gaul.s3proxy.Main</mainClass>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.4.0</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire.version}</version>
<dependencies>
<dependency>
<groupId>org.apache.maven.surefire</groupId>
<artifactId>surefire-junit47</artifactId>
<version>${surefire.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven.surefire</groupId>
<artifactId>surefire-junit-platform</artifactId>
<version>${surefire.version}</version>
</dependency>
</dependencies>
<configuration>
<parallel>classes</parallel>
<threadCount>1</threadCount>
<argLine>-Xmx512m</argLine>
<redirectTestOutputToFile>true</redirectTestOutputToFile>
<forkedProcessTimeoutInSeconds>1800</forkedProcessTimeoutInSeconds>
<runOrder>random</runOrder>
<trimStackTrace>false</trimStackTrace>
<properties>
<property>
<name>junit</name>
<value>false</value>
</property>
</properties>
</configuration>
</plugin>
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>4.9.8.3</version>
<configuration>
<effort>Max</effort>
<omitVisitors>CrossSiteScripting,DefaultEncodingDetector,FindNullDeref</omitVisitors>
<plugins>
<plugin>
<groupId>jp.skypencil.findbugs.slf4j</groupId>
<artifactId>bug-pattern</artifactId>
<version>1.5.0</version>
</plugin>
</plugins>
</configuration>
</plugin>
<plugin>
<groupId>org.skife.maven</groupId>
<artifactId>really-executable-jar-maven-plugin</artifactId>
<version>2.1.1</version>
<configuration>
<inputFile>target/s3proxy-${project.version}-jar-with-dependencies.jar</inputFile>
<programFile>s3proxy</programFile>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>really-executable-jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.gaul</groupId>
<artifactId>modernizer-maven-plugin</artifactId>
<version>${modernizer.version}</version>
<executions>
<execution>
<id>modernizer</id>
<phase>verify</phase>
<goals>
<goal>modernizer</goal>
</goals>
</execution>
</executions>
<configuration>
<javaVersion>${java.version}</javaVersion>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>17</java.version>
<aws-sdk.version>1.12.797</aws-sdk.version>
<aws-sdkv2.version>2.42.31</aws-sdkv2.version>
<jclouds.version>2.7.0</jclouds.version>
<jetty.version>12.1.8</jetty.version>
<modernizer.version>3.3.0</modernizer.version>
<njord.version>0.7.5</njord.version>
<opentelemetry.version>1.60.1</opentelemetry.version>
<opentelemetry-semconv.version>1.40.0</opentelemetry-semconv.version>
<slf4j.version>2.0.17</slf4j.version>
<shade.prefix>${project.groupId}.shaded</shade.prefix>
<surefire.version>3.5.5</surefire.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>2.21.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-bom</artifactId>
<version>${opentelemetry.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>6.0.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-prometheus</artifactId>
<version>1.60.1-alpha</version>
</dependency>
<dependency>
<groupId>io.opentelemetry.semconv</groupId>
<artifactId>opentelemetry-semconv</artifactId>
<version>${opentelemetry-semconv.version}</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>${aws-sdk.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-sts</artifactId>
<version>${aws-sdk.version}</version>
</dependency>
<dependency>
<groupId>args4j</groupId>
<artifactId>args4j</artifactId>
<version>2.37</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.32</version>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-storage</artifactId>
<version>2.64.1</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-storage-blob</artifactId>
<version>12.32.0</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.18.2</version>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.5.0-jre</version>
</dependency>
<dependency>
<groupId>com.google.jimfs</groupId>
<artifactId>jimfs</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<!-- Required for S3ProxyRule -->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>6.0.3</version>
<!-- Required for S3ProxyExtension -->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.21.2</version>
</dependency>
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-annotations</artifactId>
<version>4.9.8</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.jclouds</groupId>
<artifactId>jclouds-allblobstore</artifactId>
<version>${jclouds.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jclouds.api</groupId>
<artifactId>filesystem</artifactId>
<version>${jclouds.version}</version>
</dependency>
<dependency>
<groupId>org.apache.jclouds.driver</groupId>
<artifactId>jclouds-slf4j</artifactId>
<version>${jclouds.version}</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
<!-- we need to use the same version as in jclouds because we pull in their tests -->
<version>3.27.7</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.ee10</groupId>
<artifactId>jetty-ee10-servlet</artifactId>
<version>${jetty.version}</version>
</dependency>
<dependency>
<groupId>org.gaul</groupId>
<artifactId>modernizer-maven-annotations</artifactId>
<version>${modernizer.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- tests dependencies -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>${aws-sdkv2.version}</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>sts</artifactId>
<version>${aws-sdkv2.version}</version>
</dependency>
</dependencies>
</project>
================================================
FILE: src/main/assembly/jar-with-dependencies.xml
================================================
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
<id>jar-with-dependencies</id>
<formats>
<format>jar</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<containerDescriptorHandlers>
<containerDescriptorHandler>
<handlerName>metaInf-services</handlerName>
</containerDescriptorHandler>
</containerDescriptorHandlers>
<dependencySets>
<dependencySet>
<excludes>
<exclude>org.eclipse.jetty:*</exclude>
<exclude>org.eclipse.jetty.ee10:*</exclude>
</excludes>
<outputDirectory>/</outputDirectory>
<useProjectArtifact>true</useProjectArtifact>
<unpack>true</unpack>
<scope>runtime</scope>
</dependencySet>
</dependencySets>
<fileSets>
<fileSet>
<directory>${project.basedir}/src/main/config</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>logback.xml</include>
</includes>
<useDefaultExcludes>true</useDefaultExcludes>
</fileSet>
</fileSets>
</assembly>
================================================
FILE: src/main/config/logback.xml
================================================
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[s3proxy] %.-1p %d{MM-dd HH:mm:ss.SSS} %t %c{30}:%L %X{clientId}|%X{sessionId}:%X{messageId}:%X{fileId}] %m%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>${LOG_LEVEL:-info}</level>
</filter>
</appender>
<appender name="CONTAINER" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.JsonEncoder"/>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>${LOG_LEVEL:-info}</level>
</filter>
</appender>
<logger name="org.eclipse.jetty" level="${JETTY_LOG_LEVEL:-info}" />
<logger name="org.gaul.shaded.org.eclipse.jetty" level="${JETTY_LOG_LEVEL:-info}" />
<root level="${LOG_LEVEL:-info}">
<appender-ref ref="${LOG_APPENDER:-STDOUT}" />
</root>
</configuration>
================================================
FILE: src/main/java/org/gaul/s3proxy/AccessControlPolicy.java
================================================
/*
* Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gaul.s3proxy;
import java.util.Collection;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.google.common.base.MoreObjects;
/** Represent an Amazon AccessControlPolicy for a container or object. */
// CHECKSTYLE:OFF
final class AccessControlPolicy {
@JacksonXmlProperty(localName = "Owner")
Owner owner;
@JacksonXmlProperty(localName = "AccessControlList")
AccessControlList aclList;
@Override
public String toString() {
return MoreObjects.toStringHelper(AccessControlList.class)
.add("owner", owner)
.add("aclList", aclList)
.toString();
}
static final class Owner {
@JacksonXmlProperty(localName = "ID")
String id;
@JacksonXmlProperty(localName = "DisplayName")
String displayName;
@Override
public String toString() {
return MoreObjects.toStringHelper(Owner.class)
.add("id", id)
.add("displayName", displayName)
.toString();
}
}
static final class AccessControlList {
@JacksonXmlProperty(localName = "Grant")
@JacksonXmlElementWrapper(useWrapping = false)
Collection<Grant> grants;
@Override
public String toString() {
return MoreObjects.toStringHelper(AccessControlList.class)
.add("grants", grants)
.toString();
}
static final class Grant {
@JacksonXmlProperty(localName = "Grantee")
Grantee grantee;
@JacksonXmlProperty(localName = "Permission")
String permission;
@Override
public String toString() {
return MoreObjects.toStringHelper(Grant.class)
.add("grantee", grantee)
.add("permission", permission)
.toString();
}
static final class Grantee {
@JacksonXmlProperty(namespace = "xsi", localName = "type",
isAttribute = true)
String type;
@JacksonXmlProperty(localName = "ID")
String id;
@JacksonXmlProperty(localName = "DisplayName")
String displayName;
@JacksonXmlProperty(localName = "EmailAddress")
String emailAddress;
@JacksonXmlProperty(localName = "URI")
String uri;
@Override
public String toString() {
return MoreObjects.toStringHelper(Grantee.class)
.add("type", type)
.add("id", id)
.add("displayName", displayName)
.add("emailAddress", emailAddress)
.add("uri", uri)
.toString();
}
}
}
}
}
// CHECKSTYLE:ON
================================================
FILE: src/main/java/org/gaul/s3proxy/AliasBlobStore.java
================================================
/*
* Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gaul.s3proxy;
import static java.util.Objects.requireNonNull;
import static com.google.common.base.Preconditions.checkArgument;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.domain.BlobMetadata;
import org.jclouds.blobstore.domain.ContainerAccess;
import org.jclouds.blobstore.domain.MultipartPart;
import org.jclouds.blobstore.domain.MultipartUpload;
import org.jclouds.blobstore.domain.MutableStorageMetadata;
import org.jclouds.blobstore.domain.PageSet;
import org.jclouds.blobstore.domain.StorageMetadata;
import org.jclouds.blobstore.domain.internal.MutableStorageMetadataImpl;
import org.jclouds.blobstore.domain.internal.PageSetImpl;
import org.jclouds.blobstore.options.CopyOptions;
import org.jclouds.blobstore.options.CreateContainerOptions;
import org.jclouds.blobstore.options.GetOptions;
import org.jclouds.blobstore.options.ListContainerOptions;
import org.jclouds.blobstore.options.PutOptions;
import org.jclouds.blobstore.util.ForwardingBlobStore;
import org.jclouds.domain.Location;
import org.jclouds.io.Payload;
/**
* This class implements a middleware to alias buckets to a different name.
* The aliases are configured as:
* s3proxy.alias-blobstore.<alias name> = <backend bucket>
*
* The aliases appear in bucket listings if the configured
* backend buckets are present. Requests for all other buckets are unaffected.
*/
public final class AliasBlobStore extends ForwardingBlobStore {
private final BiMap<String, String> aliases;
private AliasBlobStore(BlobStore delegate,
BiMap<String, String> aliases) {
super(delegate);
this.aliases = requireNonNull(aliases);
}
static BlobStore newAliasBlobStore(BlobStore delegate,
BiMap<String, String> aliases) {
return new AliasBlobStore(delegate, aliases);
}
private MultipartUpload getDelegateMpu(MultipartUpload mpu) {
return MultipartUpload.create(
getContainer(mpu.containerName()),
mpu.blobName(),
mpu.id(),
mpu.blobMetadata(),
mpu.putOptions());
}
public static ImmutableBiMap<String, String> parseAliases(
Properties properties) {
Map<String, String> backendBuckets = new HashMap<>();
for (String key : properties.stringPropertyNames()) {
if (key.startsWith(S3ProxyConstants.PROPERTY_ALIAS_BLOBSTORE)) {
String virtualBucket = key.substring(
S3ProxyConstants.PROPERTY_ALIAS_BLOBSTORE.length() + 1);
String backendBucket = properties.getProperty(key);
checkArgument(
!backendBuckets.containsKey(backendBucket),
"Backend bucket %s is aliased twice",
backendBucket);
backendBuckets.put(backendBucket, virtualBucket);
}
}
return ImmutableBiMap.copyOf(backendBuckets).inverse();
}
private String getContainer(String container) {
return this.aliases.getOrDefault(container, container);
}
@Override
public boolean createContainerInLocation(Location location,
String container) {
return this.delegate().createContainerInLocation(location,
getContainer(container));
}
@Override
public boolean createContainerInLocation(
Location location, String container,
CreateContainerOptions options) {
return delegate().createContainerInLocation(
location, getContainer(container), options);
}
@Override
public boolean containerExists(String container) {
return delegate().containerExists(getContainer(container));
}
@Override
public ContainerAccess getContainerAccess(String container) {
return delegate().getContainerAccess(getContainer(container));
}
@Override
public void setContainerAccess(String container,
ContainerAccess containerAccess) {
delegate().setContainerAccess(getContainer(container), containerAccess);
}
@Override
public PageSet<? extends StorageMetadata> list() {
PageSet<? extends StorageMetadata> upstream = this.delegate().list();
var results = new ImmutableList.Builder<StorageMetadata>();
for (StorageMetadata sm : upstream) {
if (aliases.containsValue(sm.getName())) {
MutableStorageMetadata bucketAlias =
new MutableStorageMetadataImpl();
bucketAlias.setName(aliases.inverse().get(sm.getName()));
bucketAlias.setCreationDate(sm.getCreationDate());
bucketAlias.setETag(sm.getETag());
bucketAlias.setId(sm.getProviderId());
bucketAlias.setLastModified(sm.getLastModified());
bucketAlias.setLocation(sm.getLocation());
bucketAlias.setSize(sm.getSize());
bucketAlias.setTier(sm.getTier());
bucketAlias.setType(sm.getType());
// TODO: the URI should be rewritten to use the alias
bucketAlias.setUri(sm.getUri());
bucketAlias.setUserMetadata(sm.getUserMetadata());
results.add(bucketAlias);
} else {
results.add(sm);
}
}
return new PageSetImpl<>(results.build(), upstream.getNextMarker());
}
@Override
public PageSet<? extends StorageMetadata> list(String container) {
return delegate().list(getContainer(container));
}
@Override
public PageSet<? extends StorageMetadata> list(
String container, ListContainerOptions options) {
return delegate().list(getContainer(container), options);
}
@Override
public void clearContainer(String container) {
delegate().clearContainer(getContainer(container));
}
@Override
public void clearContainer(String container, ListContainerOptions options) {
delegate().clearContainer(getContainer(container), options);
}
@Override
public void deleteContainer(String container) {
delegate().deleteContainer(getContainer(container));
}
@Override
public boolean deleteContainerIfEmpty(String container) {
return delegate().deleteContainerIfEmpty(getContainer(container));
}
@Override
public boolean blobExists(String container, String name) {
return delegate().blobExists(getContainer(container), name);
}
@Override
public BlobMetadata blobMetadata(String container, String name) {
return delegate().blobMetadata(getContainer(container), name);
}
@Override
public Blob getBlob(String containerName, String blobName) {
return delegate().getBlob(getContainer(containerName), blobName);
}
@Override
public Blob getBlob(String containerName, String blobName,
GetOptions getOptions) {
return delegate().getBlob(getContainer(containerName), blobName,
getOptions);
}
@Override
public String putBlob(String containerName, Blob blob) {
return delegate().putBlob(getContainer(containerName), blob);
}
@Override
public String putBlob(final String containerName, Blob blob,
final PutOptions options) {
return delegate().putBlob(getContainer(containerName), blob,
options);
}
@Override
public void removeBlob(final String containerName, final String blobName) {
delegate().removeBlob(getContainer(containerName), blobName);
}
@Override
public void removeBlobs(final String containerName,
final Iterable<String> blobNames) {
delegate().removeBlobs(getContainer(containerName), blobNames);
}
@Override
public String copyBlob(final String fromContainer, final String fromName,
final String toContainer, final String toName,
final CopyOptions options) {
return delegate().copyBlob(getContainer(fromContainer), fromName,
getContainer(toContainer), toName, options);
}
@Override
public MultipartUpload initiateMultipartUpload(
String container, BlobMetadata blobMetadata, PutOptions options) {
MultipartUpload mpu = delegate().initiateMultipartUpload(
getContainer(container), blobMetadata, options);
return MultipartUpload.create(container, blobMetadata.getName(),
mpu.id(), mpu.blobMetadata(), mpu.putOptions());
}
@Override
public void abortMultipartUpload(MultipartUpload mpu) {
delegate().abortMultipartUpload(getDelegateMpu(mpu));
}
@Override
public String completeMultipartUpload(final MultipartUpload mpu,
final List<MultipartPart> parts) {
return delegate().completeMultipartUpload(getDelegateMpu(mpu), parts);
}
@Override
public MultipartPart uploadMultipartPart(MultipartUpload mpu,
int partNumber, Payload payload) {
return delegate().uploadMultipartPart(getDelegateMpu(mpu), partNumber,
payload);
}
}
================================================
FILE: src/main/java/org/gaul/s3proxy/AuthenticationType.java
================================================
/*
* Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gaul.s3proxy;
import com.google.common.base.CaseFormat;
public enum AuthenticationType {
AWS_V2,
AWS_V4,
AWS_V2_OR_V4,
NONE;
static AuthenticationType fromString(String string) {
return AuthenticationType.valueOf(CaseFormat.LOWER_HYPHEN.to(
CaseFormat.UPPER_UNDERSCORE, string));
}
}
================================================
FILE: src/main/java/org/gaul/s3proxy/AwsHttpHeaders.java
================================================
/*
* Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gaul.s3proxy;
final class AwsHttpHeaders {
static final String ACL = "x-amz-acl";
static final String API_VERSION = "x-amz-api-version";
static final String CHECKSUM_ALGORITHM = "x-amz-checksum-algorithm";
static final String CHECKSUM_CRC32 = "x-amz-checksum-crc32";
static final String CHECKSUM_CRC32C = "x-amz-checksum-crc32c";
static final String CHECKSUM_CRC64NVME = "x-amz-checksum-crc64nvme";
static final String CHECKSUM_MODE = "x-amz-checksum-mode";
static final String CHECKSUM_SHA1 = "x-amz-checksum-sha1";
static final String CHECKSUM_SHA256 = "x-amz-checksum-sha256";
static final String CONTENT_SHA256 = "x-amz-content-sha256";
static final String COPY_SOURCE = "x-amz-copy-source";
static final String COPY_SOURCE_IF_MATCH = "x-amz-copy-source-if-match";
static final String COPY_SOURCE_IF_MODIFIED_SINCE =
"x-amz-copy-source-if-modified-since";
static final String COPY_SOURCE_IF_NONE_MATCH =
"x-amz-copy-source-if-none-match";
static final String COPY_SOURCE_IF_UNMODIFIED_SINCE =
"x-amz-copy-source-if-unmodified-since";
static final String COPY_SOURCE_RANGE = "x-amz-copy-source-range";
static final String DATE = "x-amz-date";
static final String DECODED_CONTENT_LENGTH =
"x-amz-decoded-content-length";
static final String METADATA_DIRECTIVE = "x-amz-metadata-directive";
static final String REQUEST_ID = "x-amz-request-id";
static final String SDK_CHECKSUM_ALGORITHM = "x-amz-sdk-checksum-algorithm";
static final String STORAGE_CLASS = "x-amz-storage-class";
static final String TRAILER = "x-amz-trailer";
static final String TRANSFER_ENCODING = "x-amz-te";
static final String USER_AGENT = "x-amz-user-agent";
private AwsHttpHeaders() {
throw new AssertionError("intentionally unimplemented");
}
}
================================================
FILE: src/main/java/org/gaul/s3proxy/AwsSignature.java
================================================
/*
* Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gaul.s3proxy;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.SortedSetMultimap;
import com.google.common.collect.TreeMultimap;
import com.google.common.io.BaseEncoding;
import com.google.common.net.HttpHeaders;
import com.google.common.net.PercentEscaper;
import jakarta.servlet.http.HttpServletRequest;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
final class AwsSignature {
private static final Logger logger = LoggerFactory.getLogger(
AwsSignature.class);
private static final PercentEscaper AWS_URL_PARAMETER_ESCAPER =
new PercentEscaper("-_.~", false);
private static final Set<String> SIGNED_SUBRESOURCES = Set.of(
"acl",
"delete",
"lifecycle",
"location",
"logging",
"notification",
"partNumber",
"policy",
"requestPayment",
"response-cache-control",
"response-content-disposition",
"response-content-encoding",
"response-content-language",
"response-content-type",
"response-expires",
"torrent",
"uploadId",
"uploads",
"versionId",
"versioning",
"versions",
"website"
);
private static final Pattern REPEATING_WHITESPACE = Pattern.compile("\\s+");
private AwsSignature() { }
/**
* Create Amazon V2 signature. Reference:
* http://docs.aws.amazon.com/general/latest/gr/signature-version-2.html
*/
static String createAuthorizationSignature(
HttpServletRequest request, String uri, String credential,
boolean queryAuth, boolean bothDateHeader) {
// sort Amazon headers
SortedSetMultimap<String, String> canonicalizedHeaders =
TreeMultimap.create();
for (String headerName : Collections.list(request.getHeaderNames())) {
Collection<String> headerValues = Collections.list(
request.getHeaders(headerName));
headerName = headerName.toLowerCase();
if (!headerName.startsWith("x-amz-") || (bothDateHeader &&
headerName.equalsIgnoreCase(AwsHttpHeaders.DATE))) {
continue;
}
if (headerValues.isEmpty()) {
canonicalizedHeaders.put(headerName, "");
}
for (String headerValue : headerValues) {
canonicalizedHeaders.put(headerName,
Strings.nullToEmpty(headerValue));
}
}
// Build string to sign
var builder = new StringBuilder()
.append(request.getMethod())
.append('\n')
.append(Strings.nullToEmpty(request.getHeader(
HttpHeaders.CONTENT_MD5)))
.append('\n')
.append(Strings.nullToEmpty(request.getHeader(
HttpHeaders.CONTENT_TYPE)))
.append('\n');
String expires = request.getParameter("Expires");
if (queryAuth) {
// If expires is not nil, then it is query string sign
// If expires is nil, maybe also query string sign
// So should check other accessid param, presign to judge.
// not the expires
builder.append(Strings.nullToEmpty(expires));
} else {
if (!bothDateHeader) {
if (canonicalizedHeaders.containsKey(AwsHttpHeaders.DATE)) {
builder.append("");
} else {
builder.append(request.getHeader(HttpHeaders.DATE));
}
} else {
if (!canonicalizedHeaders.containsKey(AwsHttpHeaders.DATE)) {
builder.append(request.getHeader(AwsHttpHeaders.DATE));
} else {
// panic
}
}
}
builder.append('\n');
for (var entry : canonicalizedHeaders.entries()) {
builder.append(entry.getKey()).append(':')
.append(entry.getValue()).append('\n');
}
builder.append(uri);
char separator = '?';
List<String> subresources = Collections.list(
request.getParameterNames());
Collections.sort(subresources);
for (String subresource : subresources) {
if (SIGNED_SUBRESOURCES.contains(subresource)) {
builder.append(separator).append(subresource);
String value = request.getParameter(subresource);
if (!"".equals(value)) {
builder.append('=').append(value);
}
separator = '&';
}
}
String stringToSign = builder.toString();
logger.trace("stringToSign: {}", stringToSign);
// Sign string
Mac mac;
try {
mac = Mac.getInstance("HmacSHA1");
mac.init(new SecretKeySpec(credential.getBytes(
StandardCharsets.UTF_8), "HmacSHA1"));
} catch (InvalidKeyException | NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
return Base64.getEncoder().encodeToString(mac.doFinal(
stringToSign.getBytes(StandardCharsets.UTF_8)));
}
private static byte[] signMessage(byte[] data, byte[] key, String algorithm)
throws InvalidKeyException, NoSuchAlgorithmException {
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(key, algorithm));
return mac.doFinal(data);
}
/**
* Derive the AWS SigV4 signing key from the credential and auth header.
*/
static byte[] deriveSigningKeyV4(S3AuthorizationHeader authHeader,
String credential)
throws InvalidKeyException, NoSuchAlgorithmException {
String algorithm = authHeader.getHmacAlgorithm();
byte[] dateKey = signMessage(
authHeader.getDate().getBytes(StandardCharsets.UTF_8),
("AWS4" + credential).getBytes(StandardCharsets.UTF_8),
algorithm);
byte[] dateRegionKey = signMessage(
authHeader.getRegion().getBytes(StandardCharsets.UTF_8),
dateKey,
algorithm);
byte[] dateRegionServiceKey = signMessage(
authHeader.getService().getBytes(StandardCharsets.UTF_8),
dateRegionKey, algorithm);
return signMessage(
"aws4_request".getBytes(StandardCharsets.UTF_8),
dateRegionServiceKey, algorithm);
}
private static String getMessageDigest(byte[] payload, String algorithm)
throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance(algorithm);
byte[] hash = md.digest(payload);
return BaseEncoding.base16().lowerCase().encode(hash);
}
@Nullable
private static List<String> extractSignedHeaders(String authorization) {
int index = authorization.indexOf("SignedHeaders=");
if (index < 0) {
return null;
}
int endSigned = authorization.indexOf(',', index);
if (endSigned < 0) {
return null;
}
int startHeaders = authorization.indexOf('=', index);
return Splitter.on(';').splitToList(authorization.substring(
startHeaders + 1, endSigned));
}
private static String buildCanonicalHeaders(HttpServletRequest request,
List<String> signedHeaders) {
List<String> headers = new ArrayList<>(
/*initialCapacity=*/ signedHeaders.size());
for (String header : signedHeaders) {
headers.add(header.toLowerCase());
}
Collections.sort(headers);
var headersWithValues = new StringBuilder();
boolean firstHeader = true;
for (String header : headers) {
if (firstHeader) {
firstHeader = false;
} else {
headersWithValues.append('\n');
}
headersWithValues.append(header);
headersWithValues.append(':');
boolean firstValue = true;
for (String value : Collections.list(request.getHeaders(header))) {
if (firstValue) {
firstValue = false;
} else {
headersWithValues.append(',');
}
value = value.trim();
if (!value.startsWith("\"")) {
value = REPEATING_WHITESPACE.matcher(value).replaceAll(" ");
}
headersWithValues.append(value);
}
}
return headersWithValues.toString();
}
private static String buildCanonicalQueryString(
HttpServletRequest request) {
// The parameters are required to be sorted
List<String> parameters = Collections.list(request.getParameterNames());
Collections.sort(parameters);
List<String> queryParameters = new ArrayList<>();
for (String key : parameters) {
if (key.equals("X-Amz-Signature")) {
continue;
}
// re-encode keys and values in AWS normalized form
String value = request.getParameter(key);
queryParameters.add(AWS_URL_PARAMETER_ESCAPER.escape(key) +
"=" + AWS_URL_PARAMETER_ESCAPER.escape(value));
}
return Joiner.on("&").join(queryParameters);
}
private static String createCanonicalRequest(HttpServletRequest request,
String uri, byte[] payload,
String hashAlgorithm)
throws IOException, NoSuchAlgorithmException {
String authorizationHeader = request.getHeader("Authorization");
String xAmzContentSha256 = request.getHeader(
AwsHttpHeaders.CONTENT_SHA256);
if (xAmzContentSha256 == null) {
xAmzContentSha256 = request.getParameter("X-Amz-SignedHeaders");
}
String digest;
if (authorizationHeader == null) {
digest = "UNSIGNED-PAYLOAD";
} else if ("STREAMING-AWS4-HMAC-SHA256-PAYLOAD".equals(
xAmzContentSha256)) {
digest = "STREAMING-AWS4-HMAC-SHA256-PAYLOAD";
} else if ("STREAMING-UNSIGNED-PAYLOAD-TRAILER".equals(xAmzContentSha256)) {
digest = "STREAMING-UNSIGNED-PAYLOAD-TRAILER";
} else if ("UNSIGNED-PAYLOAD".equals(xAmzContentSha256)) {
digest = "UNSIGNED-PAYLOAD";
} else {
digest = getMessageDigest(payload, hashAlgorithm);
}
List<String> signedHeaders;
if (authorizationHeader != null) {
signedHeaders = extractSignedHeaders(authorizationHeader);
} else {
signedHeaders = Splitter.on(';').splitToList(request.getParameter(
"X-Amz-SignedHeaders"));
}
/*
* CORS Preflight
*
* The signature is based on the canonical request, which includes the
* HTTP Method.
* For presigned URLs, the method must be replaced for OPTIONS request
* to match
*/
String method = request.getMethod();
if ("OPTIONS".equals(method)) {
String corsMethod = request.getHeader(
HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD);
if (corsMethod != null) {
method = corsMethod;
}
}
String canonicalRequest = Joiner.on("\n").join(
method,
uri,
buildCanonicalQueryString(request),
buildCanonicalHeaders(request, signedHeaders) + "\n",
Joiner.on(';').join(signedHeaders),
digest);
return getMessageDigest(
canonicalRequest.getBytes(StandardCharsets.UTF_8),
hashAlgorithm);
}
/**
* Create v4 signature. Reference:
* http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
*/
static String createAuthorizationSignatureV4(
HttpServletRequest request, S3AuthorizationHeader authHeader,
byte[] payload, String uri, String credential)
throws InvalidKeyException, IOException, NoSuchAlgorithmException,
S3Exception {
String canonicalRequest = createCanonicalRequest(request, uri, payload,
authHeader.getHashAlgorithm());
String algorithm = authHeader.getHmacAlgorithm();
byte[] signingKey = deriveSigningKeyV4(authHeader, credential);
String date = request.getHeader(AwsHttpHeaders.DATE);
if (date == null) {
date = request.getParameter("X-Amz-Date");
}
String signatureString = "AWS4-HMAC-SHA256\n" +
date + "\n" +
authHeader.getDate() + "/" + authHeader.getRegion() +
"/s3/aws4_request\n" +
canonicalRequest;
byte[] signature = signMessage(
signatureString.getBytes(StandardCharsets.UTF_8),
signingKey, algorithm);
return BaseEncoding.base16().lowerCase().encode(signature);
}
}
================================================
FILE: src/main/java/org/gaul/s3proxy/BlobStoreLocator.java
================================================
/*
* Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gaul.s3proxy;
import java.util.Map;
import org.jclouds.blobstore.BlobStore;
public interface BlobStoreLocator {
Map.Entry<String, BlobStore> locateBlobStore(String identity,
String container, String blob);
}
================================================
FILE: src/main/java/org/gaul/s3proxy/CaseInsensitiveImmutableMultimap.java
================================================
/*
* Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gaul.s3proxy;
import java.util.Collection;
import com.google.common.collect.ForwardingMultimap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
final class CaseInsensitiveImmutableMultimap
extends ForwardingMultimap<String, String> {
private final Multimap<String, String> inner;
CaseInsensitiveImmutableMultimap(Multimap<String, String> map) {
var builder = ImmutableMultimap.<String, String>builder();
for (var entry : map.entries()) {
builder.put(lower(entry.getKey()), entry.getValue());
}
this.inner = builder.build();
}
@Override
protected Multimap<String, String> delegate() {
return inner;
}
@Override
public Collection<String> get(String key) {
return inner.get(lower(key));
}
private static String lower(String key) {
return key == null ? null : key.toLowerCase();
}
}
================================================
FILE: src/main/java/org/gaul/s3proxy/ChunkedInputStream.java
================================================
/*
* Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gaul.s3proxy;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import com.google.common.io.BaseEncoding;
import com.google.common.io.ByteStreams;
import org.jspecify.annotations.Nullable;
/**
* Parse an AWS v4 signature chunked stream. Reference:
* https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html
*/
final class ChunkedInputStream extends FilterInputStream {
private static final int MAX_LINE_LENGTH = 4096;
private static final String EMPTY_SHA256 =
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
private byte[] chunk;
private int currentIndex;
private int currentLength;
private String currentSignature;
private final int maxChunkSize;
private final Hasher hasher;
private final byte @Nullable [] signingKey;
@Nullable private final String hmacAlgorithm;
@Nullable private final String timestamp;
@Nullable private final String scope;
@Nullable private String previousSignature;
ChunkedInputStream(InputStream is, int maxChunkSize) {
super(is);
this.maxChunkSize = maxChunkSize;
hasher = null;
signingKey = null;
hmacAlgorithm = null;
timestamp = null;
scope = null;
}
@SuppressWarnings("deprecation")
ChunkedInputStream(InputStream is, int maxChunkSize,
@Nullable String trailer) {
super(is);
this.maxChunkSize = maxChunkSize;
if ("x-amz-checksum-crc32".equals(trailer)) {
hasher = Hashing.crc32().newHasher();
} else if ("x-amz-checksum-crc32c".equals(trailer)) {
hasher = Hashing.crc32c().newHasher();
} else if ("x-amz-checksum-sha1".equals(trailer)) {
hasher = Hashing.sha1().newHasher();
} else if ("x-amz-checksum-sha256".equals(trailer)) {
hasher = Hashing.sha256().newHasher();
} else {
// TODO: Guava does not support x-amz-checksum-crc64nvme
hasher = null;
}
signingKey = null;
hmacAlgorithm = null;
timestamp = null;
scope = null;
}
/**
* Construct a chunked stream that verifies the per-chunk signature chain
* used by STREAMING-AWS4-HMAC-SHA256-PAYLOAD.
*
* @param seedSignature the Authorization header signature (hex-encoded)
* @param signingKey the AWS SigV4 signing key
* @param hmacAlgorithm HMAC algorithm name (e.g. "HmacSHA256")
* @param timestamp full ISO8601 request timestamp (x-amz-date)
* @param scope credential scope (date/region/service/aws4_request)
*/
ChunkedInputStream(InputStream is, int maxChunkSize,
String seedSignature, byte[] signingKey, String hmacAlgorithm,
String timestamp, String scope) {
super(is);
this.maxChunkSize = maxChunkSize;
this.hasher = null;
this.signingKey = signingKey.clone();
this.hmacAlgorithm = hmacAlgorithm;
this.timestamp = timestamp;
this.scope = scope;
this.previousSignature = seedSignature;
}
@Override
public int read() throws IOException {
while (currentIndex == currentLength) {
String line = readLine(in);
if (line.equals("")) {
return -1;
}
String[] parts = line.split(";", 2);
if (parts[0].startsWith("x-amz-checksum-")) {
String[] checksumParts = parts[0].split(":", 2);
var expectedHash = checksumParts[1];
var actualHash = switch (checksumParts[0]) {
case "x-amz-checksum-crc32", "x-amz-checksum-crc32c" -> ByteBuffer.allocate(4).putInt(hasher.hash().asInt()).array(); // Use big-endian to match AWS
case "x-amz-checksum-sha1", "x-amz-checksum-sha256" -> hasher.hash().asBytes();
default -> throw new IllegalArgumentException("Unknown value: " + checksumParts[0]);
};
if (!expectedHash.equals(Base64.getEncoder().encodeToString(actualHash))) {
throw new IOException(new S3Exception(S3ErrorCode.BAD_DIGEST));
}
currentLength = 0;
} else {
currentLength = Integer.parseInt(parts[0], 16);
if (currentLength < 0 || currentLength > maxChunkSize) {
throw new IOException(
"chunk size exceeds maximum: " + currentLength);
}
}
if (parts.length > 1) {
String sigPart = parts[1];
int eq = sigPart.indexOf('=');
currentSignature = eq >= 0 ? sigPart.substring(eq + 1) : sigPart;
} else {
currentSignature = null;
}
chunk = new byte[currentLength];
currentIndex = 0;
ByteStreams.readFully(in, chunk);
if (hasher != null) {
hasher.putBytes(chunk);
}
if (signingKey != null) {
verifyChunkSignature(chunk, currentSignature);
}
if (currentLength == 0) {
return -1;
}
// consume trailing \r\n
readLine(in);
}
return chunk[currentIndex++] & 0xFF;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int i;
for (i = 0; i < len; ++i) {
int ch = read();
if (ch == -1) {
break;
}
b[off + i] = (byte) ch;
}
if (i == 0) {
return -1;
}
return i;
}
private void verifyChunkSignature(byte[] data, @Nullable String signature)
throws IOException {
if (signature == null) {
throw new IOException(new S3Exception(
S3ErrorCode.SIGNATURE_DOES_NOT_MATCH));
}
String chunkHash;
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
chunkHash = BaseEncoding.base16().lowerCase()
.encode(md.digest(data));
} catch (NoSuchAlgorithmException e) {
throw new IOException(e);
}
String stringToSign = "AWS4-HMAC-SHA256-PAYLOAD\n" +
timestamp + "\n" +
scope + "\n" +
previousSignature + "\n" +
EMPTY_SHA256 + "\n" +
chunkHash;
String expected;
try {
Mac mac = Mac.getInstance(hmacAlgorithm);
mac.init(new SecretKeySpec(signingKey, hmacAlgorithm));
expected = BaseEncoding.base16().lowerCase().encode(
mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8)));
} catch (InvalidKeyException | NoSuchAlgorithmException e) {
throw new IOException(e);
}
if (!constantTimeEquals(expected, signature)) {
throw new IOException(new S3Exception(
S3ErrorCode.SIGNATURE_DOES_NOT_MATCH));
}
previousSignature = signature;
}
private static boolean constantTimeEquals(String a, String b) {
if (a.length() != b.length()) {
return false;
}
int diff = 0;
for (int i = 0; i < a.length(); i++) {
diff |= a.charAt(i) ^ b.charAt(i);
}
return diff == 0;
}
/**
* Read a \r\n terminated line from an InputStream.
*
* @return line without the newline or empty String if InputStream is empty
*/
private static String readLine(InputStream is) throws IOException {
var builder = new StringBuilder();
while (true) {
int ch = is.read();
if (ch == '\r') {
ch = is.read();
if (ch == '\n') {
break;
} else {
throw new IOException("unexpected char after \\r: " + ch);
}
} else if (ch == -1) {
if (builder.length() > 0) {
throw new IOException("unexpected end of stream");
}
break;
}
if (builder.length() >= MAX_LINE_LENGTH) {
throw new IOException("chunk header too long");
}
builder.append((char) ch);
}
return builder.toString();
}
}
================================================
FILE: src/main/java/org/gaul/s3proxy/CompleteMultipartUploadRequest.java
================================================
/*
* Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gaul.s3proxy;
import java.util.Collection;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
// CHECKSTYLE:OFF
final class CompleteMultipartUploadRequest {
@JacksonXmlProperty(localName = "Part")
@JacksonXmlElementWrapper(useWrapping = false)
Collection<Part> parts;
static final class Part {
@JacksonXmlProperty(localName = "PartNumber")
int partNumber;
@JacksonXmlProperty(localName = "ETag")
String eTag;
// TODO: unsupported checksums
@JacksonXmlProperty(localName = "ChecksumCRC32")
String checksumCRC32;
@JacksonXmlProperty(localName = "ChecksumCRC32C")
String checksumCRC32C;
@JacksonXmlProperty(localName = "ChecksumSHA1")
String checksumSHA1;
@JacksonXmlProperty(localName = "ChecksumSHA256")
String checksumSHA256;
}
}
// CHECKSTYLE:ON
================================================
FILE: src/main/java/org/gaul/s3proxy/CreateBucketRequest.java
================================================
/*
* Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gaul.s3proxy;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
// CHECKSTYLE:OFF
final class CreateBucketRequest {
@JacksonXmlProperty(localName = "LocationConstraint")
String locationConstraint;
}
// CHECKSTYLE:ON
================================================
FILE: src/main/java/org/gaul/s3proxy/CrossOriginResourceSharing.java
================================================
/*
* Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gaul.s3proxy;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class CrossOriginResourceSharing {
protected static final List<String> SUPPORTED_METHODS =
List.of("GET", "HEAD", "PUT", "POST", "DELETE");
private static final String HEADER_VALUE_SEPARATOR = ", ";
private static final String ALLOW_ANY_ORIGIN = "*";
private static final String ALLOW_ANY_HEADER = "*";
private static final String EXPOSE_ALL_HEADERS = "*";
private static final String ALLOW_CREDENTIALS = "true";
private static final Logger logger = LoggerFactory.getLogger(
CrossOriginResourceSharing.class);
private final String allowedMethodsRaw;
private final String allowedHeadersRaw;
private final String exposedHeadersRaw;
private final boolean anyOriginAllowed;
// Enforce ordering of values
private final List<Pattern> allowedOrigins;
private final List<String> allowedMethods;
private final List<String> allowedHeaders;
private final List<String> exposedHeaders;
private final String allowCredentials;
public CrossOriginResourceSharing() {
// CORS Allow all
this(List.of(ALLOW_ANY_ORIGIN), SUPPORTED_METHODS,
List.of(ALLOW_ANY_HEADER),
List.of(EXPOSE_ALL_HEADERS), "");
}
public CrossOriginResourceSharing(List<String> allowedOrigins,
List<String> allowedMethods,
List<String> allowedHeaders,
List<String> exposedHeaders,
String allowCredentials) {
Set<Pattern> allowedPattern = new HashSet<Pattern>();
boolean anyOriginAllowed = false;
if (allowedOrigins != null) {
if (allowedOrigins.contains(ALLOW_ANY_ORIGIN)) {
anyOriginAllowed = true;
} else {
for (String origin : allowedOrigins) {
allowedPattern.add(Pattern.compile(
origin, Pattern.CASE_INSENSITIVE));
}
}
}
this.anyOriginAllowed = anyOriginAllowed;
this.allowedOrigins = List.copyOf(allowedPattern);
if (allowedMethods == null) {
this.allowedMethods = List.of();
} else {
this.allowedMethods = List.copyOf(allowedMethods);
}
this.allowedMethodsRaw = Joiner.on(HEADER_VALUE_SEPARATOR).join(
this.allowedMethods);
if (allowedHeaders == null) {
this.allowedHeaders = List.of();
} else {
this.allowedHeaders = List.copyOf(allowedHeaders);
}
this.allowedHeadersRaw = Joiner.on(HEADER_VALUE_SEPARATOR).join(
this.allowedHeaders);
if (exposedHeaders == null) {
this.exposedHeaders = List.of();
} else {
this.exposedHeaders = List.copyOf(exposedHeaders);
}
this.exposedHeadersRaw = Joiner.on(HEADER_VALUE_SEPARATOR).join(
this.exposedHeaders);
this.allowCredentials = allowCredentials;
logger.info("CORS allowed origins: {}", allowedOrigins);
logger.info("CORS allowed methods: {}", allowedMethods);
logger.info("CORS allowed headers: {}", allowedHeaders);
logger.info("CORS exposed headers: {}", exposedHeaders);
logger.info("CORS allow credentials: {}", allowCredentials);
}
public String getAllowedMethods() {
return this.allowedMethodsRaw;
}
public String getExposedHeaders() {
return this.exposedHeadersRaw;
}
public String getAllowedOrigin(String origin) {
if (this.anyOriginAllowed) {
return ALLOW_ANY_ORIGIN;
} else {
return origin;
}
}
public boolean isOriginAllowed(String origin) {
if (!Strings.isNullOrEmpty(origin)) {
if (this.anyOriginAllowed) {
logger.debug("CORS origin allowed: {}", origin);
return true;
} else {
for (Pattern pattern : this.allowedOrigins) {
Matcher matcher = pattern.matcher(origin);
if (matcher.matches()) {
logger.debug("CORS origin allowed: {}", origin);
return true;
}
}
}
}
logger.debug("CORS origin not allowed: {}", origin);
return false;
}
public boolean isMethodAllowed(String method) {
if (!Strings.isNullOrEmpty(method)) {
if (this.allowedMethods.contains(method)) {
logger.debug("CORS method allowed: {}", method);
return true;
}
}
logger.debug("CORS method not allowed: {}", method);
return false;
}
public boolean isEveryHeaderAllowed(String headers) {
boolean result = false;
if (!Strings.isNullOrEmpty(headers)) {
if (this.allowedHeadersRaw.equals(ALLOW_ANY_HEADER)) {
result = true;
} else {
for (String header : Splitter.on(HEADER_VALUE_SEPARATOR).split(
headers)) {
result = this.allowedHeaders.contains(header);
if (!result) {
// First not matching header breaks
break;
}
}
}
}
if (result) {
logger.debug("CORS headers allowed: {}", headers);
} else {
logger.debug("CORS headers not allowed: {}", headers);
}
return result;
}
public boolean isAllowCredentials() {
return ALLOW_CREDENTIALS.equals(allowCredentials);
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (!(object instanceof CrossOriginResourceSharing that)) {
return false;
}
return this.allowedOrigins.equals(that.allowedOrigins) &&
this.allowedMethodsRaw.equals(that.allowedMethodsRaw) &&
this.allowedHeadersRaw.equals(that.allowedHeadersRaw);
}
@Override
public int hashCode() {
return Objects.hash(this.allowedOrigins, this.allowedMethodsRaw,
this.allowedHeadersRaw);
}
}
================================================
FILE: src/main/java/org/gaul/s3proxy/DeleteMultipleObjectsRequest.java
================================================
/*
* Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gaul.s3proxy;
import java.util.Collection;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
// CHECKSTYLE:OFF
final class DeleteMultipleObjectsRequest {
@JacksonXmlProperty(localName = "Quiet")
boolean quiet;
@JacksonXmlProperty(localName = "Object")
@JacksonXmlElementWrapper(useWrapping = false)
Collection<S3Object> objects;
static final class S3Object {
@JacksonXmlProperty(localName = "Key")
String key;
@JacksonXmlProperty(localName = "VersionID")
String versionId;
}
}
// CHECKSTYLE:ON
================================================
FILE: src/main/java/org/gaul/s3proxy/EncryptedBlobStore.java
================================================
/*
* Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gaul.s3proxy;
import static com.google.common.base.Preconditions.checkArgument;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.spec.KeySpec;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import com.google.common.net.HttpHeaders;
import org.gaul.s3proxy.crypto.Constants;
import org.gaul.s3proxy.crypto.Decryption;
import org.gaul.s3proxy.crypto.Encryption;
import org.gaul.s3proxy.crypto.PartPadding;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.domain.BlobAccess;
import org.jclouds.blobstore.domain.BlobBuilder;
import org.jclouds.blobstore.domain.BlobMetadata;
import org.jclouds.blobstore.domain.MultipartPart;
import org.jclouds.blobstore.domain.MultipartUpload;
import org.jclouds.blobstore.domain.MutableBlobMetadata;
import org.jclouds.blobstore.domain.PageSet;
import org.jclouds.blobstore.domain.StorageMetadata;
import org.jclouds.blobstore.domain.internal.MutableBlobMetadataImpl;
import org.jclouds.blobstore.domain.internal.MutableStorageMetadataImpl;
import org.jclouds.blobstore.domain.internal.PageSetImpl;
import org.jclouds.blobstore.options.CopyOptions;
import org.jclouds.blobstore.options.GetOptions;
import org.jclouds.blobstore.options.ListContainerOptions;
import org.jclouds.blobstore.options.PutOptions;
import org.jclouds.blobstore.util.ForwardingBlobStore;
import org.jclouds.io.ContentMetadata;
import org.jclouds.io.MutableContentMetadata;
import org.jclouds.io.Payload;
import org.jclouds.io.Payloads;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@SuppressWarnings("UnstableApiUsage")
public final class EncryptedBlobStore extends ForwardingBlobStore {
private final Logger logger =
LoggerFactory.getLogger(EncryptedBlobStore.class);
private SecretKeySpec secretKey;
private EncryptedBlobStore(BlobStore blobStore, Properties properties)
throws IllegalArgumentException {
super(blobStore);
String password = properties.getProperty(
S3ProxyConstants.PROPERTY_ENCRYPTED_BLOBSTORE_PASSWORD);
checkArgument(!Strings.isNullOrEmpty(password),
"Password for encrypted blobstore is not set");
String salt = properties.getProperty(
S3ProxyConstants.PROPERTY_ENCRYPTED_BLOBSTORE_SALT);
checkArgument(!Strings.isNullOrEmpty(salt),
"Salt for encrypted blobstore is not set");
initStore(password, salt);
}
static BlobStore newEncryptedBlobStore(BlobStore blobStore,
Properties properties) throws IOException {
return new EncryptedBlobStore(blobStore, properties);
}
private void initStore(String password, String salt)
throws IllegalArgumentException {
try {
SecretKeyFactory factory =
SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec =
new PBEKeySpec(password.toCharArray(), salt.getBytes(), 65536,
128);
SecretKey tmp = factory.generateSecret(spec);
secretKey = new SecretKeySpec(tmp.getEncoded(), "AES");
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException(e);
}
}
private Blob cipheredBlob(String container, Blob blob, InputStream payload,
long contentLength,
boolean addEncryptedMetadata) {
// make a copy of the blob with the new payload stream
BlobMetadata blobMeta = blob.getMetadata();
ContentMetadata contentMeta = blob.getMetadata().getContentMetadata();
Map<String, String> userMetadata = blobMeta.getUserMetadata();
String contentType = contentMeta.getContentType();
// suffix the content type with -s3enc if we need to encrypt
if (addEncryptedMetadata) {
blobMeta = setEncryptedSuffix(blobMeta);
} else {
// remove the -s3enc suffix while decrypting
// but not if it contains a multipart meta
if (!blobMeta.getUserMetadata()
.containsKey(Constants.METADATA_IS_ENCRYPTED_MULTIPART)) {
blobMeta = removeEncryptedSuffix(blobMeta);
}
}
// we do not set contentMD5 as it will not match due to the encryption
Blob cipheredBlob = blobBuilder(container)
.name(blobMeta.getName())
.type(blobMeta.getType())
.tier(blobMeta.getTier())
.userMetadata(userMetadata)
.payload(payload)
.cacheControl(contentMeta.getCacheControl())
.contentDisposition(contentMeta.getContentDisposition())
.contentEncoding(contentMeta.getContentEncoding())
.contentLanguage(contentMeta.getContentLanguage())
.contentLength(contentLength)
.contentType(contentType)
.build();
cipheredBlob.getMetadata().setUri(blobMeta.getUri());
cipheredBlob.getMetadata().setETag(blobMeta.getETag());
cipheredBlob.getMetadata().setLastModified(blobMeta.getLastModified());
cipheredBlob.getMetadata().setSize(blobMeta.getSize());
cipheredBlob.getMetadata().setPublicUri(blobMeta.getPublicUri());
cipheredBlob.getMetadata().setContainer(blobMeta.getContainer());
return cipheredBlob;
}
private Blob encryptBlob(String container, Blob blob) {
try {
// open the streams and pass them through the encryption
InputStream isRaw = blob.getPayload().openStream();
Encryption encryption =
new Encryption(secretKey, isRaw, 1);
InputStream is = encryption.openStream();
// adjust the encrypted content length by
// adding the padding block size
long contentLength =
blob.getMetadata().getContentMetadata().getContentLength() +
Constants.PADDING_BLOCK_SIZE;
return cipheredBlob(container, blob, is, contentLength, true);
} catch (IOException | GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
private Payload encryptPayload(Payload payload, int partNumber) {
try {
// open the streams and pass them through the encryption
InputStream isRaw = payload.openStream();
Encryption encryption =
new Encryption(secretKey, isRaw, partNumber);
InputStream is = encryption.openStream();
Payload cipheredPayload = Payloads.newInputStreamPayload(is);
MutableContentMetadata contentMetadata =
payload.getContentMetadata();
HashCode md5 = null;
contentMetadata.setContentMD5(md5);
cipheredPayload.setContentMetadata(payload.getContentMetadata());
cipheredPayload.setSensitive(payload.isSensitive());
// adjust the encrypted content length by
// adding the padding block size
long contentLength =
payload.getContentMetadata().getContentLength() +
Constants.PADDING_BLOCK_SIZE;
cipheredPayload.getContentMetadata()
.setContentLength(contentLength);
return cipheredPayload;
} catch (IOException | GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
private Blob decryptBlob(Decryption decryption, String container,
Blob blob) {
try {
// handle blob does not exist
if (blob == null) {
return null;
}
// open the streams and pass them through the decryption
InputStream isRaw = blob.getPayload().openStream();
InputStream is = decryption.openStream(isRaw);
// adjust the content length if the blob is encrypted
long contentLength =
blob.getMetadata().getContentMetadata().getContentLength();
if (decryption.isEncrypted()) {
contentLength = decryption.getContentLength();
}
return cipheredBlob(container, blob, is, contentLength, false);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
// filter the list by showing the unencrypted blob size
private PageSet<? extends StorageMetadata> filteredList(
PageSet<? extends StorageMetadata> pageSet) {
var builder = ImmutableSet.<StorageMetadata>builder();
for (StorageMetadata sm : pageSet) {
if (sm instanceof BlobMetadata bm) {
MutableBlobMetadata mbm =
new MutableBlobMetadataImpl(bm);
// if blob is encrypted remove the -s3enc suffix
// from content type
if (isEncrypted(mbm)) {
mbm = removeEncryptedSuffix(bm);
mbm = calculateBlobSize(mbm);
}
builder.add(mbm);
} else if (sm.getName() != null && isEncrypted(sm.getName())) {
// non-BlobMetadata list entries (e.g. from S3 list backends)
// still need the .s3enc suffix stripped from the name
var msm = new MutableStorageMetadataImpl(sm);
msm.setName(removeEncryptedSuffix(sm.getName()));
builder.add(msm);
} else {
builder.add(sm);
}
}
// make sure the marker do not show blob with .s3enc suffix
String marker = pageSet.getNextMarker();
if (marker != null && isEncrypted(marker)) {
marker = removeEncryptedSuffix(marker);
}
return new PageSetImpl<>(builder.build(), marker);
}
private boolean isEncrypted(BlobMetadata blobMeta) {
return isEncrypted(blobMeta.getName());
}
private boolean isEncrypted(String blobName) {
return blobName.endsWith(Constants.S3_ENC_SUFFIX);
}
private MutableBlobMetadata setEncryptedSuffix(BlobMetadata blobMeta) {
var bm = new MutableBlobMetadataImpl(blobMeta);
if (blobMeta.getName() != null && !isEncrypted(blobMeta.getName())) {
bm.setName(blobNameWithSuffix(blobMeta.getName()));
}
return bm;
}
private String removeEncryptedSuffix(String blobName) {
return blobName.substring(0,
blobName.length() - Constants.S3_ENC_SUFFIX.length());
}
private MutableBlobMetadata removeEncryptedSuffix(BlobMetadata blobMeta) {
var bm = new MutableBlobMetadataImpl(blobMeta);
if (isEncrypted(bm.getName())) {
String blobName = bm.getName();
bm.setName(removeEncryptedSuffix(blobName));
}
return bm;
}
private MutableBlobMetadata calculateBlobSize(BlobMetadata blobMeta) {
MutableBlobMetadata mbm = removeEncryptedSuffix(blobMeta);
// we are using on non-s3 backends like azure or gcp a metadata key to
// calculate the part padding sizes that needs to be removed
if (mbm.getUserMetadata()
.containsKey(Constants.METADATA_ENCRYPTION_PARTS)) {
int parts = Integer.parseInt(
mbm.getUserMetadata().get(Constants.METADATA_ENCRYPTION_PARTS));
int partPaddingSizes = Constants.PADDING_BLOCK_SIZE * parts;
long size = blobMeta.getSize() - partPaddingSizes;
mbm.setSize(size);
mbm.getContentMetadata().setContentLength(size);
} else {
// on s3 backends like aws or minio we rely on the eTag suffix
Matcher matcher =
Constants.MPU_ETAG_SUFFIX_PATTERN.matcher(blobMeta.getETag());
if (matcher.find()) {
int parts = Integer.parseInt(matcher.group(1));
int partPaddingSizes = Constants.PADDING_BLOCK_SIZE * parts;
long size = blobMeta.getSize() - partPaddingSizes;
mbm.setSize(size);
mbm.getContentMetadata().setContentLength(size);
} else {
// if there is also no eTag suffix then get the number of parts from last padding
var options = new GetOptions()
.range(blobMeta.getSize() - Constants.PADDING_BLOCK_SIZE, blobMeta.getSize());
var name = blobNameWithSuffix(blobMeta.getName());
var blob = delegate().getBlob(blobMeta.getContainer(), name, options);
try {
PartPadding lastPartPadding = PartPadding.readPartPaddingFromBlob(blob);
int parts = lastPartPadding.getPart();
int partPaddingSizes = Constants.PADDING_BLOCK_SIZE * parts;
long size = blobMeta.getSize() - partPaddingSizes;
mbm.setSize(size);
mbm.getContentMetadata().setContentLength(size);
} catch (IOException e) {
throw new UncheckedIOException("Failed to read part-padding from encrypted blob", e);
}
}
}
return mbm;
}
private boolean multipartRequiresStub() {
String blobStoreType = getBlobStoreType();
return Quirks.MULTIPART_REQUIRES_STUB.contains(blobStoreType);
}
private String blobNameWithSuffix(String container, String name) {
String nameWithSuffix = blobNameWithSuffix(name);
if (delegate().blobExists(container, nameWithSuffix)) {
name = nameWithSuffix;
}
return name;
}
private String blobNameWithSuffix(String name) {
return name + Constants.S3_ENC_SUFFIX;
}
private String getBlobStoreType() {
return delegate().getContext().unwrap().getProviderMetadata().getId();
}
private String generateUploadId(String container, String blobName) {
String path = container + "/" + blobName;
@SuppressWarnings("deprecation")
var hash = Hashing.md5();
return hash.hashBytes(path.getBytes(StandardCharsets.UTF_8)).toString();
}
@Override
public Blob getBlob(String containerName, String blobName) {
return getBlob(containerName, blobName, new GetOptions());
}
@Override
public Blob getBlob(String containerName, String blobName,
GetOptions getOptions) {
// adjust the blob name
blobName = blobNameWithSuffix(blobName);
// get the metadata to determine the blob size
BlobMetadata meta = delegate().blobMetadata(containerName, blobName);
try {
// we have a blob that ends with .s3enc
if (meta != null) {
// init defaults
long offset = 0;
long end = 0;
long length = -1;
if (getOptions.getRanges().size() > 0) {
// S3 doesn't allow multiple ranges
String range = getOptions.getRanges().get(0);
String[] ranges = range.split("-", 2);
if (ranges[0].isEmpty()) {
// handle to read from the end
end = Long.parseLong(ranges[1]);
length = end;
} else if (ranges[1].isEmpty()) {
// handle to read from an offset till the end
offset = Long.parseLong(ranges[0]);
} else {
// handle to read from an offset
offset = Long.parseLong(ranges[0]);
end = Long.parseLong(ranges[1]);
length = end - offset + 1;
}
}
// init decryption
Decryption decryption =
new Decryption(secretKey, delegate(), meta, offset, length);
if (decryption.isEncrypted() &&
getOptions.getRanges().size() > 0) {
// clear current ranges to avoid multiple ranges
getOptions.getRanges().clear();
long startAt = decryption.getStartAt();
long endAt = decryption.getEncryptedSize();
if (offset == 0 && end > 0 && length == end) {
// handle to read from the end
startAt = decryption.calculateTail();
} else if (offset > 0 && end > 0) {
// handle to read from an offset
endAt = decryption.calculateEndAt(end);
}
getOptions.range(startAt, endAt);
}
Blob blob =
delegate().getBlob(containerName, blobName, getOptions);
Blob decryptedBlob = decryptBlob(decryption, containerName, blob);
if (!getOptions.getRanges().isEmpty()) {
long decryptedSize = decryption.getUnencryptedSize();
long endRange = (offset != 0 && end == 0) ? decryptedSize : end;
decryptedBlob.getAllHeaders()
.put(HttpHeaders.CONTENT_RANGE, "bytes " + offset + "-" + endRange +
"/" + decryptedSize);
}
return decryptedBlob;
} else {
// we suppose to return a unencrypted blob
// since no metadata was found
blobName = removeEncryptedSuffix(blobName);
return delegate().getBlob(containerName, blobName, getOptions);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
@Override
public String putBlob(String containerName, Blob blob) {
return delegate().putBlob(containerName,
encryptBlob(containerName, blob));
}
@Override
public String putBlob(String containerName, Blob blob,
PutOptions putOptions) {
return delegate().putBlob(containerName,
encryptBlob(containerName, blob), putOptions);
}
@Override
public String copyBlob(String fromContainer, String fromName,
String toContainer, String toName, CopyOptions options) {
// if we copy an encrypted blob
// make sure to add suffix to the destination blob name
String blobName = blobNameWithSuffix(fromName);
if (delegate().blobExists(fromContainer, blobName)) {
fromName = blobName;
toName = blobNameWithSuffix(toName);
}
return delegate().copyBlob(fromContainer, fromName, toContainer, toName,
options);
}
@Override
public void removeBlob(String container, String name) {
name = blobNameWithSuffix(container, name);
delegate().removeBlob(container, name);
}
@Override
public void removeBlobs(String container, Iterable<String> names) {
List<String> filteredNames = new ArrayList<>();
// filter the list of blobs to determine
// if we need to delete encrypted blobs
for (String name : names) {
name = blobNameWithSuffix(container, name);
filteredNames.add(name);
}
delegate().removeBlobs(container, filteredNames);
}
@Override
public BlobAccess getBlobAccess(String container, String name) {
name = blobNameWithSuffix(container, name);
return delegate().getBlobAccess(container, name);
}
@Override
public boolean blobExists(String container, String name) {
name = blobNameWithSuffix(container, name);
return delegate().blobExists(container, name);
}
@Override
public void setBlobAccess(String container, String name,
BlobAccess access) {
name = blobNameWithSuffix(container, name);
delegate().setBlobAccess(container, name, access);
}
@Override
public PageSet<? extends StorageMetadata> list() {
PageSet<? extends StorageMetadata> pageSet = delegate().list();
return filteredList(pageSet);
}
@Override
public PageSet<? extends StorageMetadata> list(String container) {
PageSet<? extends StorageMetadata> pageSet = delegate().list(container);
return filteredList(pageSet);
}
@Override
public PageSet<? extends StorageMetadata> list(String container,
ListContainerOptions options) {
PageSet<? extends StorageMetadata> pageSet =
delegate().list(container, options);
return filteredList(pageSet);
}
@Override
public MultipartUpload initiateMultipartUpload(String container,
BlobMetadata blobMetadata, PutOptions options) {
MutableBlobMetadata mbm = new MutableBlobMetadataImpl(blobMetadata);
mbm = setEncryptedSuffix(mbm);
MultipartUpload mpu =
delegate().initiateMultipartUpload(container, mbm, options);
// handle non-s3 backends
// by setting a metadata key for multipart stubs
if (multipartRequiresStub()) {
mbm.getUserMetadata()
.put(Constants.METADATA_IS_ENCRYPTED_MULTIPART, "true");
if (getBlobStoreType().equals("azureblob")) {
// use part 0 as a placeholder
delegate().uploadMultipartPart(mpu, 0,
Payloads.newStringPayload("dummy"));
// since azure does not have a uploadId
// we use the sha256 of the path
String uploadId = generateUploadId(container, mbm.getName());
mpu = MultipartUpload.create(mpu.containerName(),
mpu.blobName(), uploadId, mpu.blobMetadata(), options);
} else if (getBlobStoreType().equals("google-cloud-storage")) {
mbm.getUserMetadata()
.put(Constants.METADATA_MULTIPART_KEY, mbm.getName());
// since gcp does not have a uploadId
// we use the sha256 of the path
String uploadId = generateUploadId(container, mbm.getName());
// to emulate later the list of multipart uploads
// we create a placeholder
BlobBuilder builder =
blobBuilder(Constants.MPU_FOLDER + uploadId)
.payload("")
.userMetadata(mbm.getUserMetadata());
delegate().putBlob(container, builder.build(), options);
// final mpu on gcp
mpu = MultipartUpload.create(mpu.containerName(),
mpu.blobName(), uploadId, mpu.blobMetadata(), options);
}
}
return mpu;
}
@Override
public List<MultipartUpload> listMultipartUploads(String container) {
List<MultipartUpload> mpus = new ArrayList<>();
// emulate list of multipart uploads on gcp
if (getBlobStoreType().equals("google-cloud-storage")) {
var options = new ListContainerOptions();
PageSet<? extends StorageMetadata> mpuList =
delegate().list(container,
options.prefix(Constants.MPU_FOLDER));
// find all blobs in .mpu folder and build the list
for (StorageMetadata blob : mpuList) {
Map<String, String> meta = blob.getUserMetadata();
if (meta.containsKey(Constants.METADATA_MULTIPART_KEY)) {
String blobName =
meta.get(Constants.METADATA_MULTIPART_KEY);
String uploadId =
blob.getName()
.substring(blob.getName().lastIndexOf("/") + 1);
MultipartUpload mpu =
MultipartUpload.create(container,
blobName, uploadId, null, null);
mpus.add(mpu);
}
}
} else {
mpus = delegate().listMultipartUploads(container);
}
List<MultipartUpload> filtered = new ArrayList<>();
// filter the list uploads by removing the .s3enc suffix
for (MultipartUpload mpu : mpus) {
String blobName = mpu.blobName();
if (isEncrypted(blobName)) {
blobName = removeEncryptedSuffix(mpu.blobName());
String uploadId = mpu.id();
// since azure not have a uploadId
// we use the sha256 of the path
if (getBlobStoreType().equals("azureblob")) {
uploadId = generateUploadId(container, mpu.blobName());
}
MultipartUpload mpuWithoutSuffix =
MultipartUpload.create(mpu.containerName(),
blobName, uploadId, mpu.blobMetadata(),
mpu.putOptions());
filtered.add(mpuWithoutSuffix);
} else {
filtered.add(mpu);
}
}
return filtered;
}
@Override
public List<MultipartPart> listMultipartUpload(MultipartUpload mpu) {
mpu = filterMultipartUpload(mpu);
List<MultipartPart> parts = delegate().listMultipartUpload(mpu);
List<MultipartPart> filteredParts = new ArrayList<>();
// fix wrong multipart size due to the part padding
for (MultipartPart part : parts) {
// we use part 0 as a placeholder and hide it on azure
if (getBlobStoreType().equals("azureblob") &&
part.partNumber() == 0) {
continue;
}
MultipartPart newPart = MultipartPart.create(
part.partNumber(),
part.partSize() - Constants.PADDING_BLOCK_SIZE,
part.partETag(),
part.lastModified()
);
filteredParts.add(newPart);
}
return filteredParts;
}
@Override
public MultipartPart uploadMultipartPart(MultipartUpload mpu,
int partNumber, Payload payload) {
mpu = filterMultipartUpload(mpu);
return delegate().uploadMultipartPart(mpu, partNumber,
encryptPayload(payload, partNumber));
}
private MultipartUpload filterMultipartUpload(MultipartUpload mpu) {
MutableBlobMetadata mbm = null;
if (mpu.blobMetadata() != null) {
mbm = new MutableBlobMetadataImpl(mpu.blobMetadata());
mbm = setEncryptedSuffix(mbm);
}
String blobName = mpu.blobName();
if (!isEncrypted(blobName)) {
blobName = blobNameWithSuffix(blobName);
}
return MultipartUpload.create(mpu.containerName(), blobName, mpu.id(),
mbm, mpu.putOptions());
}
@Override
public String completeMultipartUpload(MultipartUpload mpu,
List<MultipartPart> parts) {
MutableBlobMetadata mbm =
new MutableBlobMetadataImpl(mpu.blobMetadata());
String blobName = mpu.blobName();
// always set .s3enc suffix except on gcp
// and blob name starts with multipart upload id
if (getBlobStoreType().equals("google-cloud-storage") &&
mpu.blobName().startsWith(mpu.id())) {
logger.debug("skip suffix on gcp");
} else {
mbm = setEncryptedSuffix(mbm);
if (!isEncrypted(mpu.blobName())) {
blobName = blobNameWithSuffix(blobName);
}
}
MultipartUpload mpuWithSuffix =
MultipartUpload.create(mpu.containerName(),
blobName, mpu.id(), mbm, mpu.putOptions());
// this will only work for non s3 backends like azure and gcp
if (multipartRequiresStub()) {
long partCount = parts.size();
// special handling for GCP to sum up all parts
if (getBlobStoreType().equals("google-cloud-storage")) {
partCount = 0;
for (MultipartPart part : parts) {
blobName =
"%s_%08d".formatted(
mpu.id(),
part.partNumber());
BlobMetadata metadata =
delegate().blobMetadata(mpu.containerName(), blobName);
if (metadata != null && metadata.getUserMetadata()
.containsKey(Constants.METADATA_ENCRYPTION_PARTS)) {
String partMetaCount = metadata.getUserMetadata()
.get(Constants.METADATA_ENCRYPTION_PARTS);
partCount = partCount + Long.parseLong(partMetaCount);
} else {
partCount++;
}
}
}
mpuWithSuffix.blobMetadata().getUserMetadata()
.put(Constants.METADATA_ENCRYPTION_PARTS,
String.valueOf(partCount));
mpuWithSuffix.blobMetadata().getUserMetadata()
.remove(Constants.METADATA_IS_ENCRYPTED_MULTIPART);
}
String eTag = delegate().completeMultipartUpload(mpuWithSuffix, parts);
// cleanup mpu placeholder on gcp
if (getBlobStoreType().equals("google-cloud-storage")) {
delegate().removeBlob(mpu.containerName(),
Constants.MPU_FOLDER + mpu.id());
}
return eTag;
}
@Override
public BlobMetadata blobMetadata(String container, String name) {
name = blobNameWithSuffix(container, name);
BlobMetadata blobMetadata = delegate().blobMetadata(container, name);
if (blobMetadata != null) {
// only remove the -s3enc suffix
// if the blob is encrypted and not a multipart stub
if (isEncrypted(blobMetadata) &&
!blobMetadata.getUserMetadata()
.containsKey(Constants.METADATA_IS_ENCRYPTED_MULTIPART)) {
blobMetadata = removeEncryptedSuffix(blobMetadata);
blobMetadata = calculateBlobSize(blobMetadata);
}
}
return blobMetadata;
}
@Override
public long getMaximumMultipartPartSize() {
long max = delegate().getMaximumMultipartPartSize();
return max - Constants.PADDING_BLOCK_SIZE;
}
}
================================================
FILE: src/main/java/org/gaul/s3proxy/EventualBlobStore.java
================================================
/*
* Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gaul.s3proxy;
import static java.util.Objects.requireNonNull;
import static com.google.common.base.Preconditions.checkArgument;
import java.util.Deque;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.domain.BlobMetadata;
import org.jclouds.blobstore.domain.MultipartPart;
import org.jclouds.blobstore.domain.MultipartUpload;
import org.jclouds.blobstore.options.CopyOptions;
import org.jclouds.blobstore.options.CreateContainerOptions;
import org.jclouds.blobstore.options.PutOptions;
import org.jclouds.blobstore.util.ForwardingBlobStore;
import org.jclouds.domain.Location;
import org.jclouds.io.Payload;
/**
* This class is a BlobStore wrapper which emulates eventual consistency
* using two blobstores. It writes objects to one store and reads objects
* from another. An asynchronous process copies objects between stores. Note
* that container operations are not eventually consistent.
*/
final class EventualBlobStore extends ForwardingBlobStore {
private final BlobStore writeStore; // read from delegate
private final ScheduledExecutorService executorService;
private final Deque<Callable<?>> deque = new ConcurrentLinkedDeque<>();
private final int delay;
private final TimeUnit delayUnit;
private final double probability;
private final Random random = new Random();
private EventualBlobStore(BlobStore writeStore, BlobStore readStore,
ScheduledExecutorService executorService, int delay,
TimeUnit delayUnit, double probability) {
super(readStore);
this.writeStore = requireNonNull(writeStore);
this.executorService = requireNonNull(executorService);
checkArgument(delay >= 0, "Delay must be at least zero, was: %s",
delay);
this.delay = delay;
this.delayUnit = requireNonNull(delayUnit);
checkArgument(probability >= 0.0 && probability <= 1.0,
"Probability must be between 0.0 and 1.0, was: %s",
probability);
this.probability = probability;
}
static BlobStore newEventualBlobStore(BlobStore writeStore,
BlobStore readStore, ScheduledExecutorService executorService,
int delay, TimeUnit delayUnit, double probability) {
return new EventualBlobStore(writeStore, readStore, executorService,
delay, delayUnit, probability);
}
@Override
public boolean createContainerInLocation(Location location,
String container, CreateContainerOptions options) {
return delegate().createContainerInLocation(
location, container, options) &&
writeStore.createContainerInLocation(
location, container, options);
}
@Override
public void deleteContainer(String container) {
delegate().deleteContainer(container);
writeStore.deleteContainer(container);
}
@Override
public boolean deleteContainerIfEmpty(String container) {
return delegate().deleteContainerIfEmpty(container) &&
writeStore.deleteContainerIfEmpty(container);
}
@Override
public String putBlob(String containerName, Blob blob) {
return putBlob(containerName, blob, PutOptions.NONE);
}
@Override
public String putBlob(final String containerName, Blob blob,
final PutOptions options) {
final String nearName = blob.getMetadata().getName();
String nearETag = writeStore.putBlob(containerName, blob, options);
schedule(new Callable<String>() {
@Override
public String call() {
Blob nearBlob = writeStore.getBlob(containerName, nearName);
String farETag = delegate().putBlob(containerName,
nearBlob, options);
return farETag;
}
});
return nearETag;
}
@Override
public void removeBlob(final String containerName, final String blobName) {
writeStore.removeBlob(containerName, blobName);
schedule(new Callable<Void>() {
@Override
public Void call() {
delegate().removeBlob(containerName, blobName);
return null;
}
});
}
@Override
public void removeBlobs(final String containerName,
final Iterable<String> blobNames) {
writeStore.removeBlobs(containerName, blobNames);
schedule(new Callable<Void>() {
@Override
public Void call() {
delegate().removeBlobs(containerName, blobNames);
return null;
}
});
}
@Override
public String copyBlob(final String fromContainer, final String fromName,
final String toContainer, final String toName,
final CopyOptions options) {
String nearETag = writeStore.copyBlob(fromContainer, fromName,
toContainer, toName, options);
schedule(new Callable<String>() {
@Override
public String call() {
return delegate().copyBlob(fromContainer, fromName,
toContainer, toName, options);
}
});
return nearETag;
}
@Override
public MultipartUpload initiateMultipartUpload(String container,
BlobMetadata blobMetadata, PutOptions options) {
MultipartUpload mpu = delegate().initiateMultipartUpload(container,
blobMetadata, options);
return mpu;
}
@Override
public void abortMultipartUpload(MultipartUpload mpu) {
delegate().abortMultipartUpload(mpu);
}
@Override
public String completeMultipartUpload(final MultipartUpload mpu,
final List<MultipartPart> parts) {
schedule(new Callable<String>() {
@Override
public String call() {
String farETag = delegate().completeMultipartUpload(mpu,
parts);
return farETag;
}
});
return ""; // TODO: fake ETag
}
@Override
public MultipartPart uploadMultipartPart(MultipartUpload mpu,
int partNumber, Payload payload) {
MultipartPart part = delegate().uploadMultipartPart(mpu, partNumber,
payload);
return part;
}
@SuppressWarnings("FutureReturnValueIgnored")
private void schedule(Callable<?> callable) {
if (random.nextDouble() < probability) {
deque.add(callable);
executorService.schedule(new DequeCallable(), delay, delayUnit);
}
}
private final class DequeCallable implements Callable<Void> {
@Override
public Void call() throws Exception {
deque.poll().call();
return null;
}
}
}
================================================
FILE: src/main/java/org/gaul/s3proxy/GlobBlobStoreLocator.java
================================================
/*
* Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gaul.s3proxy;
import java.nio.file.FileSystems;
import java.nio.file.PathMatcher;
import java.util.Map;
import com.google.common.collect.Maps;
import org.jclouds.blobstore.BlobStore;
import org.jspecify.annotations.Nullable;
public final class GlobBlobStoreLocator implements BlobStoreLocator {
private final Map<String, Map.Entry<String, BlobStore>> locator;
private final Map<PathMatcher, Map.Entry<String, BlobStore>> globLocator;
public GlobBlobStoreLocator(
Map<String, Map.Entry<String, BlobStore>> locator,
Map<PathMatcher, Map.Entry<String, BlobStore>> globLocator) {
this.locator = locator;
this.globLocator = globLocator;
}
@Override
public Map.Entry<String, BlobStore> locateBlobStore(
@Nullable String identity, String container, String blob) {
Map.Entry<String, BlobStore> locatorEntry =
locator.get(identity);
Map.Entry<String, BlobStore> globEntry = null;
if (container != null) {
for (var entry : globLocator.entrySet()) {
if (entry.getKey().matches(FileSystems.getDefault()
.getPath(container))) {
globEntry = entry.getValue();
}
}
}
if (globEntry == null) {
if (identity == null) {
if (!locator.isEmpty()) {
return locator.entrySet().iterator().next()
.getValue();
}
return Maps.immutableEntry(null,
globLocator.entrySet().iterator().next().getValue()
.getValue());
}
return locatorEntry;
}
if (identity == null) {
return Maps.immutableEntry(null, globEntry.getValue());
}
if (!globEntry.getKey().equals(identity)) {
return null;
}
if (locatorEntry == null) {
return null;
}
return Map.entry(locatorEntry.getKey(), globEntry.getValue());
}
}
================================================
FILE: src/main/java/org/gaul/s3proxy/LatencyBlobStore.java
================================================
/*
* Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gaul.s3proxy;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.collect.ImmutableMap;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.domain.BlobAccess;
import org.jclouds.blobstore.domain.BlobMetadata;
import org.jclouds.blobstore.domain.ContainerAccess;
import org.jclouds.blobstore.domain.MultipartPart;
import org.jclouds.blobstore.domain.MultipartUpload;
import org.jclouds.blobstore.domain.PageSet;
import org.jclouds.blobstore.domain.StorageMetadata;
import org.jclouds.blobstore.options.CopyOptions;
import org.jclouds.blobstore.options.CreateContainerOptions;
import org.jclouds.blobstore.options.GetOptions;
import org.jclouds.blobstore.options.ListContainerOptions;
import org.jclouds.blobstore.options.PutOptions;
import org.jclouds.blobstore.util.ForwardingBlobStore;
import org.jclouds.domain.Location;
import org.jclouds.io.ContentMetadata;
import org.jclouds.io.Payload;
import org.jclouds.io.payloads.InputStreamPayload;
public final class LatencyBlobStore extends ForwardingBlobStore {
private static final Pattern PROPERTIES_LATENCY_RE = Pattern.compile(
"^" + S3ProxyConstants.PROPERTY_LATENCY + "\\.(?<op>.*)\\.latency$");
private static final Pattern PROPERTIES_SPEED_RE = Pattern.compile(
"^" + S3ProxyConstants.PROPERTY_LATENCY + "\\.(?<op>.*)\\.speed$");
private static final String OP_ALL = "*";
private static final String OP_CONTAINER_EXISTS = "container-exists";
private static final String OP_CREATE_CONTAINER = "create-container";
private static final String OP_CONTAINER_ACCESS = "container-access";
private static final String OP_LIST = "list";
private static final String OP_CLEAR_CONTAINER = "clear-container";
private static final String OP_DELETE_CONTAINER = "delete-container";
private static final String OP_DIRECTORY_EXISTS = "directory-exists";
private static final String OP_CREATE_DIRECTORY = "create-directory";
private static final String OP_DELETE_DIRECTORY = "delete-directory";
private static final String OP_BLOB_EXISTS = "blob-exists";
private static final String OP_PUT_BLOB = "put";
private static final String OP_COPY_BLOB = "copy";
private static final String OP_BLOB_METADATA = "metadata";
private static final String OP_GET_BLOB = "get";
private static final String OP_REMOVE_BLOB = "remove";
private static final String OP_BLOB_ACCESS = "blob-access";
private static final String OP_COUNT_BLOBS = "count";
private static final String OP_MULTIPART_MESSAGE = "multipart-message";
private static final String OP_UPLOAD_PART = "upload-part";
private static final String OP_LIST_MULTIPART = "list-multipart";
private static final String OP_MULTIPART_PARAM = "multipart-param";
private static final String OP_DOWNLOAD_BLOB = "download";
private static final String OP_STREAM_BLOB = "stream";
private final Map<String, Long> latencies;
private final Map<String, Long> speeds;
private LatencyBlobStore(BlobStore blobStore, Map<String, Long> latencies, Map<String, Long> speeds) {
super(blobStore);
this.latencies = requireNonNull(latencies);
for (String op : latencies.keySet()) {
checkArgument(latencies.get(op) >= 0, "Latency must be non negative for %s", op);
}
this.speeds = requireNonNull(speeds);
for (String op : speeds.keySet()) {
checkArgument(speeds.get(op) > 0, "Speed must be positive for %s", op);
}
}
public static Map<String, Long> parseLatencies(Properties properties) {
var latencies = new ImmutableMap.Builder<String, Long>();
for (String key : properties.stringPropertyNames()) {
Matcher matcher = PROPERTIES_LATENCY_RE.matcher(key);
if (!matcher.matches()) {
continue;
}
String op = matcher.group("op");
long latency = Long.parseLong(properties.getProperty(key));
checkArgument(latency >= 0, "Latency must be non negative for %s", op);
latencies.put(op, latency);
}
return latencies.build();
}
public static Map<String, Long> parseSpeeds(Properties properties) {
var speeds = new ImmutableMap.Builder<String, Long>();
for (String key : properties.stringPropertyNames()) {
Matcher matcher = PROPERTIES_SPEED_RE.matcher(key);
if (!matcher.matches()) {
continue;
}
String op = matcher.group("op");
long speed = Long.parseLong(properties.getProperty(key));
checkArgument(speed > 0, "Speed must be positive for %s", op);
speeds.put(op, speed);
}
return speeds.build();
}
static BlobStore newLatencyBlobStore(BlobStore delegate, Map<String, Long> latencies, Map<String, Long> speeds) {
return new LatencyBlobStore(delegate, latencies, speeds);
}
@Override
public Set<? extends Location> listAssignableLocations() {
simulateLatency(OP_LIST);
return super.listAssignableLocations();
}
@Override
public PageSet<? extends StorageMetadata> list() {
simulateLatency(OP_LIST);
return super.list();
}
@Override
public PageSet<? extends StorageMetadata> list(String container) {
simulateLatency(OP_LIST);
return super.list(container);
}
@Override
public PageSet<? extends StorageMetadata> list(String container, ListContainerOptions options) {
simulateLatency(OP_LIST);
return super.list(container, options);
}
@Override
public boolean containerExists(String container) {
simulateLatency(OP_CONTAINER_EXISTS);
return super.containerExists(container);
}
@Override
public boolean createContainerInLocation(Location location, String container) {
simulateLatency(OP_CREATE_CONTAINER);
return super.createContainerInLocation(location, container);
}
@Override
public boolean createContainerInLocation(Location location, String container, CreateContainerOptions createContainerOptions) {
simulateLatency(OP_CREATE_CONTAINER);
return super.createContainerInLocation(location, container, createContainerOptions);
}
@Override
public ContainerAccess getContainerAccess(String container) {
simulateLatency(OP_CONTAINER_ACCESS);
return super.getContainerAccess(container);
}
@Override
public void setContainerAccess(String container, ContainerAccess containerAccess) {
simulateLatency(OP_CONTAINER_ACCESS);
super.setContainerAccess(container, containerAccess);
}
@Override
public void clearContainer(String container) {
simulateLatency(OP_CLEAR_CONTAINER);
super.clearContainer(container);
}
@Override
public void clearContainer(String container, ListContainerOptions options) {
simulateLatency(OP_CLEAR_CONTAINER);
super.clearContainer(container, options);
}
@Override
public void deleteContainer(String container) {
simulateLatency(OP_DELETE_CONTAINER);
super.deleteContainer(container);
}
@Override
public boolean deleteContainerIfEmpty(String container) {
simulateLatency(OP_DELETE_CONTAINER);
return super.deleteContainerIfEmpty(container);
}
@Override
public boolean directoryExists(String container, String directory) {
simulateLatency(OP_DIRECTORY_EXISTS);
return super.directoryExists(container, directory);
}
@Override
public void createDirectory(String container, String directory) {
simulateLatency(OP_CREATE_DIRECTORY);
super.createDirectory(container, directory);
}
@Override
public void deleteDirectory(String container, String directory) {
simulateLatency(OP_DELETE_DIRECTORY);
super.deleteDirectory(container, directory);
}
@Override
public boolean blobExists(String container, String name) {
simulateLatency(OP_BLOB_EXISTS);
return super.blobExists(container, name);
}
@Override
public String putBlob(String containerName, Blob blob) {
simulateLatency(OP_PUT_BLOB);
try {
InputStream is = blob.getPayload().openStream();
Blob newBlob = replaceStream(blob, new ThrottledInputStream(is, getSpeed(OP_PUT_BLOB)));
return super.putBlob(containerName, newBlob);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public String putBlob(String containerName, Blob blob, PutOptions putOptions) {
simulateLatency(OP_PUT_BLOB);
try {
InputStream is = blob.getPayload().openStream();
Blob newBlob = replaceStream(blob, new ThrottledInputStream(is, getSpeed(OP_PUT_BLOB)));
return super.putBlob(containerName, newBlob);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public String copyBlob(String fromContainer, String fromName, String toContainer, String toName, CopyOptions options) {
simulateLatency(OP_COPY_BLOB);
return super.copyBlob(fromContainer, fromName, toContainer, toName, options);
}
@Override
public BlobMetadata blobMetadata(String container, String name) {
simulateLatency(OP_BLOB_METADATA);
return super.blobMetadata(container, name);
}
@Override
public Blob getBlob(String containerName, String blobName) {
simulateLatency(OP_GET_BLOB);
Blob blob = super.getBlob(containerName, blobName);
try {
InputStream is = blob.getPayload().openStream();
return replaceStream(blob, new ThrottledInputStream(is, getSpeed(OP_GET_BLOB)));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public Blob getBlob(String containerName, String blobName, GetOptions getOptions) {
simulateLatency(OP_GET_BLOB);
Blob blob = super.getBlob(containerName, blobName, getOptions);
try {
InputStream is = blob.getPayload().openStream();
return replaceStream(blob, new ThrottledInputStream(is, getSpeed(OP_GET_BLOB)));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void removeBlob(String container, String name) {
simulateLatency(OP_REMOVE_BLOB);
super.removeBlob(container, name);
}
@Override
public void removeBlobs(String container, Iterable<String> iterable) {
simulateLatency(OP_REMOVE_BLOB);
super.removeBlobs(container, iterable);
}
@Override
public BlobAccess getBlobAccess(String container, String name) {
simulateLatency(OP_BLOB_ACCESS);
return super.getBlobAccess(container, name);
}
@Override
public void setBlobAccess(String container, String name, BlobAccess access) {
simulateLatency(OP_BLOB_ACCESS);
super.setBlobAccess(container, name, access);
}
@Override
public long countBlobs(String container) {
simulateLatency(OP_COUNT_BLOBS);
return super.countBlobs(container);
}
@Override
public long countBlobs(String container, ListContainerOptions options) {
simulateLatency(OP_COUNT_BLOBS);
return super.countBlobs(container, options);
}
@Override
public MultipartUpload initiateMultipartUpload(String container, BlobMetadata blobMetadata, PutOptions options) {
simulateLatency(OP_MULTIPART_MESSAGE);
return super.initiateMultipartUpload(container, blobMetadata, options);
}
@Override
public void abortMultipartUpload(MultipartUpload mpu) {
simulateLatency(OP_MULTIPART_MESSAGE);
super.abortMultipartUpload(mpu);
}
@Override
public String completeMultipartUpload(MultipartUpload mpu, List<MultipartPart> parts) {
simulateLatency(OP_MULTIPART_MESSAGE);
return super.completeMultipartUpload(mpu, parts);
}
@Override
public MultipartPart uploadMultipartPart(MultipartUpload mpu, int partNumber, Payload payload) {
simulateLatency(OP_UPLOAD_PART);
try {
InputStream is = payload.openStream();
payload = new InputStreamPayload(new ThrottledInputStream(is, getSpeed(OP_UPLOAD_PART)));
} catch (IOException e) {
throw new RuntimeException(e);
}
return super.uploadMultipartPart(mpu, partNumber, payload);
}
@Override
public List<MultipartPart> listMultipartUpload(MultipartUpload mpu) {
simulateLatency(OP_LIST_MULTIPART);
return super.listMultipartUpload(mpu);
}
@Override
public List<MultipartUpload> listMultipartUploads(String container) {
simulateLatency(OP_LIST_MULTIPART);
return super.listMultipartUploads(container);
}
@Override
public long getMinimumMultipartPartSize() {
simulateLatency(OP_MULTIPART_PARAM);
return super.getMinimumMultipartPartSize();
}
@Override
public long getMaximumMultipartPartSize() {
simulateLatency(OP_MULTIPART_PARAM);
return super.getMaximumMultipartPartSize();
}
@Override
public int getMaximumNumberOfParts() {
simulateLatency(OP_MULTIPART_PARAM);
return super.getMaximumNumberOfParts();
}
@Override
public void downloadBlob(String container, String name, File destination) {
simulateLatency(OP_DOWNLOAD_BLOB);
super.downloadBlob(container, name, destination);
}
@Override
public void downloadBlob(String container, String name, File destination, ExecutorService executor) {
simulateLatency(OP_DOWNLOAD_BLOB);
super.downloadBlob(container, name, destination, executor);
}
@Override
public InputStream streamBlob(String container, String name) {
simulateLatency(OP_STREAM_BLOB);
InputStream is = super.streamBlob(container, name);
return new ThrottledInputStream(is, getSpeed(OP_STREAM_BLOB));
}
@Override
public InputStream streamBlob(String container, String name, ExecutorService executor) {
simulateLatency(OP_STREAM_BLOB);
InputStream is = super.streamBlob(container, name, executor);
return new ThrottledInputStream(is, getSpeed(OP_STREAM_BLOB));
}
private long getLatency(String op) {
return latencies.getOrDefault(op, latencies.getOrDefault(OP_ALL, 0L));
}
private Long getSpeed(String op) {
return speeds.getOrDefault(op, speeds.getOrDefault(OP_ALL, null));
}
private void simulateLatency(String op) {
long latency = getLatency(op);
if (latency > 0) {
try {
Thread.sleep(latency);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
private Blob replaceStream(Blob blob, InputStream is) {
BlobMetadata blobMeta = blob.getMetadata();
ContentMetadata contentMeta = blobMeta.getContentMetadata();
Map<String, String> userMetadata = blobMeta.getUserMetadata();
Blob newBlob = blobBuilder(blobMeta.getName())
.type(blobMeta.getType())
.tier(blobMeta.getTier())
.userMetadata(userMetadata)
.payload(is)
.cacheControl(contentMeta.getCacheControl())
.contentDisposition(contentMeta.getContentDisposition())
.contentEncoding(contentMeta.getContentEncoding())
.contentLanguage(contentMeta.getContentLanguage())
.contentLength(contentMeta.getContentLength())
.contentType(contentMeta.getContentType())
.build();
newBlob.getMetadata().setUri(blobMeta.getUri());
newBlob.getMetadata().setETag(blobMeta.getETag());
newBlob.getMetadata().setLastModified(blobMeta.getLastModified());
newBlob.getMetadata().setSize(blobMeta.getSize());
newBlob.getMetadata().setPublicUri(blobMeta.getPublicUri());
newBlob.getMetadata().setContainer(blobMeta.getContainer());
return newBlob;
}
}
================================================
FILE: src/main/java/org/gaul/s3proxy/Main.java
================================================
/*
* Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gaul.s3proxy;
import java.io.Console;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSSessionCredentials;
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
import com.google.common.base.Strings;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.io.MoreFiles;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.gaul.modernizer_maven_annotations.SuppressModernizer;
import org.jclouds.Constants;
import org.jclouds.ContextBuilder;
import org.jclouds.JcloudsVersion;
import org.jclouds.aws.domain.SessionCredentials;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.concurrent.DynamicExecutors;
import org.jclouds.concurrent.config.ExecutorServiceModule;
import org.jclouds.domain.Credentials;
import org.jclouds.location.reference.LocationConstants;
import org.jclouds.logging.slf4j.config.SLF4JLoggingModule;
import org.jclouds.openstack.swift.v1.blobstore.RegionScopedBlobStoreContext;
import org.jclouds.s3.domain.ObjectMetadata.StorageClass;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class Main {
private static final Logger logger = LoggerFactory.getLogger(Main.class);
private Main() {
throw new AssertionError("intentionally not implemented");
}
private static final class Options {
@Option(name = "--properties",
usage = "S3Proxy configuration (required, multiple allowed)")
private List<Path> properties = new ArrayList<>();
@Option(name = "--version", usage = "display version")
private boolean version;
}
@SuppressWarnings("EqualsIncompatibleType")
public static void main(String[] args) throws Exception {
Console console = System.console();
if (console == null) {
System.setErr(createLoggerErrorPrintStream());
}
var options = new Options();
var parser = new CmdLineParser(options);
try {
parser.parseArgument(args);
} catch (CmdLineException cle) {
usage(parser);
}
if (options.version) {
System.err.println(
Main.class.getPackage().getImplementationVersion());
System.exit(0);
} else if (options.properties.isEmpty()) {
usage(parser);
}
S3Proxy.Builder s3ProxyBuilder = null;
var factory = new ThreadFactoryBuilder()
.setNameFormat("user thread %d")
.setThreadFactory(Executors.defaultThreadFactory())
.build();
ExecutorService executorService = DynamicExecutors.newScalingThreadPool(
1, 20, 60 * 1000, factory);
var locators = ImmutableMap
.<String, Map.Entry<String, BlobStore>>builder();
var globLocators = ImmutableMap
.<PathMatcher, Map.Entry<String, BlobStore>>builder();
Set<String> locatorGlobs = new HashSet<>();
Set<String> parsedIdentities = new HashSet<>();
for (var path : options.properties) {
var properties = new Properties();
try (var is = Files.newInputStream(path)) {
properties.load(is);
}
properties.putAll(System.getProperties());
BlobStore blobStore = createBlobStore(properties, executorService);
var blobStoreType = blobStore.getContext().unwrap().getProviderMetadata().getId();
if (blobStoreType.equals("aws-s3")) {
System.err.println("WARNING: aws-s3 storage backend deprecated -- please use aws-s3-sdk instead");
} else if (blobStoreType.equals("azureblob")) {
System.err.println("WARNING: azureblob storage backend deprecated -- please use azureblob-sdk instead");
} else if (blobStoreType.equals("filesystem")) {
System.err.println("WARNING: filesystem storage backend deprecated -- please use filesystem-nio2 instead");
} else if (blobStoreType.equals("google-cloud-storage")) {
System.err.println("WARNING: google-cloud-storage storage backend deprecated -- please use google-cloud-storage-sdk instead");
} else if (blobStoreType.equals("s3")) {
System.err.println("WARNING: s3 storage backend deprecated -- please use aws-s3-sdk instead");
} else if (blobStoreType.equals("transient")) {
System.err.println("WARNING: transient storage backend deprecated -- please use transient-nio2 instead");
}
blobStore = parseMiddlewareProperties(blobStore, executorService,
properties);
String s3ProxyAuthorizationString = properties.getProperty(
S3ProxyConstants.PROPERTY_AUTHORIZATION);
String localIdentity = null;
if (AuthenticationType.fromString(s3ProxyAuthorizationString) !=
AuthenticationType.NONE) {
localIdentity = properties.getProperty(
S3ProxyConstants.PROPERTY_IDENTITY);
String localCredential = properties.getProperty(
S3ProxyConstants.PROPERTY_CREDENTIAL);
if (parsedIdentities.add(localIdentity)) {
locators.put(localIdentity,
Map.entry(localCredential, blobStore));
}
}
for (String key : properties.stringPropertyNames()) {
if (key.startsWith(S3ProxyConstants.PROPERTY_BUCKET_LOCATOR)) {
String bucketLocator = properties.getProperty(key);
if (locatorGlobs.add(bucketLocator)) {
globLocators.put(
FileSystems.getDefault().getPathMatcher(
"glob:" + bucketLocator),
Maps.immutableEntry(localIdentity, blobStore));
} else {
System.err.println("Multiple definitions of the " +
"bucket locator: " + bucketLocator);
System.exit(1);
}
}
}
S3Proxy.Builder s3ProxyBuilder2 = S3Proxy.Builder
.fromProperties(properties)
.blobStore(blobStore);
if (s3ProxyBuilder != null &&
!s3ProxyBuilder.equals(s3ProxyBuilder2)) {
System.err.println("Multiple configurations require" +
" identical s3proxy properties");
System.exit(1);
}
s3ProxyBuilder = s3ProxyBuilder2;
}
S3Proxy s3Proxy;
try {
s3Proxy = s3ProxyBuilder.build();
} catch (IllegalArgumentException | IllegalStateException e) {
System.err.println(e.getMessage());
System.exit(1);
throw e;
}
var locator = locators.build();
var globLocator = globLocators.build();
if (!locator.isEmpty() || !globLocator.isEmpty()) {
s3Proxy.setBlobStoreLocator(
new GlobBlobStoreLocator(locator, globLocator));
}
try {
s3Proxy.start();
} catch (Exception e) {
System.err.println(e.getMessage());
System.exit(1);
}
}
private static BlobStore parseMiddlewareProperties(BlobStore blobStore,
ExecutorService executorService, Properties properties)
throws IOException {
var altProperties = new Properties();
for (var entry : properties.entrySet()) {
String key = (String) entry.getKey();
if (key.startsWith(S3ProxyConstants.PROPERTY_ALT_JCLOUDS_PREFIX)) {
key = key.substring(
S3ProxyConstants.PROPERTY_ALT_JCLOUDS_PREFIX.length());
altProperties.put(key, (String) entry.getValue());
}
}
String eventualConsistency = properties.getProperty(
S3ProxyConstants.PROPERTY_EVENTUAL_CONSISTENCY);
if ("true".equalsIgnoreCase(eventualConsistency)) {
BlobStore altBlobStore = createBlobStore(altProperties,
executorService);
int delay = Integer.parseInt(properties.getProperty(
S3ProxyConstants.PROPERTY_EVENTUAL_CONSISTENCY_DELAY,
"5"));
double probability = Double.parseDouble(properties.getProperty(
S3ProxyConstants.PROPERTY_EVENTUAL_CONSISTENCY_PROBABILITY,
"1.0"));
System.err.println("Emulating eventual consistency with delay " +
delay + " seconds and probability " + (probability * 100) +
"%");
blobStore = EventualBlobStore.newEventualBlobStore(
blobStore, altBlobStore,
Executors.newScheduledThreadPool(1),
delay, TimeUnit.SECONDS, probability);
}
String nullBlobStore = properties.getProperty(
S3ProxyConstants.PROPERTY_NULL_BLOBSTORE);
if ("true".equalsIgnoreCase(nullBlobStore)) {
System.err.println("Using null storage backend");
blobStore = NullBlobStore.newNullBlobStore(blobStore);
}
String readOnlyBlobStore = properties.getProperty(
S3ProxyConstants.PROPERTY_READ_ONLY_BLOBSTORE);
if ("true".equalsIgnoreCase(readOnlyBlobStore)) {
System.err.println("Using read-only storage backend");
blobStore = ReadOnlyBlobStore.newReadOnlyBlobStore(blobStore);
}
ImmutableBiMap<String, String> aliases = AliasBlobStore.parseAliases(
properties);
if (!aliases.isEmpty()) {
System.err.println("Using alias backend");
blobStore = AliasBlobStore.newAliasBlobStore(blobStore, aliases);
}
Map<String, String> prefixMap = PrefixBlobStore.parsePrefixes(properties);
if (!prefixMap.isEmpty()) {
System.err.println("Using prefix backend");
blobStore = PrefixBlobStore.newPrefixBlobStore(blobStore,
prefixMap);
}
List<Map.Entry<Pattern, String>> regexs =
RegexBlobStore.parseRegexs(properties);
if (!regexs.isEmpty()) {
System.err.println("Using regex backend");
blobStore = RegexBlobStore.newRegexBlobStore(blobStore, regexs);
}
Map<String, Integer> shards =
ShardedBlobStore.parseBucketShards(properties);
Map<String, String> prefixes =
ShardedBlobStore.parsePrefixes(properties);
if (!shards.isEmpty()) {
System.err.println("Using sharded buckets backend");
blobStore = ShardedBlobStore.newShardedBlobStore(blobStore,
shards, prefixes);
}
String encryptedBlobStore = properties.getProperty(
S3ProxyConstants.PROPERTY_ENCRYPTED_BLOBSTORE);
if ("true".equalsIgnoreCase(encryptedBlobStore)) {
System.err.println("Using encrypted storage backend");
blobStore = EncryptedBlobStore.newEncryptedBlobStore(blobStore,
properties);
}
var storageClass = properties.getProperty(
S3ProxyConstants.PROPERTY_STORAGE_CLASS_BLOBSTORE);
if (!Strings.isNullOrEmpty(storageClass)) {
System.err.println("Using storage class override backend");
var storageClassBlobStore =
StorageClassBlobStore.newStorageClassBlobStore(
blobStore, storageClass);
blobStore = storageClassBlobStore;
System.err.println("Configuration storage class: " + storageClass);
// TODO: This only makes sense for S3 backends.
System.err.println("Mapping storage storage class to: " +
StorageClass.fromTier(storageClassBlobStore.getTier()));
}
String userMetadataReplacerBlobStore = properties.getProperty(
S3ProxyConstants.PROPERTY_USER_METADATA_REPLACER);
if ("true".equalsIgnoreCase(userMetadataReplacerBlobStore)) {
System.err.println("Using user metadata replacers storage backend");
String fromChars = properties.getProperty(S3ProxyConstants
.PROPERTY_USER_METADATA_REPLACER_FROM_CHARS);
String toChars = properties.getProperty(S3ProxyConstants
.PROPERTY_USER_METADATA_REPLACER_TO_CHARS);
blobStore = UserMetadataReplacerBlobStore
.newUserMetadataReplacerBlobStore(
blobStore, fromChars, toChars);
}
Map<String, Long> latencies = LatencyBlobStore.parseLatencies(properties);
Map<String, Long> speeds = LatencyBlobStore.parseSpeeds(properties);
if (!latencies.isEmpty() || !speeds.isEmpty()) {
System.err.println("Using latency storage backend");
blobStore = LatencyBlobStore.newLatencyBlobStore(blobStore, latencies, speeds);
}
String noCacheBlobStore = properties.getProperty(
S3ProxyConstants.PROPERTY_NO_CACHE_BLOBSTORE);
if ("true".equalsIgnoreCase(noCacheBlobStore)) {
System.err.println("Using no-cache storage backend middleware");
blobStore = NoCacheBlobStore
.newNoCacheBlobStore(blobStore);
}
return blobStore;
}
private static PrintStream createLoggerErrorPrintStream() {
return new PrintStream(System.err) {
private final StringBuilder builder = new StringBuilder();
@Override
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(
"SLF4J_SIGN_ONLY_FORMAT")
public void print(final String string) {
logger.error("{}", string);
}
@Override
public void write(byte[] buf, int off, int len) {
for (int i = off; i < len; ++i) {
char ch = (char) buf[i];
if (ch == '\n') {
if (builder.length() != 0) {
print(builder.toString());
builder.setLength(0);
}
} else {
builder.append(ch);
}
}
}
};
}
private static BlobStore createBlobStore(Properties properties,
ExecutorService executorService) throws IOException {
String provider = properties.getProperty(Constants.PROPERTY_PROVIDER);
String identity = properties.getProperty(Constants.PROPERTY_IDENTITY);
String credential = properties.getProperty(
Constants.PROPERTY_CREDENTIAL);
String endpoint = properties.getProperty(Constants.PROPERTY_ENDPOINT);
properties.remove(Constants.PROPERTY_ENDPOINT);
String region = properties.getProperty(
LocationConstants.PROPERTY_REGION);
if (provider == null) {
System.err.println(
"Properties file must contain: " +
Constants.PROPERTY_PROVIDER);
System.exit(1);
}
if (provider.equals("filesystem") ||
provider.equals("filesystem-nio2") ||
provider.equals("transient") ||
provider.equals("transient-nio2")) {
// local blobstores do not require credentials
identity = Strings.nullToEmpty(identity);
credential = Strings.nullToEmpty(credential);
} else if (provider.equals("google-cloud-storage") ||
provider.equals("google-cloud-storage-sdk")) {
if (credential != null && !credential.isEmpty()) {
var path = FileSystems.getDefault().getPath(credential);
if (Files.exists(path)) {
credential = MoreFiles.asCharSource(path,
StandardCharsets.UTF_8).read();
}
}
identity = Strings.nullToEmpty(identity);
credential = Strings.nullToEmpty(credential);
properties.remove(Constants.PROPERTY_CREDENTIAL);
// We also need to clear the system property, otherwise the
// credential will be overridden by the system property.
System.clearProperty(Constants.PROPERTY_CREDENTIAL);
}
if (identity == null || credential == null) {
System.err.println(
"Properties file must contain: " +
Constants.PROPERTY_IDENTITY + " and " +
Constants.PROPERTY_CREDENTIAL);
System.exit(1);
}
properties.setProperty(Constants.PROPERTY_USER_AGENT,
"s3proxy/%s jclouds/%s java/%s".formatted(
Main.class.getPackage().getImplementationVersion(),
JcloudsVersion.get(),
System.getProperty("java.version")));
ContextBuilder builder = ContextBuilder
.newBuilder(provider)
.modules(List.of(
new SLF4JLoggingModule(),
new ExecutorServiceModule(executorService)))
.overrides(properties);
if (!Strings.isNullOrEmpty(endpoint)) {
builder = builder.endpoint(endpoint);
}
if ((identity.isEmpty() || credential.isEmpty()) && provider.equals("aws-s3")) {
@SuppressModernizer
Supplier<Credentials> credentialsSupplier = new Supplier<Credentials>() {
@Override
public Credentials get() {
AWSCredentialsProvider authChain = DefaultAWSCredentialsProviderChain.getInstance();
AWSCredentials newCreds = authChain.getCredentials();
Credentials jcloudsCred = null;
if (newCreds instanceof AWSSessionCredentials sessionCreds) {
jcloudsCred = SessionCredentials.builder()
gitextract_jwamyj7y/
├── .dockerignore
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ └── ci-main.yml
├── .gitignore
├── .gitmodules
├── .mailmap
├── .mvn/
│ └── maven.config
├── .releaserc
├── Dockerfile
├── LICENSE
├── README.md
├── docs/
│ ├── Encryption.md
│ └── Logging.md
├── pom.xml
└── src/
├── main/
│ ├── assembly/
│ │ └── jar-with-dependencies.xml
│ ├── config/
│ │ └── logback.xml
│ ├── java/
│ │ └── org/
│ │ └── gaul/
│ │ └── s3proxy/
│ │ ├── AccessControlPolicy.java
│ │ ├── AliasBlobStore.java
│ │ ├── AuthenticationType.java
│ │ ├── AwsHttpHeaders.java
│ │ ├── AwsSignature.java
│ │ ├── BlobStoreLocator.java
│ │ ├── CaseInsensitiveImmutableMultimap.java
│ │ ├── ChunkedInputStream.java
│ │ ├── CompleteMultipartUploadRequest.java
│ │ ├── CreateBucketRequest.java
│ │ ├── CrossOriginResourceSharing.java
│ │ ├── DeleteMultipleObjectsRequest.java
│ │ ├── EncryptedBlobStore.java
│ │ ├── EventualBlobStore.java
│ │ ├── GlobBlobStoreLocator.java
│ │ ├── LatencyBlobStore.java
│ │ ├── Main.java
│ │ ├── MetricsHandler.java
│ │ ├── NoCacheBlobStore.java
│ │ ├── NullBlobStore.java
│ │ ├── PrefixBlobStore.java
│ │ ├── PutOptions2.java
│ │ ├── Quirks.java
│ │ ├── ReadOnlyBlobStore.java
│ │ ├── RegexBlobStore.java
│ │ ├── S3AuthorizationHeader.java
│ │ ├── S3ErrorCode.java
│ │ ├── S3Exception.java
│ │ ├── S3Operation.java
│ │ ├── S3Proxy.java
│ │ ├── S3ProxyConstants.java
│ │ ├── S3ProxyHandler.java
│ │ ├── S3ProxyHandlerJetty.java
│ │ ├── S3ProxyMetrics.java
│ │ ├── ShardedBlobStore.java
│ │ ├── StorageClassBlobStore.java
│ │ ├── ThrottledInputStream.java
│ │ ├── UserMetadataReplacerBlobStore.java
│ │ ├── awssdk/
│ │ │ ├── AwsS3SdkApiMetadata.java
│ │ │ ├── AwsS3SdkBlobStore.java
│ │ │ ├── AwsS3SdkBlobStoreContextModule.java
│ │ │ └── AwsS3SdkProviderMetadata.java
│ │ ├── azureblob/
│ │ │ ├── AzureBlobApiMetadata.java
│ │ │ ├── AzureBlobProviderMetadata.java
│ │ │ ├── AzureBlobStore.java
│ │ │ └── AzureBlobStoreContextModule.java
│ │ ├── crypto/
│ │ │ ├── Constants.java
│ │ │ ├── Decryption.java
│ │ │ ├── DecryptionInputStream.java
│ │ │ ├── Encryption.java
│ │ │ ├── EncryptionInputStream.java
│ │ │ └── PartPadding.java
│ │ ├── gcloudsdk/
│ │ │ ├── GCloudApiMetadata.java
│ │ │ ├── GCloudBlobStore.java
│ │ │ ├── GCloudBlobStoreContextModule.java
│ │ │ └── GCloudProviderMetadata.java
│ │ ├── junit/
│ │ │ ├── S3ProxyExtension.java
│ │ │ ├── S3ProxyJunitCore.java
│ │ │ └── S3ProxyRule.java
│ │ └── nio2blob/
│ │ ├── AbstractNio2BlobStore.java
│ │ ├── FilesystemNio2BlobApiMetadata.java
│ │ ├── FilesystemNio2BlobProviderMetadata.java
│ │ ├── FilesystemNio2BlobStore.java
│ │ ├── FilesystemNio2BlobStoreContextModule.java
│ │ ├── TransientNio2BlobApiMetadata.java
│ │ ├── TransientNio2BlobProviderMetadata.java
│ │ ├── TransientNio2BlobStore.java
│ │ └── TransientNio2BlobStoreContextModule.java
│ └── resources/
│ ├── checkstyle.xml
│ ├── copyright_header.txt
│ └── run-docker-container.sh
└── test/
├── java/
│ └── org/
│ └── gaul/
│ └── s3proxy/
│ ├── AliasBlobStoreTest.java
│ ├── AwsS3SdkBlobStoreTest.java
│ ├── AwsSdk2Test.java
│ ├── AwsSdkAnonymousTest.java
│ ├── AwsSdkTest.java
│ ├── CrossOriginResourceSharingAllowAllResponseTest.java
│ ├── CrossOriginResourceSharingResponseTest.java
│ ├── CrossOriginResourceSharingRuleTest.java
│ ├── EncryptedBlobStoreTest.java
│ ├── EventualBlobStoreTest.java
│ ├── GlobBlobStoreLocatorTest.java
│ ├── LatencyBlobStoreTest.java
│ ├── NoCacheBlobStoreTest.java
│ ├── NullBlobStoreTest.java
│ ├── PrefixBlobStoreTest.java
│ ├── ReadOnlyBlobStoreTest.java
│ ├── RegexBlobStoreTest.java
│ ├── ShardedBlobStoreTest.java
│ ├── TestUtils.java
│ ├── TierBlobStoreTest.java
│ ├── UserMetadataReplacerBlobStoreTest.java
│ └── junit/
│ ├── S3ProxyExtensionTest.java
│ └── S3ProxyRuleTest.java
└── resources/
├── keystore.jks
├── logback.xml
├── run-s3-tests.sh
├── s3-tests.conf
├── s3proxy-anonymous.conf
├── s3proxy-azurite.conf
├── s3proxy-cors-allow-all.conf
├── s3proxy-cors.conf
├── s3proxy-encryption.conf
├── s3proxy-fake-gcs-server.conf
├── s3proxy-filesystem-nio2.conf
├── s3proxy-localstack-aws-s3-sdk.conf
├── s3proxy-localstack-s3.conf
├── s3proxy-transient-nio2.conf
└── s3proxy.conf
SYMBOL INDEX (1213 symbols across 91 files)
FILE: src/main/java/org/gaul/s3proxy/AccessControlPolicy.java
class AccessControlPolicy (line 27) | final class AccessControlPolicy {
method toString (line 33) | @Override
class Owner (line 41) | static final class Owner {
method toString (line 47) | @Override
class AccessControlList (line 56) | static final class AccessControlList {
method toString (line 61) | @Override
class Grant (line 68) | static final class Grant {
method toString (line 74) | @Override
class Grantee (line 82) | static final class Grantee {
method toString (line 95) | @Override
FILE: src/main/java/org/gaul/s3proxy/AliasBlobStore.java
class AliasBlobStore (line 59) | public final class AliasBlobStore extends ForwardingBlobStore {
method AliasBlobStore (line 62) | private AliasBlobStore(BlobStore delegate,
method newAliasBlobStore (line 68) | static BlobStore newAliasBlobStore(BlobStore delegate,
method getDelegateMpu (line 73) | private MultipartUpload getDelegateMpu(MultipartUpload mpu) {
method parseAliases (line 82) | public static ImmutableBiMap<String, String> parseAliases(
method getContainer (line 100) | private String getContainer(String container) {
method createContainerInLocation (line 104) | @Override
method createContainerInLocation (line 111) | @Override
method containerExists (line 119) | @Override
method getContainerAccess (line 124) | @Override
method setContainerAccess (line 129) | @Override
method list (line 135) | @Override
method list (line 163) | @Override
method list (line 168) | @Override
method clearContainer (line 174) | @Override
method clearContainer (line 179) | @Override
method deleteContainer (line 184) | @Override
method deleteContainerIfEmpty (line 189) | @Override
method blobExists (line 194) | @Override
method blobMetadata (line 199) | @Override
method getBlob (line 204) | @Override
method getBlob (line 209) | @Override
method putBlob (line 216) | @Override
method putBlob (line 221) | @Override
method removeBlob (line 228) | @Override
method removeBlobs (line 233) | @Override
method copyBlob (line 239) | @Override
method initiateMultipartUpload (line 247) | @Override
method abortMultipartUpload (line 256) | @Override
method completeMultipartUpload (line 261) | @Override
method uploadMultipartPart (line 267) | @Override
FILE: src/main/java/org/gaul/s3proxy/AuthenticationType.java
type AuthenticationType (line 21) | public enum AuthenticationType {
method fromString (line 27) | static AuthenticationType fromString(String string) {
FILE: src/main/java/org/gaul/s3proxy/AwsHttpHeaders.java
class AwsHttpHeaders (line 19) | final class AwsHttpHeaders {
method AwsHttpHeaders (line 50) | private AwsHttpHeaders() {
FILE: src/main/java/org/gaul/s3proxy/AwsSignature.java
class AwsSignature (line 50) | final class AwsSignature {
method AwsSignature (line 81) | private AwsSignature() { }
method createAuthorizationSignature (line 87) | static String createAuthorizationSignature(
method signMessage (line 182) | private static byte[] signMessage(byte[] data, byte[] key, String algo...
method deriveSigningKeyV4 (line 192) | static byte[] deriveSigningKeyV4(S3AuthorizationHeader authHeader,
method getMessageDigest (line 212) | private static String getMessageDigest(byte[] payload, String algorithm)
method extractSignedHeaders (line 219) | @Nullable
method buildCanonicalHeaders (line 234) | private static String buildCanonicalHeaders(HttpServletRequest request,
method buildCanonicalQueryString (line 272) | private static String buildCanonicalQueryString(
method createCanonicalRequest (line 291) | private static String createCanonicalRequest(HttpServletRequest request,
method createAuthorizationSignatureV4 (line 356) | static String createAuthorizationSignatureV4(
FILE: src/main/java/org/gaul/s3proxy/BlobStoreLocator.java
type BlobStoreLocator (line 23) | public interface BlobStoreLocator {
method locateBlobStore (line 24) | Map.Entry<String, BlobStore> locateBlobStore(String identity,
FILE: src/main/java/org/gaul/s3proxy/CaseInsensitiveImmutableMultimap.java
class CaseInsensitiveImmutableMultimap (line 25) | final class CaseInsensitiveImmutableMultimap
method CaseInsensitiveImmutableMultimap (line 29) | CaseInsensitiveImmutableMultimap(Multimap<String, String> map) {
method delegate (line 37) | @Override
method get (line 42) | @Override
method lower (line 47) | private static String lower(String key) {
FILE: src/main/java/org/gaul/s3proxy/ChunkedInputStream.java
class ChunkedInputStream (line 43) | final class ChunkedInputStream extends FilterInputStream {
method ChunkedInputStream (line 59) | ChunkedInputStream(InputStream is, int maxChunkSize) {
method ChunkedInputStream (line 69) | @SuppressWarnings("deprecation")
method ChunkedInputStream (line 102) | ChunkedInputStream(InputStream is, int maxChunkSize,
method read (line 115) | @Override
method read (line 167) | @Override
method verifyChunkSignature (line 183) | private void verifyChunkSignature(byte[] data, @Nullable String signat...
method constantTimeEquals (line 219) | private static boolean constantTimeEquals(String a, String b) {
method readLine (line 235) | private static String readLine(InputStream is) throws IOException {
FILE: src/main/java/org/gaul/s3proxy/CompleteMultipartUploadRequest.java
class CompleteMultipartUploadRequest (line 25) | final class CompleteMultipartUploadRequest {
class Part (line 30) | static final class Part {
FILE: src/main/java/org/gaul/s3proxy/CreateBucketRequest.java
class CreateBucketRequest (line 22) | final class CreateBucketRequest {
FILE: src/main/java/org/gaul/s3proxy/CrossOriginResourceSharing.java
class CrossOriginResourceSharing (line 33) | public final class CrossOriginResourceSharing {
method CrossOriginResourceSharing (line 57) | public CrossOriginResourceSharing() {
method CrossOriginResourceSharing (line 64) | public CrossOriginResourceSharing(List<String> allowedOrigins,
method getAllowedMethods (line 118) | public String getAllowedMethods() {
method getExposedHeaders (line 122) | public String getExposedHeaders() {
method getAllowedOrigin (line 126) | public String getAllowedOrigin(String origin) {
method isOriginAllowed (line 134) | public boolean isOriginAllowed(String origin) {
method isMethodAllowed (line 153) | public boolean isMethodAllowed(String method) {
method isEveryHeaderAllowed (line 164) | public boolean isEveryHeaderAllowed(String headers) {
method isAllowCredentials (line 191) | public boolean isAllowCredentials() {
method equals (line 195) | @Override
method hashCode (line 209) | @Override
FILE: src/main/java/org/gaul/s3proxy/DeleteMultipleObjectsRequest.java
class DeleteMultipleObjectsRequest (line 25) | final class DeleteMultipleObjectsRequest {
class S3Object (line 33) | static final class S3Object {
FILE: src/main/java/org/gaul/s3proxy/EncryptedBlobStore.java
class EncryptedBlobStore (line 73) | @SuppressWarnings("UnstableApiUsage")
method EncryptedBlobStore (line 79) | private EncryptedBlobStore(BlobStore blobStore, Properties properties)
method newEncryptedBlobStore (line 95) | static BlobStore newEncryptedBlobStore(BlobStore blobStore,
method initStore (line 100) | private void initStore(String password, String salt)
method cipheredBlob (line 115) | private Blob cipheredBlob(String container, Blob blob, InputStream pay...
method encryptBlob (line 162) | private Blob encryptBlob(String container, Blob blob) {
method encryptPayload (line 183) | private Payload encryptPayload(Payload payload, int partNumber) {
method decryptBlob (line 214) | private Blob decryptBlob(Decryption decryption, String container,
method filteredList (line 240) | private PageSet<? extends StorageMetadata> filteredList(
method isEncrypted (line 275) | private boolean isEncrypted(BlobMetadata blobMeta) {
method isEncrypted (line 279) | private boolean isEncrypted(String blobName) {
method setEncryptedSuffix (line 283) | private MutableBlobMetadata setEncryptedSuffix(BlobMetadata blobMeta) {
method removeEncryptedSuffix (line 292) | private String removeEncryptedSuffix(String blobName) {
method removeEncryptedSuffix (line 297) | private MutableBlobMetadata removeEncryptedSuffix(BlobMetadata blobMet...
method calculateBlobSize (line 307) | private MutableBlobMetadata calculateBlobSize(BlobMetadata blobMeta) {
method multipartRequiresStub (line 352) | private boolean multipartRequiresStub() {
method blobNameWithSuffix (line 357) | private String blobNameWithSuffix(String container, String name) {
method blobNameWithSuffix (line 365) | private String blobNameWithSuffix(String name) {
method getBlobStoreType (line 369) | private String getBlobStoreType() {
method generateUploadId (line 373) | private String generateUploadId(String container, String blobName) {
method getBlob (line 380) | @Override
method getBlob (line 385) | @Override
method putBlob (line 469) | @Override
method putBlob (line 475) | @Override
method copyBlob (line 482) | @Override
method removeBlob (line 498) | @Override
method removeBlobs (line 504) | @Override
method getBlobAccess (line 518) | @Override
method blobExists (line 524) | @Override
method setBlobAccess (line 530) | @Override
method list (line 537) | @Override
method list (line 543) | @Override
method list (line 549) | @Override
method initiateMultipartUpload (line 557) | @Override
method listMultipartUploads (line 608) | @Override
method listMultipartUpload (line 666) | @Override
method uploadMultipartPart (line 692) | @Override
method filterMultipartUpload (line 701) | private MultipartUpload filterMultipartUpload(MultipartUpload mpu) {
method completeMultipartUpload (line 717) | @Override
method blobMetadata (line 784) | @Override
method getMaximumMultipartPartSize (line 802) | @Override
FILE: src/main/java/org/gaul/s3proxy/EventualBlobStore.java
class EventualBlobStore (line 48) | final class EventualBlobStore extends ForwardingBlobStore {
method EventualBlobStore (line 57) | private EventualBlobStore(BlobStore writeStore, BlobStore readStore,
method newEventualBlobStore (line 73) | static BlobStore newEventualBlobStore(BlobStore writeStore,
method createContainerInLocation (line 80) | @Override
method deleteContainer (line 89) | @Override
method deleteContainerIfEmpty (line 95) | @Override
method putBlob (line 101) | @Override
method putBlob (line 106) | @Override
method removeBlob (line 123) | @Override
method removeBlobs (line 135) | @Override
method copyBlob (line 148) | @Override
method initiateMultipartUpload (line 164) | @Override
method abortMultipartUpload (line 172) | @Override
method completeMultipartUpload (line 177) | @Override
method uploadMultipartPart (line 191) | @Override
method schedule (line 199) | @SuppressWarnings("FutureReturnValueIgnored")
class DequeCallable (line 207) | private final class DequeCallable implements Callable<Void> {
method call (line 208) | @Override
FILE: src/main/java/org/gaul/s3proxy/GlobBlobStoreLocator.java
class GlobBlobStoreLocator (line 28) | public final class GlobBlobStoreLocator implements BlobStoreLocator {
method GlobBlobStoreLocator (line 32) | public GlobBlobStoreLocator(
method locateBlobStore (line 39) | @Override
FILE: src/main/java/org/gaul/s3proxy/LatencyBlobStore.java
class LatencyBlobStore (line 55) | public final class LatencyBlobStore extends ForwardingBlobStore {
method LatencyBlobStore (line 87) | private LatencyBlobStore(BlobStore blobStore, Map<String, Long> latenc...
method parseLatencies (line 99) | public static Map<String, Long> parseLatencies(Properties properties) {
method parseSpeeds (line 114) | public static Map<String, Long> parseSpeeds(Properties properties) {
method newLatencyBlobStore (line 129) | static BlobStore newLatencyBlobStore(BlobStore delegate, Map<String, L...
method listAssignableLocations (line 133) | @Override
method list (line 139) | @Override
method list (line 145) | @Override
method list (line 151) | @Override
method containerExists (line 157) | @Override
method createContainerInLocation (line 163) | @Override
method createContainerInLocation (line 169) | @Override
method getContainerAccess (line 175) | @Override
method setContainerAccess (line 181) | @Override
method clearContainer (line 187) | @Override
method clearContainer (line 193) | @Override
method deleteContainer (line 199) | @Override
method deleteContainerIfEmpty (line 205) | @Override
method directoryExists (line 211) | @Override
method createDirectory (line 217) | @Override
method deleteDirectory (line 223) | @Override
method blobExists (line 229) | @Override
method putBlob (line 235) | @Override
method putBlob (line 247) | @Override
method copyBlob (line 259) | @Override
method blobMetadata (line 265) | @Override
method getBlob (line 271) | @Override
method getBlob (line 283) | @Override
method removeBlob (line 295) | @Override
method removeBlobs (line 301) | @Override
method getBlobAccess (line 307) | @Override
method setBlobAccess (line 313) | @Override
method countBlobs (line 319) | @Override
method countBlobs (line 325) | @Override
method initiateMultipartUpload (line 331) | @Override
method abortMultipartUpload (line 337) | @Override
method completeMultipartUpload (line 343) | @Override
method uploadMultipartPart (line 349) | @Override
method listMultipartUpload (line 361) | @Override
method listMultipartUploads (line 367) | @Override
method getMinimumMultipartPartSize (line 373) | @Override
method getMaximumMultipartPartSize (line 379) | @Override
method getMaximumNumberOfParts (line 385) | @Override
method downloadBlob (line 391) | @Override
method downloadBlob (line 397) | @Override
method streamBlob (line 403) | @Override
method streamBlob (line 410) | @Override
method getLatency (line 417) | private long getLatency(String op) {
method getSpeed (line 421) | private Long getSpeed(String op) {
method simulateLatency (line 425) | private void simulateLatency(String op) {
method replaceStream (line 436) | private Blob replaceStream(Blob blob, InputStream is) {
FILE: src/main/java/org/gaul/s3proxy/Main.java
class Main (line 70) | public final class Main {
method Main (line 72) | private Main() {
class Options (line 76) | private static final class Options {
method main (line 85) | @SuppressWarnings("EqualsIncompatibleType")
method parseMiddlewareProperties (line 215) | private static BlobStore parseMiddlewareProperties(BlobStore blobStore,
method createLoggerErrorPrintStream (line 346) | private static PrintStream createLoggerErrorPrintStream() {
method createBlobStore (line 374) | private static BlobStore createBlobStore(Properties properties,
method usage (line 480) | private static void usage(CmdLineParser parser) {
FILE: src/main/java/org/gaul/s3proxy/MetricsHandler.java
class MetricsHandler (line 26) | public final class MetricsHandler extends HttpServlet {
method MetricsHandler (line 29) | public MetricsHandler(S3ProxyMetrics metrics) {
method service (line 33) | @Override
FILE: src/main/java/org/gaul/s3proxy/NoCacheBlobStore.java
class NoCacheBlobStore (line 28) | final class NoCacheBlobStore extends ForwardingBlobStore {
method NoCacheBlobStore (line 30) | private NoCacheBlobStore(BlobStore blobStore) {
method newNoCacheBlobStore (line 34) | public static BlobStore newNoCacheBlobStore(BlobStore blobStore) {
method getBlob (line 38) | @Override
method getBlob (line 43) | @Override
method resetCacheHeaders (line 48) | static GetOptions resetCacheHeaders(GetOptions options) {
FILE: src/main/java/org/gaul/s3proxy/NullBlobStore.java
class NullBlobStore (line 47) | final class NullBlobStore extends ForwardingBlobStore {
method NullBlobStore (line 48) | private NullBlobStore(BlobStore blobStore) {
method newNullBlobStore (line 52) | static BlobStore newNullBlobStore(BlobStore blobStore) {
method blobMetadata (line 56) | @Override
method getBlob (line 66) | @Override
method getBlob (line 72) | @Override
method list (line 98) | @Override
method putBlob (line 110) | @Override
method putBlob (line 115) | @Override
method completeMultipartUpload (line 136) | @Override
method abortMultipartUpload (line 161) | @Override
method uploadMultipartPart (line 171) | @Override
method listMultipartUpload (line 201) | @Override
class NullByteSource (line 216) | private static final class NullByteSource extends ByteSource {
method openStream (line 217) | @Override
class NullInputStream (line 223) | private static final class NullInputStream extends InputStream {
method read (line 226) | @Override
method read (line 234) | @Override
method close (line 243) | @Override
FILE: src/main/java/org/gaul/s3proxy/PrefixBlobStore.java
class PrefixBlobStore (line 52) | public final class PrefixBlobStore extends ForwardingBlobStore {
method PrefixBlobStore (line 55) | private PrefixBlobStore(BlobStore delegate, Map<String, String> prefix...
method newPrefixBlobStore (line 60) | static BlobStore newPrefixBlobStore(BlobStore delegate,
method parsePrefixes (line 71) | public static Map<String, String> parsePrefixes(Properties properties) {
method hasPrefix (line 90) | private boolean hasPrefix(String container) {
method getPrefix (line 94) | private String getPrefix(String container) {
method addPrefix (line 98) | private String addPrefix(String container, String name) {
method trimPrefix (line 112) | private String trimPrefix(String container, String name) {
method trimBlobMetadata (line 123) | private BlobMetadata trimBlobMetadata(String container,
method trimBlob (line 133) | private Blob trimBlob(String container, Blob blob) {
method toDelegateMultipartUpload (line 142) | private MultipartUpload toDelegateMultipartUpload(MultipartUpload uplo...
method toClientMultipartUpload (line 157) | private MultipartUpload toClientMultipartUpload(MultipartUpload upload) {
method applyPrefix (line 172) | private ListContainerOptions applyPrefix(String container,
method trimListing (line 201) | private PageSet<? extends StorageMetadata> trimListing(String container,
method directoryExists (line 225) | @Override
method createDirectory (line 231) | @Override
method deleteDirectory (line 236) | @Override
method blobExists (line 241) | @Override
method blobMetadata (line 246) | @Override
method getBlob (line 252) | @Override
method getBlob (line 259) | @Override
method putBlob (line 267) | @Override
method putBlob (line 278) | @Override
method removeBlob (line 290) | @Override
method removeBlobs (line 295) | @Override
method getBlobAccess (line 308) | @Override
method setBlobAccess (line 313) | @Override
method copyBlob (line 319) | @Override
method list (line 326) | @Override
method list (line 334) | @Override
method clearContainer (line 344) | @Override
method clearContainer (line 356) | @Override
method initiateMultipartUpload (line 365) | @Override
method abortMultipartUpload (line 375) | @Override
method completeMultipartUpload (line 380) | @Override
method uploadMultipartPart (line 387) | @Override
method listMultipartUpload (line 394) | @Override
method listMultipartUploads (line 399) | @Override
FILE: src/main/java/org/gaul/s3proxy/PutOptions2.java
class PutOptions2 (line 29) | public final class PutOptions2 extends PutOptions {
method PutOptions2 (line 35) | public PutOptions2() {
method PutOptions2 (line 39) | public PutOptions2(PutOptions options) {
method getIfMatch (line 50) | @Nullable
method setIfMatch (line 55) | public PutOptions2 setIfMatch(@Nullable String etag) {
method getIfNoneMatch (line 60) | @Nullable
method setIfNoneMatch (line 65) | public PutOptions2 setIfNoneMatch(@Nullable String etag) {
method setBlobAccess (line 70) | @Override
method multipart (line 76) | @Override
method multipart (line 82) | @Override
method multipart (line 88) | @Override
method setCustomExecutor (line 94) | @Override
method toString (line 100) | @Override
FILE: src/main/java/org/gaul/s3proxy/Quirks.java
class Quirks (line 21) | final class Quirks {
method Quirks (line 130) | private Quirks() {
FILE: src/main/java/org/gaul/s3proxy/ReadOnlyBlobStore.java
class ReadOnlyBlobStore (line 34) | final class ReadOnlyBlobStore extends ForwardingBlobStore {
method ReadOnlyBlobStore (line 35) | private ReadOnlyBlobStore(BlobStore blobStore) {
method newReadOnlyBlobStore (line 39) | static BlobStore newReadOnlyBlobStore(BlobStore blobStore) {
method createContainerInLocation (line 43) | @Override
method deleteContainer (line 49) | @Override
method deleteContainerIfEmpty (line 54) | @Override
method putBlob (line 59) | @Override
method putBlob (line 64) | @Override
method removeBlob (line 70) | @Override
method removeBlobs (line 75) | @Override
method copyBlob (line 81) | @Override
method initiateMultipartUpload (line 88) | @Override
method abortMultipartUpload (line 94) | @Override
method completeMultipartUpload (line 99) | @Override
method uploadMultipartPart (line 105) | @Override
FILE: src/main/java/org/gaul/s3proxy/RegexBlobStore.java
class RegexBlobStore (line 56) | public final class RegexBlobStore extends ForwardingBlobStore {
method RegexBlobStore (line 62) | private RegexBlobStore(BlobStore blobStore,
method newRegexBlobStore (line 68) | static BlobStore newRegexBlobStore(BlobStore delegate,
method parseRegexs (line 73) | public static List<Map.Entry<Pattern, String>> parseRegexs(
method directoryExists (line 118) | @Override
method createDirectory (line 123) | @Override
method deleteDirectory (line 128) | @Override
method blobExists (line 133) | @Override
method putBlob (line 138) | @Override
method putBlob (line 149) | @Override
method copyBlob (line 161) | @Override
method blobMetadata (line 168) | @Override
method getBlob (line 173) | @Override
method removeBlob (line 178) | @Override
method removeBlobs (line 183) | @Override
method getBlobAccess (line 192) | @Override
method setBlobAccess (line 197) | @Override
method downloadBlob (line 203) | @Override
method downloadBlob (line 208) | @Override
method streamBlob (line 215) | @Override
method streamBlob (line 220) | @Override
method replaceBlobName (line 226) | private String replaceBlobName(String name) {
FILE: src/main/java/org/gaul/s3proxy/S3AuthorizationHeader.java
class S3AuthorizationHeader (line 26) | final class S3AuthorizationHeader {
method S3AuthorizationHeader (line 43) | S3AuthorizationHeader(String header) {
method toString (line 96) | @Override
method extractSignature (line 107) | private static String extractSignature(String header) {
method getAuthenticationType (line 121) | public AuthenticationType getAuthenticationType() {
method getHmacAlgorithm (line 125) | public String getHmacAlgorithm() {
method getHashAlgorithm (line 129) | public String getHashAlgorithm() {
method getRegion (line 133) | public String getRegion() {
method getDate (line 137) | public String getDate() {
method getService (line 141) | public String getService() {
method getIdentity (line 145) | public String getIdentity() {
method getSignature (line 149) | public String getSignature() {
FILE: src/main/java/org/gaul/s3proxy/S3ErrorCode.java
type S3ErrorCode (line 29) | public enum S3ErrorCode {
method S3ErrorCode (line 104) | S3ErrorCode(int httpStatusCode, String message) {
method getErrorCode (line 111) | String getErrorCode() {
method getHttpStatusCode (line 115) | int getHttpStatusCode() {
method getMessage (line 119) | String getMessage() {
method toString (line 123) | @Override
FILE: src/main/java/org/gaul/s3proxy/S3Exception.java
class S3Exception (line 23) | @SuppressWarnings("serial")
method S3Exception (line 28) | S3Exception(S3ErrorCode error) {
method S3Exception (line 32) | S3Exception(S3ErrorCode error, String message) {
method S3Exception (line 36) | S3Exception(S3ErrorCode error, Throwable cause) {
method S3Exception (line 40) | S3Exception(S3ErrorCode error, String message, Throwable cause) {
method S3Exception (line 44) | S3Exception(S3ErrorCode error, String message, Throwable cause,
method getError (line 51) | S3ErrorCode getError() {
method getElements (line 55) | Map<String, String> getElements() {
method getMessage (line 59) | @Override
FILE: src/main/java/org/gaul/s3proxy/S3Operation.java
type S3Operation (line 20) | public enum S3Operation {
method S3Operation (line 50) | S3Operation(String value) {
method getValue (line 54) | public String getValue() {
FILE: src/main/java/org/gaul/s3proxy/S3Proxy.java
class S3Proxy (line 54) | public final class S3Proxy {
method S3Proxy (line 61) | S3Proxy(Builder builder) {
class Builder (line 155) | public static final class Builder {
method Builder (line 179) | Builder() {
method build (line 182) | public S3Proxy build() {
method fromProperties (line 186) | public static Builder fromProperties(Properties properties)
method blobStore (line 369) | public Builder blobStore(BlobStore blobStore) {
method endpoint (line 374) | public Builder endpoint(URI endpoint) {
method secureEndpoint (line 379) | public Builder secureEndpoint(URI secureEndpoint) {
method awsAuthentication (line 384) | public Builder awsAuthentication(AuthenticationType authenticationType,
method sslContext (line 394) | public Builder sslContext(SSLContext sslContext) {
method keyStore (line 401) | public Builder keyStore(String keyStorePath, String keyStorePassword) {
method virtualHost (line 408) | public Builder virtualHost(String virtualHost) {
method maxSinglePartObjectSize (line 413) | public Builder maxSinglePartObjectSize(long maxSinglePartObjectSize) {
method v4MaxNonChunkedRequestSize (line 423) | public Builder v4MaxNonChunkedRequestSize(
method v4MaxChunkSize (line 434) | public Builder v4MaxChunkSize(int v4MaxChunkSize) {
method ignoreUnknownHeaders (line 444) | public Builder ignoreUnknownHeaders(boolean ignoreUnknownHeaders) {
method corsRules (line 449) | public Builder corsRules(CrossOriginResourceSharing corsRules) {
method jettyMaxThreads (line 454) | public Builder jettyMaxThreads(int jettyMaxThreads) {
method maximumTimeSkew (line 459) | public Builder maximumTimeSkew(int maximumTimeSkew) {
method metricsEnabled (line 464) | public Builder metricsEnabled(boolean metricsEnabled) {
method metricsPort (line 469) | public Builder metricsPort(int metricsPort) {
method metricsHost (line 474) | public Builder metricsHost(String metricsHost) {
method servicePath (line 479) | public Builder servicePath(String s3ProxyServicePath) {
method getEndpoint (line 493) | public URI getEndpoint() {
method getSecureEndpoint (line 497) | public URI getSecureEndpoint() {
method getServicePath (line 501) | public String getServicePath() {
method getIdentity (line 505) | public String getIdentity() {
method getCredential (line 509) | public String getCredential() {
method equals (line 513) | @Override
method hashCode (line 539) | @Override
method builder (line 548) | public static Builder builder() {
method start (line 552) | public void start() throws Exception {
method stop (line 556) | public void stop() throws Exception {
method getPort (line 563) | public int getPort() {
method getSecurePort (line 571) | public int getSecurePort() {
method getState (line 585) | public String getState() {
method setBlobStoreLocator (line 589) | public void setBlobStoreLocator(BlobStoreLocator lookup) {
FILE: src/main/java/org/gaul/s3proxy/S3ProxyConstants.java
class S3ProxyConstants (line 19) | public final class S3ProxyConstants {
method S3ProxyConstants (line 161) | private S3ProxyConstants() {
FILE: src/main/java/org/gaul/s3proxy/S3ProxyHandler.java
class S3ProxyHandler (line 120) | public class S3ProxyHandler {
class RequestContext (line 124) | public static final class RequestContext {
method getOperation (line 128) | public S3Operation getOperation() {
method setOperation (line 132) | public void setOperation(S3Operation operation) {
method getBucket (line 136) | public String getBucket() {
method setBucket (line 140) | public void setBucket(String bucket) {
method S3ProxyHandler (line 262) | public S3ProxyHandler(final BlobStore blobStore,
method getBlobStoreType (line 311) | private static String getBlobStoreType(BlobStore blobStore) {
method isValidContainer (line 315) | private static boolean isValidContainer(String containerName) {
method doHandle (line 327) | public final void doHandle(HttpServletRequest baseRequest,
method setOperation (line 879) | private static void setOperation(@Nullable RequestContext ctx,
method checkPublicAccess (line 886) | private static boolean checkPublicAccess(BlobStore blobStore,
method doHandleAnonymous (line 905) | private void doHandleAnonymous(HttpServletRequest request,
method handleGetContainerAcl (line 995) | private void handleGetContainerAcl(HttpServletRequest request,
method handleSetContainerAcl (line 1061) | private void handleSetContainerAcl(HttpServletRequest request,
method handleGetBlobAcl (line 1098) | private void handleGetBlobAcl(HttpServletRequest request,
method handleSetBlobAcl (line 1161) | private void handleSetBlobAcl(HttpServletRequest request,
method mapXmlAclsToCannedPolicy (line 1200) | private static String mapXmlAclsToCannedPolicy(
method handleContainerList (line 1236) | private void handleContainerList(HttpServletRequest request,
method handleContainerLocation (line 1281) | private void handleContainerLocation(HttpServletRequest request,
method handleBucketPolicy (line 1300) | private static void handleBucketPolicy(BlobStore blobStore,
method handleListMultipartUploads (line 1308) | private void handleListMultipartUploads(HttpServletRequest request,
method handleContainerExists (line 1386) | private void handleContainerExists(HttpServletRequest request,
method handleContainerCreate (line 1395) | private void handleContainerCreate(HttpServletRequest request,
method handleContainerDelete (line 1471) | private void handleContainerDelete(HttpServletRequest request,
method handleBlobList (line 1496) | private void handleBlobList(HttpServletRequest request,
method handleBlobRemove (line 1705) | private void handleBlobRemove(HttpServletRequest request,
method handleMultiBlobRemove (line 1714) | private void handleMultiBlobRemove(HttpServletRequest request,
method handleBlobMetadata (line 1784) | private void handleBlobMetadata(HttpServletRequest request,
method handleOptionsBlob (line 1832) | private void handleOptionsBlob(HttpServletRequest request,
method handleGetBlob (line 1881) | private void handleGetBlob(HttpServletRequest request,
method handleCopyBlob (line 1957) | private void handleCopyBlob(HttpServletRequest request,
method handlePutBlob (line 2083) | private void handlePutBlob(HttpServletRequest request,
method handleStatuszRequest (line 2253) | private void handleStatuszRequest(HttpServletResponse response)
method loadGitHash (line 2270) | private static String loadGitHash() {
method handlePostBlob (line 2289) | private void handlePostBlob(HttpServletRequest request,
method handleInitiateMultipartUpload (line 2466) | private void handleInitiateMultipartUpload(HttpServletRequest request,
method handleCompleteMultipartUpload (line 2548) | private void handleCompleteMultipartUpload(HttpServletRequest request,
method handleAbortMultipartUpload (line 2748) | private void handleAbortMultipartUpload(HttpServletRequest request,
method handleListParts (line 2775) | private void handleListParts(HttpServletRequest request,
method handleCopyPart (line 2875) | private void handleCopyPart(HttpServletRequest request,
method handleUploadPart (line 3076) | private void handleUploadPart(HttpServletRequest request,
method addResponseHeaderWithOverride (line 3212) | private static void addResponseHeaderWithOverride(
method addMetadataToResponse (line 3225) | private static void addMetadataToResponse(HttpServletRequest request,
method parseIso8601 (line 3281) | private static long parseIso8601(String date) {
method isTimeSkewed (line 3292) | private void isTimeSkewed(
method generateRequestId (line 3313) | private static String generateRequestId() {
method formatDate (line 3317) | private static String formatDate(Date date) {
method sendSimpleErrorResponse (line 3324) | protected final void sendSimpleErrorResponse(
method addCorsResponseHeader (line 3371) | private void addCorsResponseHeader(HttpServletRequest request,
method addContentMetadataFromHttpRequest (line 3389) | private static void addContentMetadataFromHttpRequest(
method writeInitiatorStanza (line 3420) | private static void writeInitiatorStanza(XMLStreamWriter xml)
method writeOwnerStanza (line 3432) | private static void writeOwnerStanza(XMLStreamWriter xml)
method writeSimpleElement (line 3442) | private static void writeSimpleElement(XMLStreamWriter xml,
method createFakeBlobMetadata (line 3449) | private static BlobMetadata createFakeBlobMetadata(BlobStore blobStore) {
method equalsIgnoringSurroundingQuotes (line 3455) | private static boolean equalsIgnoringSurroundingQuotes(String s1,
method maybeQuoteETag (line 3466) | private static String maybeQuoteETag(String eTag) {
method startsWithIgnoreCase (line 3473) | private static boolean startsWithIgnoreCase(String string, String pref...
method hmac (line 3477) | private static byte[] hmac(String algorithm, byte[] data, byte[] key) {
method encodeBlob (line 3489) | private static String encodeBlob(String encodingType, String blobName) {
class UncloseableInputStream (line 3497) | private static final class UncloseableInputStream
method UncloseableInputStream (line 3499) | UncloseableInputStream(InputStream is) {
method close (line 3503) | @Override
method getBlobStoreLocator (line 3508) | public final BlobStoreLocator getBlobStoreLocator() {
method setBlobStoreLocator (line 3512) | public final void setBlobStoreLocator(BlobStoreLocator locator) {
method validateIpAddress (line 3516) | private static boolean validateIpAddress(String string) {
method constantTimeEquals (line 3534) | private static boolean constantTimeEquals(String x, String y) {
FILE: src/main/java/org/gaul/s3proxy/S3ProxyHandlerJetty.java
class S3ProxyHandlerJetty (line 42) | final class S3ProxyHandlerJetty extends HttpServlet {
method S3ProxyHandlerJetty (line 49) | S3ProxyHandlerJetty(final BlobStore blobStore,
method sendS3Exception (line 65) | private void sendS3Exception(HttpServletRequest request,
method service (line 72) | @Override
method recordMetrics (line 183) | private void recordMetrics(HttpServletRequest request,
method getHandler (line 199) | public S3ProxyHandler getHandler() {
FILE: src/main/java/org/gaul/s3proxy/S3ProxyMetrics.java
class S3ProxyMetrics (line 33) | public final class S3ProxyMetrics {
method S3ProxyMetrics (line 53) | public S3ProxyMetrics() {
method S3ProxyMetrics (line 57) | public S3ProxyMetrics(String host, int port) {
method recordRequest (line 76) | public void recordRequest(
method scrape (line 102) | public String scrape() {
method close (line 106) | public void close() {
FILE: src/main/java/org/gaul/s3proxy/ShardedBlobStore.java
class ShardedBlobStore (line 87) | final class ShardedBlobStore extends ForwardingBlobStore {
class ShardedBucket (line 105) | private static final class ShardedBucket {
method ShardedBucket (line 109) | private ShardedBucket(String name, int shards) {
method ShardedBlobStore (line 115) | private ShardedBlobStore(BlobStore blobStore,
method parseBucketShards (line 144) | public static Map<String, Integer> parseBucketShards(
method parsePrefixes (line 162) | public static Map<String, String> parsePrefixes(Properties properties) {
method newShardedBlobStore (line 175) | static ShardedBlobStore newShardedBlobStore(
method createSuperblockMeta (line 182) | private Map<String, String> createSuperblockMeta(ShardedBucket bucket) {
method getShardContainer (line 190) | private static String getShardContainer(ShardedBucket bucket, int shar...
method getShard (line 194) | private String getShard(String containerName, String blob) {
method checkSuperBlock (line 204) | private void checkSuperBlock(Blob blob, Map<String, String> expectedMeta,
method createShards (line 219) | private boolean createShards(ShardedBucket bucket, Location location,
method createContainerInLocation (line 246) | @Override
method createContainerInLocation (line 253) | @SuppressWarnings("EmptyCatch")
method list (line 292) | @Override
method list (line 332) | @Override
method list (line 341) | @Override
method containerExists (line 352) | @Override
method getContainerAccess (line 360) | @Override
method setContainerAccess (line 368) | @Override
method clearContainer (line 377) | @Override
method clearContainer (line 382) | @Override
method deleteContainer (line 387) | @Override
method deleteShards (line 392) | private boolean deleteShards(ShardedBucket bucket) {
method deleteContainerIfEmpty (line 415) | @Override
method directoryExists (line 438) | @Override
method createDirectory (line 443) | @Override
method deleteDirectory (line 448) | @Override
method blobExists (line 453) | @Override
method putBlob (line 458) | @Override
method putBlob (line 464) | @Override
method copyBlob (line 472) | @Override
method blobMetadata (line 482) | @Override
method getBlob (line 488) | @Override
method getBlob (line 494) | @Override
method removeBlob (line 502) | @Override
method removeBlobs (line 507) | @Override
method getBlobAccess (line 526) | @Override
method setBlobAccess (line 532) | @Override
method countBlobs (line 539) | @Override
method countBlobs (line 547) | @Override
method initiateMultipartUpload (line 555) | @Override
method abortMultipartUpload (line 566) | @Override
method completeMultipartUpload (line 574) | @Override
method uploadMultipartPart (line 583) | @Override
method listMultipartUpload (line 593) | @Override
method listMultipartUploads (line 601) | @Override
method downloadBlob (line 609) | @Override
method downloadBlob (line 615) | @Override
method streamBlob (line 623) | @Override
method streamBlob (line 628) | @Override
FILE: src/main/java/org/gaul/s3proxy/StorageClassBlobStore.java
class StorageClassBlobStore (line 41) | public final class StorageClassBlobStore extends ForwardingBlobStore {
method StorageClassBlobStore (line 44) | private StorageClassBlobStore(BlobStore delegate,
method newStorageClassBlobStore (line 57) | static StorageClassBlobStore newStorageClassBlobStore(BlobStore blobSt...
method getTier (line 62) | public Tier getTier() {
method putBlob (line 66) | @Override
method putBlob (line 72) | @Override
method initiateMultipartUpload (line 79) | @Override
method replaceTier (line 87) | private Blob replaceTier(String containerName, Blob blob) {
method replaceTier (line 104) | private BlobMetadata replaceTier(BlobMetadata meta) {
FILE: src/main/java/org/gaul/s3proxy/ThrottledInputStream.java
class ThrottledInputStream (line 23) | final class ThrottledInputStream extends FilterInputStream {
method ThrottledInputStream (line 26) | ThrottledInputStream(InputStream is, Long speed) {
method read (line 31) | @Override
method read (line 40) | @Override
method simulateLatency (line 49) | private void simulateLatency(int size) {
FILE: src/main/java/org/gaul/s3proxy/UserMetadataReplacerBlobStore.java
class UserMetadataReplacerBlobStore (line 38) | final class UserMetadataReplacerBlobStore extends ForwardingBlobStore {
method UserMetadataReplacerBlobStore (line 42) | private UserMetadataReplacerBlobStore(
method newUserMetadataReplacerBlobStore (line 50) | public static BlobStore newUserMetadataReplacerBlobStore(
method putBlob (line 55) | @Override
method putBlob (line 60) | @Override
method blobMetadata (line 73) | @Override
method getBlob (line 90) | @Override
method getBlob (line 95) | @Override
method initiateMultipartUpload (line 112) | @Override
method replaceChars (line 125) | private static String replaceChars(String value, String fromChars,
FILE: src/main/java/org/gaul/s3proxy/awssdk/AwsS3SdkApiMetadata.java
class AwsS3SdkApiMetadata (line 28) | @SuppressWarnings("rawtypes")
method AwsS3SdkApiMetadata (line 60) | public AwsS3SdkApiMetadata() {
method AwsS3SdkApiMetadata (line 64) | protected AwsS3SdkApiMetadata(Builder builder) {
method builder (line 68) | private static Builder builder() {
method toBuilder (line 72) | @Override
method defaultProperties (line 77) | public static Properties defaultProperties() {
type AwsS3SdkClient (line 87) | private interface AwsS3SdkClient {
class Builder (line 90) | public static final class Builder
method Builder (line 92) | protected Builder() {
method build (line 108) | @Override
method self (line 113) | @Override
FILE: src/main/java/org/gaul/s3proxy/awssdk/AwsS3SdkBlobStore.java
class AwsS3SdkBlobStore (line 126) | @Singleton
method AwsS3SdkBlobStore (line 134) | @Inject
method list (line 190) | @Override
method list (line 210) | @Override
method containerExists (line 280) | @Override
method createContainerInLocation (line 297) | @Override
method createContainerInLocation (line 304) | @Override
method deleteContainer (line 344) | @Override
method deleteContainerIfEmpty (line 359) | @Override
method blobExists (line 384) | @Override
method getBlob (line 402) | @Override
method putBlob (line 484) | @Override
method putBlob (line 489) | @Override
method copyBlob (line 578) | @Override
method removeBlob (line 630) | @Override
method blobMetadata (line 646) | @Override
method deleteAndVerifyContainerGone (line 676) | @Override
method getContainerAccess (line 688) | @Override
method setContainerAccess (line 707) | @Override
method getBlobAccess (line 724) | @Override
method hasPublicRead (line 745) | private static boolean hasPublicRead(List<Grant> grants) {
method translateAclNotFound (line 757) | private RuntimeException translateAclNotFound(String container, String...
method applyMultipartAclIfNeeded (line 773) | private void applyMultipartAclIfNeeded(MultipartUpload mpu) {
method setBlobAccess (line 783) | @Override
method initiateMultipartUpload (line 803) | @Override
method abortMultipartUpload (line 860) | @Override
method completeMultipartUpload (line 881) | @Override
method uploadMultipartPart (line 911) | @Override
method listMultipartUpload (line 938) | @Override
method listMultipartUploads (line 973) | @Override
method getMinimumMultipartPartSize (line 1012) | @Override
method getMaximumMultipartPartSize (line 1018) | @Override
method getMaximumNumberOfParts (line 1024) | @Override
method streamBlob (line 1029) | @Override
method sortAndValidateParts (line 1034) | private static List<MultipartPart> sortAndValidateParts(
method toDate (line 1059) | private static Date toDate(@Nullable Instant instant) {
method toStorageClass (line 1066) | private static StorageClass toStorageClass(Tier tier) {
method toTier (line 1075) | private static Tier toTier(@Nullable StorageClass storageClass) {
method toTier (line 1087) | private static Tier toTier(
method toContentMetadata (line 1101) | private static org.jclouds.io.ContentMetadata toContentMetadata(
method translateAndRethrowException (line 1128) | private void translateAndRethrowException(S3Exception e,
method maybeQuoteETag (line 1166) | private static String maybeQuoteETag(String eTag) {
method maybeStripETagQuotes (line 1180) | private String maybeStripETagQuotes(String eTag) {
method equalsIgnoringSurroundingQuotes (line 1193) | private static boolean equalsIgnoringSurroundingQuotes(
method throwPreconditionFailed (line 1204) | private void throwPreconditionFailed() {
method throwKeyNotFound (line 1216) | private void throwKeyNotFound(String container, String key) {
method validateConditionalPut (line 1225) | private void validateConditionalPut(String container, String blobName,
method validateIfMatch (line 1238) | private void validateIfMatch(String container, String blobName,
method validateIfNoneMatch (line 1259) | private void validateIfNoneMatch(String ifNoneMatch,
FILE: src/main/java/org/gaul/s3proxy/awssdk/AwsS3SdkBlobStoreContextModule.java
class AwsS3SdkBlobStoreContextModule (line 25) | public final class AwsS3SdkBlobStoreContextModule extends AbstractModule {
method configure (line 26) | @Override
FILE: src/main/java/org/gaul/s3proxy/awssdk/AwsS3SdkProviderMetadata.java
class AwsS3SdkProviderMetadata (line 26) | @AutoService(ProviderMetadata.class)
method AwsS3SdkProviderMetadata (line 28) | public AwsS3SdkProviderMetadata() {
method AwsS3SdkProviderMetadata (line 32) | public AwsS3SdkProviderMetadata(Builder builder) {
method builder (line 36) | public static Builder builder() {
method toBuilder (line 40) | @Override
method defaultProperties (line 45) | public static Properties defaultProperties() {
class Builder (line 50) | public static final class Builder extends BaseProviderMetadata.Builder {
method Builder (line 51) | protected Builder() {
method build (line 60) | @Override
method fromProviderMetadata (line 65) | @Override
FILE: src/main/java/org/gaul/s3proxy/azureblob/AzureBlobApiMetadata.java
class AzureBlobApiMetadata (line 31) | @SuppressWarnings("rawtypes")
method AzureBlobApiMetadata (line 33) | public AzureBlobApiMetadata() {
method AzureBlobApiMetadata (line 37) | protected AzureBlobApiMetadata(Builder builder) {
method builder (line 41) | private static Builder builder() {
method toBuilder (line 45) | @Override
method defaultProperties (line 50) | public static Properties defaultProperties() {
type AzureBlobClient (line 62) | private interface AzureBlobClient {
class Builder (line 65) | public static final class Builder
method Builder (line 67) | protected Builder() {
method build (line 85) | @Override
method self (line 90) | @Override
FILE: src/main/java/org/gaul/s3proxy/azureblob/AzureBlobProviderMetadata.java
class AzureBlobProviderMetadata (line 34) | @AutoService(ProviderMetadata.class)
method AzureBlobProviderMetadata (line 36) | public AzureBlobProviderMetadata() {
method AzureBlobProviderMetadata (line 40) | public AzureBlobProviderMetadata(Builder builder) {
method builder (line 44) | public static Builder builder() {
method toBuilder (line 48) | @Override
method defaultProperties (line 53) | public static Properties defaultProperties() {
class Builder (line 63) | public static final class Builder extends BaseProviderMetadata.Builder {
method Builder (line 64) | protected Builder() {
method build (line 79) | @Override
method fromProviderMetadata (line 84) | @Override
FILE: src/main/java/org/gaul/s3proxy/azureblob/AzureBlobStore.java
class AzureBlobStore (line 118) | @Singleton
method AzureBlobStore (line 134) | @Inject
method list (line 159) | @Override
method list (line 173) | @Override
method containerExists (line 221) | @Override
method createContainerInLocation (line 227) | @Override
method createContainerInLocation (line 234) | @Override
method deleteContainer (line 256) | @Override
method deleteContainerIfEmpty (line 267) | @Override
method blobExists (line 287) | @Override
method getBlob (line 294) | @Override
method putBlob (line 383) | @Override
method putBlob (line 388) | @Override
method copyBlob (line 447) | @Override
method removeBlob (line 519) | @Override
method blobMetadata (line 533) | @Override
method deleteAndVerifyContainerGone (line 556) | @Override
method getContainerAccess (line 562) | @Override
method setContainerAccess (line 577) | @Override
method getBlobAccess (line 585) | @Override
method setBlobAccess (line 590) | @Override
method initiateMultipartUpload (line 595) | @Override
method isValidMetadataKey (line 656) | private static boolean isValidMetadataKey(String key) {
method abortMultipartUpload (line 674) | @Override
method completeMultipartUpload (line 692) | @Override
method uploadMultipartPart (line 824) | @Override
method createNonRetryingBlockBlobAsyncClient (line 945) | private BlockBlobAsyncClient createNonRetryingBlockBlobAsyncClient(
method listMultipartUpload (line 966) | @Override
method listMultipartUploads (line 1034) | @Override
method getMinimumMultipartPartSize (line 1063) | @Override
method getMaximumMultipartPartSize (line 1068) | @Override
method getMaximumNumberOfParts (line 1073) | @Override
method streamBlob (line 1078) | @Override
method toOffsetDateTime (line 1083) | private static OffsetDateTime toOffsetDateTime(@Nullable Date date) {
method toDate (line 1090) | private static Date toDate(OffsetDateTime time) {
method toAccessTier (line 1094) | private static AccessTier toAccessTier(Tier tier) {
method toTier (line 1104) | private static Tier toTier(AccessTier tier) {
method toContentMetadata (line 1118) | private static ContentMetadata toContentMetadata(
method makeBlockId (line 1145) | private static String makeBlockId(String nonce, int partNumber) {
method translateAndRethrowException (line 1155) | private void translateAndRethrowException(BlobStorageException bse,
FILE: src/main/java/org/gaul/s3proxy/azureblob/AzureBlobStoreContextModule.java
class AzureBlobStoreContextModule (line 25) | public final class AzureBlobStoreContextModule extends AbstractModule {
method configure (line 26) | @Override
FILE: src/main/java/org/gaul/s3proxy/crypto/Constants.java
class Constants (line 22) | public final class Constants {
method Constants (line 45) | private Constants() {
FILE: src/main/java/org/gaul/s3proxy/crypto/Decryption.java
class Decryption (line 35) | public class Decryption {
method Decryption (line 48) | public Decryption(SecretKeySpec key, BlobStore blobStore,
method blobIsNotEncrypted (line 143) | private void blobIsNotEncrypted(long offset) {
method calculateTail (line 150) | public final long calculateTail() {
method getEncryptedSize (line 157) | public final long getEncryptedSize() {
method getUnencryptedSize (line 161) | public final long getUnencryptedSize() {
method calculateEndAt (line 165) | public final long calculateEndAt(long endAt) {
method openStream (line 201) | public final InputStream openStream(InputStream is) throws IOException {
method calculateOffset (line 222) | private void calculateOffset(long offset) {
method getStartAt (line 301) | public final long getStartAt() {
method isEncrypted (line 305) | public final boolean isEncrypted() {
method getContentLength (line 309) | public final long getContentLength() {
FILE: src/main/java/org/gaul/s3proxy/crypto/DecryptionInputStream.java
class DecryptionInputStream (line 28) | public class DecryptionInputStream extends FilterInputStream {
method DecryptionInputStream (line 75) | public DecryptionInputStream(InputStream is, SecretKey key,
method ensureCapacity (line 107) | private void ensureCapacity(int inLen) {
method getMoreData (line 134) | private int getMoreData() throws IOException {
method read (line 212) | @Override
method read (line 242) | @Override
method read (line 263) | @Override
method skip (line 306) | @Override
method available (line 328) | @Override
method close (line 343) | @Override
method markSupported (line 374) | @Override
FILE: src/main/java/org/gaul/s3proxy/crypto/Encryption.java
class Encryption (line 29) | public class Encryption {
method Encryption (line 34) | public Encryption(SecretKeySpec key, InputStream isRaw, int partNumber)
method openStream (line 44) | public final InputStream openStream() throws IOException {
method generateIV (line 48) | private IvParameterSpec generateIV() {
FILE: src/main/java/org/gaul/s3proxy/crypto/EncryptionInputStream.java
class EncryptionInputStream (line 26) | public class EncryptionInputStream extends InputStream {
method EncryptionInputStream (line 34) | public EncryptionInputStream(InputStream in, int part,
method padding (line 48) | final void padding() throws IOException {
method available (line 68) | @Override
method read (line 76) | @Override
method read (line 89) | @Override
method close (line 111) | @Override
FILE: src/main/java/org/gaul/s3proxy/crypto/PartPadding.java
class PartPadding (line 30) | public class PartPadding {
method readPartPaddingFromBlob (line 40) | public static PartPadding readPartPaddingFromBlob(Blob blob)
method getDelimiter (line 72) | public final String getDelimiter() {
method getIv (line 76) | public final IvParameterSpec getIv() {
method getPart (line 80) | public final int getPart() {
method getSize (line 84) | public final long getSize() {
FILE: src/main/java/org/gaul/s3proxy/gcloudsdk/GCloudApiMetadata.java
class GCloudApiMetadata (line 29) | @SuppressWarnings("rawtypes")
method GCloudApiMetadata (line 31) | public GCloudApiMetadata() {
method GCloudApiMetadata (line 35) | protected GCloudApiMetadata(Builder builder) {
method builder (line 39) | private static Builder builder() {
method toBuilder (line 43) | @Override
method defaultProperties (line 48) | public static Properties defaultProperties() {
type GCloudClient (line 56) | private interface GCloudClient {
class Builder (line 59) | public static final class Builder
method Builder (line 61) | protected Builder() {
method build (line 77) | @Override
method self (line 82) | @Override
FILE: src/main/java/org/gaul/s3proxy/gcloudsdk/GCloudBlobStore.java
class GCloudBlobStore (line 102) | @Singleton
method GCloudBlobStore (line 113) | @Inject
method list (line 154) | @Override
method list (line 168) | @Override
method containerExists (line 231) | @Override
method createContainerInLocation (line 237) | @Override
method createContainerInLocation (line 244) | @Override
method deleteContainer (line 267) | @Override
method deleteContainerIfEmpty (line 283) | @Override
method blobExists (line 301) | @Override
method getBlob (line 307) | @Override
method putBlob (line 394) | @Override
method putBlob (line 400) | @Override
method copyBlob (line 452) | @Override
method removeBlob (line 500) | @Override
method blobMetadata (line 511) | @Override
method deleteAndVerifyContainerGone (line 539) | @Override
method getContainerAccess (line 552) | @Override
method setContainerAccess (line 571) | @Override
method getBlobAccess (line 586) | @Override
method setBlobAccess (line 591) | @Override
method initiateMultipartUpload (line 598) | @Override
method abortMultipartUpload (line 660) | @Override
method completeMultipartUpload (line 685) | @Override
method composeRecursive (line 807) | private String composeRecursive(String container, BlobInfo target,
method uploadMultipartPart (line 847) | @Override
method listMultipartUpload (line 908) | @Override
method listMultipartUploads (line 933) | @Override
method getMinimumMultipartPartSize (line 956) | @Override
method getMaximumMultipartPartSize (line 962) | @Override
method getMaximumNumberOfParts (line 967) | @Override
method streamBlob (line 973) | @Override
method makePartBlobName (line 978) | private static String makePartBlobName(String nonce, int partNumber) {
method getGeneration (line 987) | private long getGeneration(String container, String name,
method toDate (line 1008) | private static Date toDate(
method toStorageClass (line 1016) | private static com.google.cloud.storage.StorageClass toStorageClass(
method toTier (line 1029) | private static Tier toTier(
method toContentMetadata (line 1047) | private static ContentMetadata toContentMetadata(Blob blob) {
method translateAndRethrowException (line 1061) | private static void translateAndRethrowException(StorageException se,
FILE: src/main/java/org/gaul/s3proxy/gcloudsdk/GCloudBlobStoreContextModule.java
class GCloudBlobStoreContextModule (line 25) | public final class GCloudBlobStoreContextModule extends AbstractModule {
method configure (line 26) | @Override
FILE: src/main/java/org/gaul/s3proxy/gcloudsdk/GCloudProviderMetadata.java
class GCloudProviderMetadata (line 31) | @AutoService(ProviderMetadata.class)
method GCloudProviderMetadata (line 33) | public GCloudProviderMetadata() {
method GCloudProviderMetadata (line 37) | public GCloudProviderMetadata(Builder builder) {
method builder (line 41) | public static Builder builder() {
method toBuilder (line 45) | @Override
method defaultProperties (line 50) | public static Properties defaultProperties() {
class Builder (line 55) | public static final class Builder extends BaseProviderMetadata.Builder {
method Builder (line 56) | protected Builder() {
method build (line 71) | @Override
method fromProviderMetadata (line 76) | @Override
FILE: src/main/java/org/gaul/s3proxy/junit/S3ProxyExtension.java
class S3ProxyExtension (line 30) | public final class S3ProxyExtension
class Builder (line 35) | public static final class Builder {
method Builder (line 39) | private Builder() {
method withCredentials (line 43) | public Builder withCredentials(AuthenticationType authType,
method withCredentials (line 49) | public Builder withCredentials(String accessKey, String secretKey) {
method withSecretStore (line 54) | public Builder withSecretStore(String path, String password) {
method withPort (line 59) | public Builder withPort(int port) {
method withBlobStoreProvider (line 64) | public Builder withBlobStoreProvider(String blobStoreProvider) {
method ignoreUnknownHeaders (line 69) | public Builder ignoreUnknownHeaders() {
method build (line 74) | public S3ProxyExtension build() {
method S3ProxyExtension (line 79) | private S3ProxyExtension(Builder builder) {
method builder (line 83) | public static Builder builder() {
method beforeEach (line 87) | @Override
method afterEach (line 92) | @Override
method getUri (line 97) | public URI getUri() {
method getAccessKey (line 101) | public String getAccessKey() {
method getSecretKey (line 105) | public String getSecretKey() {
FILE: src/main/java/org/gaul/s3proxy/junit/S3ProxyJunitCore.java
class S3ProxyJunitCore (line 37) | public class S3ProxyJunitCore {
class Builder (line 53) | public static final class Builder {
method withCredentials (line 63) | public Builder withCredentials(AuthenticationType authType,
method withCredentials (line 71) | public Builder withCredentials(String accessKey, String secretKey) {
method withSecretStore (line 76) | public Builder withSecretStore(String path, String password) {
method withPort (line 82) | public Builder withPort(int port) {
method withBlobStoreProvider (line 87) | public Builder withBlobStoreProvider(String blobStoreProvider) {
method ignoreUnknownHeaders (line 92) | public Builder ignoreUnknownHeaders() {
method build (line 97) | public S3ProxyJunitCore build() {
method S3ProxyJunitCore (line 102) | S3ProxyJunitCore(Builder builder) {
method beforeEach (line 145) | public final void beforeEach() throws Exception {
method afterEach (line 156) | public final void afterEach() {
method getUri (line 176) | public final URI getUri() {
method getAccessKey (line 180) | public final String getAccessKey() {
method getSecretKey (line 184) | public final String getSecretKey() {
FILE: src/main/java/org/gaul/s3proxy/junit/S3ProxyRule.java
class S3ProxyRule (line 30) | @Beta
class Builder (line 35) | public static final class Builder {
method Builder (line 39) | private Builder() {
method withCredentials (line 43) | public Builder withCredentials(AuthenticationType authType,
method withCredentials (line 49) | public Builder withCredentials(String accessKey, String secretKey) {
method withSecretStore (line 54) | public Builder withSecretStore(String path, String password) {
method withPort (line 59) | public Builder withPort(int port) {
method withBlobStoreProvider (line 64) | public Builder withBlobStoreProvider(String blobStoreProvider) {
method ignoreUnknownHeaders (line 69) | public Builder ignoreUnknownHeaders() {
method build (line 74) | public S3ProxyRule build() {
method S3ProxyRule (line 79) | private S3ProxyRule(Builder builder) {
method builder (line 83) | public static Builder builder() {
method before (line 87) | @Override
method after (line 92) | @Override
method getUri (line 97) | public URI getUri() {
method getAccessKey (line 101) | public String getAccessKey() {
method getSecretKey (line 105) | public String getSecretKey() {
FILE: src/main/java/org/gaul/s3proxy/nio2blob/AbstractNio2BlobStore.java
class AbstractNio2BlobStore (line 95) | @Singleton
method AbstractNio2BlobStore (line 121) | protected AbstractNio2BlobStore(BlobStoreContext context, BlobUtils bl...
method getRoot (line 131) | protected final Path getRoot() {
method list (line 135) | @Override
method list (line 157) | @Override
method listHelper (line 221) | private void listHelper(ImmutableSortedSet.Builder<StorageMetadata> bu...
method containerExists (line 301) | @Override
method createContainerInLocation (line 306) | @Override
method createContainerInLocation (line 313) | @Override
method deleteContainer (line 329) | @Override
method blobExists (line 340) | @Override
method getBlob (line 345) | @Override
method putBlob (line 549) | @Override
method putBlob (line 554) | @Override
method copyBlob (line 649) | @Override
method removeBlob (line 728) | @Override
method blobMetadata (line 747) | @Override
method deleteAndVerifyContainerGone (line 762) | @Override
method getContainerAccess (line 768) | @Override
method setContainerAccess (line 788) | @Override
method getBlobAccess (line 812) | @Override
method setBlobAccess (line 841) | @Override
method initiateMultipartUpload (line 860) | @Override
method abortMultipartUpload (line 871) | @Override
method completeMultipartUpload (line 880) | @Override
method uploadMultipartPart (line 947) | @Override
method listMultipartUpload (line 959) | @Override
method listMultipartUploads (line 982) | @Override
method getMinimumMultipartPartSize (line 1009) | @Override
method getMaximumMultipartPartSize (line 1014) | @Override
method getMaximumNumberOfParts (line 1019) | @Override
method streamBlob (line 1024) | @Override
method readStringAttributeIfPresent (line 1033) | private static String readStringAttributeIfPresent(
method writeStringAttributeIfPresent (line 1045) | private static void writeStringAttributeIfPresent(
class MultiBlobInputStream (line 1053) | private static final class MultiBlobInputStream extends InputStream {
method MultiBlobInputStream (line 1058) | MultiBlobInputStream(BlobStore blobStore, List<BlobMetadata> metas) {
method read (line 1063) | @Override
method read (line 1083) | @Override
method close (line 1103) | @Override
method returnResponseException (line 1112) | private static HttpResponseException returnResponseException(int code) {
method maybeQuoteETag (line 1120) | private static String maybeQuoteETag(String eTag) {
method removeEmptyParentDirectories (line 1131) | private static void removeEmptyParentDirectories(Path containerPath, P...
method writeCommonMetadataAttr (line 1152) | private static void writeCommonMetadataAttr(UserDefinedFileAttributeVi...
method safeGetXattrs (line 1180) | private static XattrState safeGetXattrs(Path path) {
method checkValidPath (line 1193) | private static void checkValidPath(Path container, Path path) {
method setBlobAccessHelper (line 1199) | private static void setBlobAccessHelper(Path path, BlobAccess access) {
FILE: src/main/java/org/gaul/s3proxy/nio2blob/FilesystemNio2BlobApiMetadata.java
class FilesystemNio2BlobApiMetadata (line 27) | @SuppressWarnings("rawtypes")
method FilesystemNio2BlobApiMetadata (line 29) | public FilesystemNio2BlobApiMetadata() {
method FilesystemNio2BlobApiMetadata (line 33) | protected FilesystemNio2BlobApiMetadata(Builder builder) {
method builder (line 37) | private static Builder builder() {
method toBuilder (line 41) | @Override
method defaultProperties (line 46) | public static Properties defaultProperties() {
type FilesystemNio2BlobClient (line 51) | private interface FilesystemNio2BlobClient {
class Builder (line 54) | public static final class Builder
method Builder (line 56) | protected Builder() {
method build (line 71) | @Override
method self (line 76) | @Override
FILE: src/main/java/org/gaul/s3proxy/nio2blob/FilesystemNio2BlobProviderMetadata.java
class FilesystemNio2BlobProviderMetadata (line 29) | @AutoService(ProviderMetadata.class)
method FilesystemNio2BlobProviderMetadata (line 31) | public FilesystemNio2BlobProviderMetadata() {
method FilesystemNio2BlobProviderMetadata (line 35) | public FilesystemNio2BlobProviderMetadata(Builder builder) {
method builder (line 39) | public static Builder builder() {
method toBuilder (line 43) | @Override
method defaultProperties (line 48) | public static Properties defaultProperties() {
class Builder (line 53) | public static final class Builder extends BaseProviderMetadata.Builder {
method Builder (line 54) | protected Builder() {
method build (line 63) | @Override
method fromProviderMetadata (line 68) | @Override
FILE: src/main/java/org/gaul/s3proxy/nio2blob/FilesystemNio2BlobStore.java
class FilesystemNio2BlobStore (line 38) | @Singleton
method FilesystemNio2BlobStore (line 40) | @Inject
FILE: src/main/java/org/gaul/s3proxy/nio2blob/FilesystemNio2BlobStoreContextModule.java
class FilesystemNio2BlobStoreContextModule (line 25) | public final class FilesystemNio2BlobStoreContextModule extends Abstract...
method configure (line 26) | @Override
FILE: src/main/java/org/gaul/s3proxy/nio2blob/TransientNio2BlobApiMetadata.java
class TransientNio2BlobApiMetadata (line 27) | @SuppressWarnings("rawtypes")
method TransientNio2BlobApiMetadata (line 29) | public TransientNio2BlobApiMetadata() {
method TransientNio2BlobApiMetadata (line 33) | protected TransientNio2BlobApiMetadata(Builder builder) {
method builder (line 37) | private static Builder builder() {
method toBuilder (line 41) | @Override
method defaultProperties (line 46) | public static Properties defaultProperties() {
type TransientNio2BlobClient (line 51) | private interface TransientNio2BlobClient {
class Builder (line 54) | public static final class Builder
method Builder (line 56) | protected Builder() {
method build (line 71) | @Override
method self (line 76) | @Override
FILE: src/main/java/org/gaul/s3proxy/nio2blob/TransientNio2BlobProviderMetadata.java
class TransientNio2BlobProviderMetadata (line 29) | @AutoService(ProviderMetadata.class)
method TransientNio2BlobProviderMetadata (line 31) | public TransientNio2BlobProviderMetadata() {
method TransientNio2BlobProviderMetadata (line 35) | public TransientNio2BlobProviderMetadata(Builder builder) {
method builder (line 39) | public static Builder builder() {
method toBuilder (line 43) | @Override
method defaultProperties (line 48) | public static Properties defaultProperties() {
class Builder (line 53) | public static final class Builder extends BaseProviderMetadata.Builder {
method Builder (line 54) | protected Builder() {
method build (line 63) | @Override
method fromProviderMetadata (line 68) | @Override
FILE: src/main/java/org/gaul/s3proxy/nio2blob/TransientNio2BlobStore.java
class TransientNio2BlobStore (line 36) | @Singleton
method TransientNio2BlobStore (line 38) | @Inject
method TransientNio2BlobStore (line 52) | private TransientNio2BlobStore(BlobStoreContext context, BlobUtils blo...
FILE: src/main/java/org/gaul/s3proxy/nio2blob/TransientNio2BlobStoreContextModule.java
class TransientNio2BlobStoreContextModule (line 25) | public final class TransientNio2BlobStoreContextModule extends AbstractM...
method configure (line 26) | @Override
FILE: src/test/java/org/gaul/s3proxy/AliasBlobStoreTest.java
class AliasBlobStoreTest (line 50) | public final class AliasBlobStoreTest {
method setUp (line 58) | @Before
method tearDown (line 75) | @After
method createContainer (line 85) | private void createContainer(String container) {
method testListNoAliasContainers (line 95) | @Test
method testListAliasContainer (line 105) | @Test
method testAliasBlob (line 118) | @Test
method testAliasMultipartUpload (line 139) | @Test
method testParseDuplicateAliases (line 168) | @Test
FILE: src/test/java/org/gaul/s3proxy/AwsS3SdkBlobStoreTest.java
class AwsS3SdkBlobStoreTest (line 27) | public final class AwsS3SdkBlobStoreTest {
method testProviderRegistration (line 29) | @Test
method testProviderMetadata (line 36) | @Test
method testCustomRegionConfiguration (line 53) | @Test
FILE: src/test/java/org/gaul/s3proxy/AwsSdk2Test.java
class AwsSdk2Test (line 37) | public final class AwsSdk2Test {
method setUp (line 42) | @Before
method tearDown (line 67) | @After
method testPutObject (line 78) | @Test
FILE: src/test/java/org/gaul/s3proxy/AwsSdkAnonymousTest.java
class AwsSdkAnonymousTest (line 45) | public final class AwsSdkAnonymousTest {
method setUp (line 66) | @Before
method tearDown (line 99) | @After
method testListBuckets (line 110) | @Test
method testAwsV4SignatureChunkedAnonymous (line 115) | @Test
method testHealthzEndpoint (line 136) | @Test
method createRandomContainerName (line 164) | private static String createRandomContainerName() {
FILE: src/test/java/org/gaul/s3proxy/AwsSdkTest.java
class AwsSdkTest (line 106) | public final class AwsSdkTest {
method setUp (line 134) | @Before
method tearDown (line 171) | @After
method testAwsV2Signature (line 182) | @Test
method testAwsV2SignatureWithOverrideParameters (line 204) | @Test
method testAwsV4Signature (line 243) | @Test
method testAwsV4SignatureChunkedSigned (line 259) | @Test
method testAwsV4SignatureNonChunked (line 282) | @Test
method testAwsV4SignaturePayloadUnsigned (line 304) | @Test
method testAwsV4SignatureBadIdentity (line 327) | @Test
method testAwsV4SignatureBadCredential (line 349) | @Ignore
method testAwsV2UrlSigning (line 372) | @Test
method testAwsV2UrlSigningWithOverrideParameters (line 396) | @Test
method testAwsV4UrlSigning (line 438) | @Test
method testMultipartCopy (line 456) | @Test
method testBigMultipartUpload (line 503) | @Test
method testMultipartUploadReplace (line 557) | @Test
method testUpdateBlobXmlAcls (line 625) | @Test
method testUnicodeObject (line 656) | @Test
method testSpecialCharacters (line 674) | @Test
method testAtomicMpuAbort (line 707) | @Test
method testOverrideResponseHeader (line 733) | @Test
method testDeleteMultipleObjectsEmpty (line 780) | @Test
method testDeleteMultipleObjects (line 793) | @Test
method testPartNumberMarker (line 819) | @Test
method testHttpClient (line 839) | @Test
method testListBuckets (line 871) | @Test
method testContainerExists (line 880) | @Test
method testContainerCreateDelete (line 892) | @Test
method testContainerDelete (line 909) | @Test
method putBlobAndCheckIt (line 921) | private void putBlobAndCheckIt(String blobName) throws Exception {
method testBlobPutGet (line 935) | @Test
method testBlobEscape (line 942) | @Test
method testBlobList (line 955) | @Test
method testBlobListRecursive (line 982) | @Test
method testBlobListRecursiveImplicitMarker (line 1014) | @Test
method testBlobListV2 (line 1045) | @Test
method testBlobMetadata (line 1105) | @Test
method testBlobRemove (line 1119) | @Test
method testSinglepartUploadJettyCachedHeader (line 1140) | @Test
method testSinglepartUpload (line 1161) | @Test
method testMultipartUpload (line 1223) | @Test
method testMaximumMultipartUpload (line 1310) | @Ignore
method testMultipartUploadAbort (line 1357) | @Test
method testCopyObjectPreserveMetadata (line 1409) | @Test
method testCopyObjectReplaceMetadata (line 1482) | @Test
method testConditionalGet (line 1573) | @Test
method testStorageClass (line 1600) | @Test
method testGetObjectRange (line 1617) | @Test
method testUnknownHeader (line 1638) | @Test
method testGetBucketPolicy (line 1654) | @Test
method testUnknownParameter (line 1664) | @Test
method testBlobStoreLocator (line 1676) | @Test
method testCopyRelativePath (line 1731) | @Test
method testDeleteRelativePath (line 1743) | @Test
method testGetRelativePath (line 1755) | @Test
method testPutRelativePath (line 1765) | @Test
method testListRelativePath (line 1780) | @Test
class NullX509TrustManager (line 1795) | private static final class NullX509TrustManager
method getAcceptedIssuers (line 1797) | @Override
method checkClientTrusted (line 1803) | @Override
method checkServerTrusted (line 1808) | @Override
method disableSslVerification (line 1814) | static void disableSslVerification() {
method createRandomContainerName (line 1841) | static String createRandomContainerName() {
FILE: src/test/java/org/gaul/s3proxy/CrossOriginResourceSharingAllowAllResponseTest.java
class CrossOriginResourceSharingAllowAllResponseTest (line 65) | public final class CrossOriginResourceSharingAllowAllResponseTest {
method setUp (line 84) | @Before
method tearDown (line 121) | @After
method testCorsPreflight (line 135) | @Test
method testCorsActual (line 168) | @Test
method testNonCors (line 192) | @Test
method createRandomContainerName (line 202) | private static String createRandomContainerName() {
method getHttpClient (line 206) | private static CloseableHttpClient getHttpClient() throws
FILE: src/test/java/org/gaul/s3proxy/CrossOriginResourceSharingResponseTest.java
class CrossOriginResourceSharingResponseTest (line 65) | public final class CrossOriginResourceSharingResponseTest {
method setUp (line 85) | @Before
method tearDown (line 124) | @After
method testCorsPreflight (line 138) | @Test
method testCorsPreflightPublicRead (line 217) | @Test
method testCorsActual (line 266) | @Test
method testNonCors (line 288) | @Test
method createRandomContainerName (line 300) | private static String createRandomContainerName() {
method getHttpClient (line 304) | private static CloseableHttpClient getHttpClient() throws
FILE: src/test/java/org/gaul/s3proxy/CrossOriginResourceSharingRuleTest.java
class CrossOriginResourceSharingRuleTest (line 26) | public final class CrossOriginResourceSharingRuleTest {
method setUp (line 31) | @Before
method testCorsOffOrigin (line 48) | @Test
method testCorsOffMethod (line 58) | @Test
method testCorsOffHeader (line 68) | @Test
method testCorsAllOrigin (line 81) | @Test
method testCorsAllMethod (line 94) | @Test
method testCorsAllHeader (line 119) | @Test
method testCorsCfgOrigin (line 132) | @Test
method testCorsCfgMethod (line 151) | @Test
method testCorsCfgHeader (line 167) | @Test
method testAllowCredentials (line 186) | @Test
FILE: src/test/java/org/gaul/s3proxy/EncryptedBlobStoreTest.java
class EncryptedBlobStoreTest (line 62) | @SuppressWarnings("UnstableApiUsage")
method makeBlob (line 72) | private static Blob makeBlob(BlobStore blobStore, String blobName,
method makeBlob (line 81) | private static Blob makeBlob(BlobStore blobStore, String blobName,
method makeBlobWithContentType (line 90) | private static Blob makeBlobWithContentType(BlobStore blobStore,
method setUp (line 103) | @Before
method tearDown (line 130) | @After
method testBlobNotExists (line 138) | @Test
method testBlobNotEncrypted (line 150) | @Test
method testListEncrypted (line 202) | @Test
method testListEncryptedMultipart (line 243) | @Test
method testBlobNotEncryptedRanges (line 306) | @Test
method testEncryptContent (line 389) | @Test
method testEncryptContentWithOptions (line 441) | @Test
method testEncryptMultipartContent (line 511) | @Test
method testReadPartial (line 566) | @Test
method testReadTail (line 600) | @Test
method testReadPartialWithRandomEnd (line 635) | @Test
method testMultipartReadPartial (line 681) | @Test
method testMultipartReadTail (line 731) | @Test
method testMultipartReadPartialWithRandomEnd (line 781) | @Test
method testReadConditional (line 844) | @Test
method testReadDoubleZeroRange (line 863) | @Test
FILE: src/test/java/org/gaul/s3proxy/EventualBlobStoreTest.java
class EventualBlobStoreTest (line 47) | public final class EventualBlobStoreTest {
method setUp (line 60) | @Before
method tearDown (line 87) | @After
method testReadAfterCreate (line 102) | @Test
method testReadAfterDelete (line 113) | @Test
method testOverwriteAfterDelete (line 128) | @Test
method testReadAfterCopy (line 141) | @Test
method testReadAfterMultipartUpload (line 156) | @Test
method testListAfterCreate (line 171) | @Test
method createRandomContainerName (line 181) | private static String createRandomContainerName() {
method createRandomBlobName (line 185) | private static String createRandomBlobName() {
method makeBlob (line 189) | private static Blob makeBlob(BlobStore blobStore, String blobName)
method validateBlob (line 202) | private static void validateBlob(Blob blob) throws IOException {
method delay (line 225) | private static void delay() throws InterruptedException {
FILE: src/test/java/org/gaul/s3proxy/GlobBlobStoreLocatorTest.java
class GlobBlobStoreLocatorTest (line 37) | public final class GlobBlobStoreLocatorTest {
method setUp (line 41) | @Before
method testLocateIdentity (line 56) | @Test
method testLocateContainer (line 70) | @Test
method testLocateGlob (line 98) | @Test
method testGlobLocatorAnonymous (line 123) | @Test
FILE: src/test/java/org/gaul/s3proxy/LatencyBlobStoreTest.java
class LatencyBlobStoreTest (line 47) | public final class LatencyBlobStoreTest {
method setUp (line 52) | @Before
method tearDown (line 65) | @After
method testLoadProperties (line 73) | @Test
method testAllLatency (line 91) | @Test
method testSpecificLatency (line 100) | @Test
method testAllSpeed (line 110) | @Test
method testSpecificSpeed (line 125) | @Test
method testInvalidLatency (line 141) | @Test
method testInvalidSpeed (line 147) | @Test
method testLatencyAndSpeed (line 155) | @Test
method testLatencyAndSpeedWithEmptyContent (line 170) | @Test
method testMultipleOperations (line 185) | @Test
method testSimultaneousOperations (line 203) | @Test
method createRandomContainerName (line 240) | private static String createRandomContainerName() {
method createRandomBlobName (line 244) | private static String createRandomBlobName() {
method time (line 248) | private static long time(Runnable runnable) {
method consume (line 254) | private static void consume(Blob blob) {
FILE: src/test/java/org/gaul/s3proxy/NoCacheBlobStoreTest.java
class NoCacheBlobStoreTest (line 27) | public final class NoCacheBlobStoreTest {
method testResetCacheHeadersKeepRange (line 28) | @Test
method testResetCacheHeadersKeepTail (line 35) | @Test
method testResetCacheHeadersRangeDropCache (line 42) | @Test
method testResetCacheHeadersNoRange (line 56) | @Test
FILE: src/test/java/org/gaul/s3proxy/NullBlobStoreTest.java
class NullBlobStoreTest (line 49) | public final class NullBlobStoreTest {
method setUp (line 57) | @Before
method tearDown (line 72) | @After
method testCreateBlobGetBlob (line 80) | @Test
method testCreateBlobBlobMetadata (line 107) | @Test
method testCreateMultipartBlobGetBlob (line 117) | @Test
method createRandomContainerName (line 169) | private static String createRandomContainerName() {
method createRandomBlobName (line 173) | private static String createRandomBlobName() {
method makeBlob (line 177) | private static Blob makeBlob(BlobStore blobStore, String blobName)
method validateBlobMetadata (line 190) | private static void validateBlobMetadata(BlobMetadata metadata)
FILE: src/test/java/org/gaul/s3proxy/PrefixBlobStoreTest.java
class PrefixBlobStoreTest (line 46) | public final class PrefixBlobStoreTest {
method setUp (line 53) | @Before
method tearDown (line 68) | @After
method testPutAndGetBlob (line 77) | @Test
method testListTrimsPrefix (line 97) | @Test
method testClearContainerKeepsOtherObjects (line 117) | @Test
method testMultipartUploadUsesPrefix (line 133) | @Test
method testListMultipartUploadsTrimsPrefix (line 150) | @Test
method testParseRejectsEmptyPrefix (line 166) | @Test
FILE: src/test/java/org/gaul/s3proxy/ReadOnlyBlobStoreTest.java
class ReadOnlyBlobStoreTest (line 34) | public final class ReadOnlyBlobStoreTest {
method setUp (line 40) | @Before
method tearDown (line 54) | @After
method testContainerExists (line 62) | @Test
method testPutBlob (line 69) | @Test
method testPutBlobOptions (line 80) | @Test
method createRandomContainerName (line 91) | private static String createRandomContainerName() {
FILE: src/test/java/org/gaul/s3proxy/RegexBlobStoreTest.java
class RegexBlobStoreTest (line 44) | public final class RegexBlobStoreTest {
method setUp (line 49) | @Before
method tearDown (line 63) | @After
method testRemoveSomeCharsFromName (line 71) | @Test
method testParseMatchWithoutReplace (line 107) | @Test
method createRandomContainerName (line 136) | private static String createRandomContainerName() {
FILE: src/test/java/org/gaul/s3proxy/ShardedBlobStoreTest.java
class ShardedBlobStoreTest (line 40) | public final class ShardedBlobStoreTest {
method setUp (line 50) | @Before
method tearDown (line 68) | @After
method createContainer (line 78) | private void createContainer(String container) {
method countShards (line 92) | public int countShards() {
method testCreateContainer (line 103) | @Test
method testDeleteContainer (line 110) | @Test
method testPutBlob (line 119) | @Test
method testDeleteBlob (line 163) | @Test
method testPutBlobUnsharded (line 178) | @Test
method testCopyBlob (line 194) | @Test
method testCopyBlobUnshardedToSharded (line 213) | @Test
method testCopyBlobShardedToUnsharded (line 233) | @Test
FILE: src/test/java/org/gaul/s3proxy/TestUtils.java
class TestUtils (line 44) | final class TestUtils {
method TestUtils (line 48) | private TestUtils() {
method randomByteSource (line 52) | static ByteSource randomByteSource() {
method randomByteSource (line 56) | static ByteSource randomByteSource(long seed) {
class RandomByteSource (line 60) | private static final class RandomByteSource extends ByteSource {
method RandomByteSource (line 63) | RandomByteSource(long seed) {
method openStream (line 67) | @Override
class RandomInputStream (line 73) | private static final class RandomInputStream extends InputStream {
method RandomInputStream (line 77) | RandomInputStream(long seed) {
method read (line 81) | @Override
method read (line 90) | @Override
method read (line 95) | @Override
method close (line 104) | @Override
class S3ProxyLaunchInfo (line 111) | static final class S3ProxyLaunchInfo {
method getS3Proxy (line 121) | S3Proxy getS3Proxy() {
method getProperties (line 125) | Properties getProperties() {
method getS3Identity (line 129) | String getS3Identity() {
method getS3Credential (line 133) | String getS3Credential() {
method getServicePath (line 137) | String getServicePath() {
method getBlobStore (line 141) | BlobStore getBlobStore() {
method getEndpoint (line 145) | URI getEndpoint() {
method getSecureEndpoint (line 149) | URI getSecureEndpoint() {
method startS3Proxy (line 154) | static S3ProxyLaunchInfo startS3Proxy(String configFile) throws Except...
method createRandomContainerName (line 251) | static String createRandomContainerName() {
method createRandomBlobName (line 255) | static String createRandomBlobName() {
FILE: src/test/java/org/gaul/s3proxy/TierBlobStoreTest.java
class TierBlobStoreTest (line 35) | @SuppressWarnings("UnstableApiUsage")
method setUp (line 42) | @Before
method tearDown (line 59) | @After
method testPutNewBlob (line 67) | @Test
method testGetExistingBlob (line 78) | @Test
method testPutNewMpu (line 89) | @Test
method testGetExistingMpu (line 108) | @Test
FILE: src/test/java/org/gaul/s3proxy/UserMetadataReplacerBlobStoreTest.java
class UserMetadataReplacerBlobStoreTest (line 33) | @SuppressWarnings("UnstableApiUsage")
method setUp (line 41) | @Before
method tearDown (line 58) | @After
method testPutNewBlob (line 66) | @Test
method testPutNewMultipartBlob (line 104) | @Test
FILE: src/test/java/org/gaul/s3proxy/junit/S3ProxyExtensionTest.java
class S3ProxyExtensionTest (line 41) | public class S3ProxyExtensionTest {
method setUp (line 53) | @BeforeEach
method listBucket (line 69) | @Test
method uploadFile (line 77) | @Test
method doesBucketExistV2 (line 90) | @Test
method createExtensionWithoutCredentials (line 98) | @Test
FILE: src/test/java/org/gaul/s3proxy/junit/S3ProxyRuleTest.java
class S3ProxyRuleTest (line 42) | public class S3ProxyRuleTest {
method setUp (line 54) | @Before
method listBucket (line 70) | @Test
method uploadFile (line 78) | @Test
method doesBucketExistV2 (line 91) | @Test
method createExtensionWithoutCredentials (line 99) | @Test
Condensed preview — 125 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,081K chars).
[
{
"path": ".dockerignore",
"chars": 147,
"preview": "# Exclude everything from context that is not used by COPY steps in the Dockerfile\n*\n!/target/s3proxy\n!/src/main/resourc"
},
{
"path": ".github/dependabot.yml",
"chars": 597,
"preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
},
{
"path": ".github/workflows/ci-main.yml",
"chars": 9165,
"preview": "name: Main CI\n\non:\n push:\n branches:\n - \"master\"\n tags:\n - \"*\"\n pull_request:\n branches:\n - \"*"
},
{
"path": ".gitignore",
"chars": 424,
"preview": "s3proxy.iml\n.idea/\n\n# Eclipse project configuration files\n.classpath\n.project\n.settings\n\n# MAC stuff\n.DS_Store\n\n# below "
},
{
"path": ".gitmodules",
"chars": 84,
"preview": "[submodule \"s3-tests\"]\n\tpath = s3-tests\n\turl = https://github.com/gaul/s3-tests.git\n"
},
{
"path": ".mailmap",
"chars": 73,
"preview": "Hironao Sekine <phant.acc+github@gmail.com>\nSheng Hu <s.huhot@gmail.com>\n"
},
{
"path": ".mvn/maven.config",
"chars": 56,
"preview": "-Daether.checksums.algorithms=SHA-512,SHA-256,SHA-1,MD5\n"
},
{
"path": ".releaserc",
"chars": 686,
"preview": "{\n \"tagFormat\": 's3proxy-${version}',\n \"branches\": [\n {\n \"name\": 'master',\n "
},
{
"path": "Dockerfile",
"chars": 1635,
"preview": "FROM docker.io/library/eclipse-temurin:21-jre\nLABEL maintainer=\"Andrew Gaul <andrew@gaul.org>\"\n\nWORKDIR /opt/s3proxy\n\nRU"
},
{
"path": "LICENSE",
"chars": 11360,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 8356,
"preview": "# S3Proxy\n\n[](https://github.com/g"
},
{
"path": "docs/Encryption.md",
"chars": 4169,
"preview": "S3Proxy\n\n# Encryption \n\n## Motivation \nThe motivation behind this implementation is to provide a fully transparent and s"
},
{
"path": "docs/Logging.md",
"chars": 275,
"preview": "# Logging\n\n## Configuration\n\nThe following environment variables can be used to configure logging\n\n* LOG_LEVEL default v"
},
{
"path": "pom.xml",
"chars": 21509,
"preview": "<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocat"
},
{
"path": "src/main/assembly/jar-with-dependencies.xml",
"chars": 1268,
"preview": "<assembly xmlns=\"http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0\"\n xmlns:xsi=\"http://www.w3.org/20"
},
{
"path": "src/main/config/logback.xml",
"chars": 935,
"preview": "<configuration>\n <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n <encoder>\n <pattern>[s3p"
},
{
"path": "src/main/java/org/gaul/s3proxy/AccessControlPolicy.java",
"chars": 3747,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/AliasBlobStore.java",
"chars": 10350,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/AuthenticationType.java",
"chars": 963,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/AwsHttpHeaders.java",
"chars": 2515,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/AwsSignature.java",
"chars": 14630,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/BlobStoreLocator.java",
"chars": 855,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/CaseInsensitiveImmutableMultimap.java",
"chars": 1581,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/ChunkedInputStream.java",
"chars": 9487,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/CompleteMultipartUploadRequest.java",
"chars": 1609,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/CreateBucketRequest.java",
"chars": 879,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/CrossOriginResourceSharing.java",
"chars": 7247,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/DeleteMultipleObjectsRequest.java",
"chars": 1293,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/EncryptedBlobStore.java",
"chars": 31529,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/EventualBlobStore.java",
"chars": 7903,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/GlobBlobStoreLocator.java",
"chars": 2718,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/LatencyBlobStore.java",
"chars": 17416,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/Main.java",
"chars": 21114,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/MetricsHandler.java",
"chars": 1395,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/NoCacheBlobStore.java",
"chars": 2572,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/NullBlobStore.java",
"chars": 8785,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/PrefixBlobStore.java",
"chars": 15076,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/PutOptions2.java",
"chars": 2895,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/Quirks.java",
"chars": 4238,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/ReadOnlyBlobStore.java",
"chars": 3836,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/RegexBlobStore.java",
"chars": 8225,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/S3AuthorizationHeader.java",
"chars": 5242,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/S3ErrorCode.java",
"chars": 5861,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/S3Exception.java",
"chars": 1999,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/S3Operation.java",
"chars": 1862,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/S3Proxy.java",
"chars": 23379,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/S3ProxyConstants.java",
"chars": 7438,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/S3ProxyHandler.java",
"chars": 147502,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/S3ProxyHandlerJetty.java",
"chars": 8076,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/S3ProxyMetrics.java",
"chars": 4024,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/ShardedBlobStore.java",
"chars": 24670,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/StorageClassBlobStore.java",
"chars": 4354,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/ThrottledInputStream.java",
"chars": 1640,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/UserMetadataReplacerBlobStore.java",
"chars": 5497,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/awssdk/AwsS3SdkApiMetadata.java",
"chars": 4160,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/awssdk/AwsS3SdkBlobStore.java",
"chars": 50212,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/awssdk/AwsS3SdkBlobStoreContextModule.java",
"chars": 1102,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/awssdk/AwsS3SdkProviderMetadata.java",
"chars": 2128,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/azureblob/AzureBlobApiMetadata.java",
"chars": 3250,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/azureblob/AzureBlobProviderMetadata.java",
"chars": 3299,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/azureblob/AzureBlobStore.java",
"chars": 49345,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/azureblob/AzureBlobStoreContextModule.java",
"chars": 1099,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/crypto/Constants.java",
"chars": 1950,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/crypto/Decryption.java",
"chars": 10681,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/crypto/DecryptionInputStream.java",
"chars": 12633,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/crypto/Encryption.java",
"chars": 1790,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/crypto/EncryptionInputStream.java",
"chars": 3464,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/crypto/PartPadding.java",
"chars": 2635,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/gcloudsdk/GCloudApiMetadata.java",
"chars": 2746,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/gcloudsdk/GCloudBlobStore.java",
"chars": 42521,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/gcloudsdk/GCloudBlobStoreContextModule.java",
"chars": 1101,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/gcloudsdk/GCloudProviderMetadata.java",
"chars": 2600,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/junit/S3ProxyExtension.java",
"chars": 3075,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/junit/S3ProxyJunitCore.java",
"chars": 6266,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/junit/S3ProxyRule.java",
"chars": 2867,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/nio2blob/AbstractNio2BlobStore.java",
"chars": 52538,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/nio2blob/FilesystemNio2BlobApiMetadata.java",
"chars": 2608,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/nio2blob/FilesystemNio2BlobProviderMetadata.java",
"chars": 2362,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/nio2blob/FilesystemNio2BlobStore.java",
"chars": 2003,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/nio2blob/FilesystemNio2BlobStoreContextModule.java",
"chars": 1116,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/nio2blob/TransientNio2BlobApiMetadata.java",
"chars": 2596,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/nio2blob/TransientNio2BlobProviderMetadata.java",
"chars": 2354,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/nio2blob/TransientNio2BlobStore.java",
"chars": 2337,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/java/org/gaul/s3proxy/nio2blob/TransientNio2BlobStoreContextModule.java",
"chars": 1114,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/resources/checkstyle.xml",
"chars": 5536,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE module PUBLIC \"-//Puppy Crawl//DTD Check Configuration 1.3//EN\" \"http:/"
},
{
"path": "src/main/resources/copyright_header.txt",
"chars": 616,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/main/resources/run-docker-container.sh",
"chars": 2693,
"preview": "#!/bin/sh\n\nmkdir \"${JCLOUDS_FILESYSTEM_BASEDIR}\"\n\nexec java \\\n $S3PROXY_JAVA_OPTS \\\n -DLOG_LEVEL=\"${LOG_LEVEL}\" \\\n"
},
{
"path": "src/test/java/org/gaul/s3proxy/AliasBlobStoreTest.java",
"chars": 7535,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/test/java/org/gaul/s3proxy/AwsS3SdkBlobStoreTest.java",
"chars": 2589,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/test/java/org/gaul/s3proxy/AwsSdk2Test.java",
"chars": 3791,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/test/java/org/gaul/s3proxy/AwsSdkAnonymousTest.java",
"chars": 6215,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/test/java/org/gaul/s3proxy/AwsSdkTest.java",
"chars": 78035,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/test/java/org/gaul/s3proxy/CrossOriginResourceSharingAllowAllResponseTest.java",
"chars": 9677,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/test/java/org/gaul/s3proxy/CrossOriginResourceSharingResponseTest.java",
"chars": 14411,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/test/java/org/gaul/s3proxy/CrossOriginResourceSharingRuleTest.java",
"chars": 7343,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/test/java/org/gaul/s3proxy/EncryptedBlobStoreTest.java",
"chars": 36692,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/test/java/org/gaul/s3proxy/EventualBlobStoreTest.java",
"chars": 8777,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/test/java/org/gaul/s3proxy/GlobBlobStoreLocatorTest.java",
"chars": 5817,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/test/java/org/gaul/s3proxy/LatencyBlobStoreTest.java",
"chars": 10548,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/test/java/org/gaul/s3proxy/NoCacheBlobStoreTest.java",
"chars": 2545,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/test/java/org/gaul/s3proxy/NullBlobStoreTest.java",
"chars": 8185,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/test/java/org/gaul/s3proxy/PrefixBlobStoreTest.java",
"chars": 7053,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/test/java/org/gaul/s3proxy/ReadOnlyBlobStoreTest.java",
"chars": 3101,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/test/java/org/gaul/s3proxy/RegexBlobStoreTest.java",
"chars": 5155,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/test/java/org/gaul/s3proxy/ShardedBlobStoreTest.java",
"chars": 10024,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/test/java/org/gaul/s3proxy/TestUtils.java",
"chars": 8870,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/test/java/org/gaul/s3proxy/TierBlobStoreTest.java",
"chars": 4675,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/test/java/org/gaul/s3proxy/UserMetadataReplacerBlobStoreTest.java",
"chars": 5931,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/test/java/org/gaul/s3proxy/junit/S3ProxyExtensionTest.java",
"chars": 3727,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/test/java/org/gaul/s3proxy/junit/S3ProxyRuleTest.java",
"chars": 3789,
"preview": "/*\n * Copyright 2014-2026 Andrew Gaul <andrew@gaul.org>\n *\n * Licensed under the Apache License, Version 2.0 (the \"Licen"
},
{
"path": "src/test/resources/logback.xml",
"chars": 556,
"preview": "<configuration>\n <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">\n <encoder>\n <pattern>[s3p"
},
{
"path": "src/test/resources/run-s3-tests.sh",
"chars": 2473,
"preview": "#!/bin/bash\n\nset -o errexit\nset -o nounset\n\n# Optional first argument selects a config; remaining args pass through to p"
},
{
"path": "src/test/resources/s3-tests.conf",
"chars": 2772,
"preview": "[DEFAULT]\n## this section is just used as default for all the \"s3 *\"\n## sections, you can place these variables also dir"
},
{
"path": "src/test/resources/s3proxy-anonymous.conf",
"chars": 491,
"preview": "s3proxy.endpoint=http://127.0.0.1:0\ns3proxy.secure-endpoint=https://127.0.0.1:0\n#s3proxy.service-path=s3proxy\n# authoriz"
},
{
"path": "src/test/resources/s3proxy-azurite.conf",
"chars": 576,
"preview": "s3proxy.endpoint=http://127.0.0.1:0\ns3proxy.secure-endpoint=https://127.0.0.1:0\n#s3proxy.service-path=s3proxy\n# authoriz"
},
{
"path": "src/test/resources/s3proxy-cors-allow-all.conf",
"chars": 565,
"preview": "s3proxy.endpoint=http://127.0.0.1:0\ns3proxy.secure-endpoint=https://127.0.0.1:0\n# authorization must be aws-v2, aws-v4, "
},
{
"path": "src/test/resources/s3proxy-cors.conf",
"chars": 749,
"preview": "s3proxy.endpoint=http://127.0.0.1:0\ns3proxy.secure-endpoint=https://127.0.0.1:0\n# authorization must be aws-v2, aws-v4, "
},
{
"path": "src/test/resources/s3proxy-encryption.conf",
"chars": 697,
"preview": "s3proxy.endpoint=http://127.0.0.1:0\ns3proxy.secure-endpoint=https://127.0.0.1:0\n#s3proxy.service-path=s3proxy\n# authoriz"
},
{
"path": "src/test/resources/s3proxy-fake-gcs-server.conf",
"chars": 473,
"preview": "s3proxy.endpoint=http://127.0.0.1:0\ns3proxy.secure-endpoint=https://127.0.0.1:0\n#s3proxy.service-path=s3proxy\n# authoriz"
},
{
"path": "src/test/resources/s3proxy-filesystem-nio2.conf",
"chars": 573,
"preview": "s3proxy.endpoint=http://127.0.0.1:0\ns3proxy.secure-endpoint=https://127.0.0.1:0\n#s3proxy.service-path=s3proxy\n# authoriz"
},
{
"path": "src/test/resources/s3proxy-localstack-aws-s3-sdk.conf",
"chars": 688,
"preview": "s3proxy.endpoint=http://127.0.0.1:0\ns3proxy.secure-endpoint=https://127.0.0.1:0\n#s3proxy.service-path=s3proxy\n# authoriz"
},
{
"path": "src/test/resources/s3proxy-localstack-s3.conf",
"chars": 475,
"preview": "s3proxy.endpoint=http://127.0.0.1:0\ns3proxy.secure-endpoint=https://127.0.0.1:0\n#s3proxy.service-path=s3proxy\n# authoriz"
},
{
"path": "src/test/resources/s3proxy-transient-nio2.conf",
"chars": 572,
"preview": "s3proxy.endpoint=http://127.0.0.1:0\ns3proxy.secure-endpoint=https://127.0.0.1:0\n#s3proxy.service-path=s3proxy\n# authoriz"
},
{
"path": "src/test/resources/s3proxy.conf",
"chars": 567,
"preview": "s3proxy.endpoint=http://127.0.0.1:0\ns3proxy.secure-endpoint=https://127.0.0.1:0\n#s3proxy.service-path=s3proxy\n# authoriz"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the andrewgaul/s3proxy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 125 files (1014.3 KB), approximately 218.3k tokens, and a symbol index with 1213 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.