From 9d89da243c0c5a2a7a738d021699f5964b27dc1c Mon Sep 17 00:00:00 2001 From: Jonas Genannt Date: Mon, 22 Mar 2021 16:15:00 +0100 Subject: [PATCH 1/7] provide a information about user/group #164 --- src/site/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/site/index.html b/src/site/index.html index 600f15f..17b9564 100644 --- a/src/site/index.html +++ b/src/site/index.html @@ -59,10 +59,10 @@

S3 ninja

  1. A readily packaged docker image is available at scireum/s3-ninja
  2. -
  3. Run like docker run -p 9444:9000 scireum/s3-ninja
  4. +
  5. Run like docker run -p 9444:9000 scireum/s3-ninja:7
  6. Navigate to http://localhost:9444/ui
  7. Run S3 API-Calls against http://localhost:9444/ (e.g. http://localhost:9444/test-bucket/test-object)
  8. -
  9. Provide an volume for /home/sirius/data to persist data accross restarts.
  10. +
  11. Provide an volume for /home/sirius/data to persist data accross restarts.
    S3Ninja runs as user id 2000 inside the container. If you link a existing directory into your container, change the user/group id to 2000.
API From 4de0ba8d52c6ade0c46bdb8c9b0a8313619debb5 Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Fri, 23 Apr 2021 12:08:11 +0200 Subject: [PATCH 2/7] =?UTF-8?q?Upgrades=20dependencies=20=F0=9F=AA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 17be667..9dcc909 100644 --- a/pom.xml +++ b/pom.xml @@ -16,8 +16,8 @@ http://s3ninja.net - dev-21.0.1 - dev-33.0.1 + dev-21.2 + dev-33.4.2 From 543a4993c28c15ed2b12a8d03b48c6dd0e69445f Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Fri, 23 Apr 2021 12:09:27 +0200 Subject: [PATCH 3/7] =?UTF-8?q?Expands=20shortened=20imports=20?= =?UTF-8?q?=F0=9F=9A=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/ninja/S3Dispatcher.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/java/ninja/S3Dispatcher.java b/src/main/java/ninja/S3Dispatcher.java index 30ccd80..84ba110 100644 --- a/src/main/java/ninja/S3Dispatcher.java +++ b/src/main/java/ninja/S3Dispatcher.java @@ -20,7 +20,11 @@ import ninja.errors.S3ErrorSynthesizer; import ninja.queries.S3QuerySynthesizer; import sirius.kernel.async.CallContext; -import sirius.kernel.commons.*; +import sirius.kernel.commons.Callback; +import sirius.kernel.commons.Hasher; +import sirius.kernel.commons.Strings; +import sirius.kernel.commons.Tuple; +import sirius.kernel.commons.Value; import sirius.kernel.di.GlobalContext; import sirius.kernel.di.std.ConfigValue; import sirius.kernel.di.std.Part; @@ -49,7 +53,17 @@ import java.time.chrono.IsoChronology; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; -import java.util.*; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; import java.util.regex.Matcher; import java.util.stream.Collectors; From 2360ec3a17334928b9634e50328bd77ad7d7a15a Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Fri, 23 Apr 2021 12:10:59 +0200 Subject: [PATCH 4/7] =?UTF-8?q?Fixes=20comparison=20of=20HTTP=20methods=20?= =?UTF-8?q?=F0=9F=A7=91=E2=80=8D=F0=9F=94=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/ninja/S3Dispatcher.java | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/main/java/ninja/S3Dispatcher.java b/src/main/java/ninja/S3Dispatcher.java index 84ba110..44ca715 100644 --- a/src/main/java/ninja/S3Dispatcher.java +++ b/src/main/java/ninja/S3Dispatcher.java @@ -67,7 +67,6 @@ import java.util.regex.Matcher; import java.util.stream.Collectors; -import static io.netty.handler.codec.http.HttpMethod.*; import static ninja.Aws4HashCalculator.AWS_AUTH4_PATTERN; import static ninja.AwsHashCalculator.AWS_AUTH_PATTERN; @@ -212,7 +211,9 @@ public Callback preparePreDispatch(WebContext ctx) { } private InputStreamHandler createInputStreamHandler(WebContext ctx) { - if (aws4HashCalculator.supports(ctx) && ctx.getRequest().method() == PUT && ctx.getHeader("x-amz-decoded-content-length") != null) { + if (aws4HashCalculator.supports(ctx) + && HttpMethod.PUT.equals(ctx.getRequest().method()) + && ctx.getHeader("x-amz-decoded-content-length") != null) { return new SignedChunkHandler(); } else { return new InputStreamHandler(); @@ -366,7 +367,7 @@ private String getAuthHash(WebContext ctx) { * Writes an API error to the log */ private void signalObjectError(WebContext ctx, String bucket, String key, S3ErrorCode errorCode, String message) { - if (ctx.getRequest().method() == HEAD) { + if (HttpMethod.HEAD.equals(ctx.getRequest().method())) { ctx.respondWith().status(errorCode.getHttpStatusCode()); } else { errorSynthesizer.synthesiseError(ctx, bucket, key, errorCode, message); @@ -395,7 +396,7 @@ private void signalObjectSuccess(WebContext ctx) { private void listBuckets(WebContext ctx) { HttpMethod method = ctx.getRequest().method(); - if (GET == method) { + if (HttpMethod.GET.equals(method)) { List buckets = storage.getBuckets(); Response response = ctx.respondWith(); @@ -444,20 +445,20 @@ private void bucket(WebContext ctx, String bucketName) { HttpMethod method = ctx.getRequest().method(); - if (HEAD == method) { + if (HttpMethod.HEAD.equals(method)) { if (bucket.exists()) { signalObjectSuccess(ctx); ctx.respondWith().status(HttpResponseStatus.OK); } else { signalObjectError(ctx, bucketName, null, S3ErrorCode.NoSuchBucket, ERROR_BUCKET_DOES_NOT_EXIST); } - } else if (GET == method) { + } else if (HttpMethod.GET.equals(method)) { if (bucket.exists()) { listObjects(ctx, bucket); } else { signalObjectError(ctx, bucketName, null, S3ErrorCode.NoSuchBucket, ERROR_BUCKET_DOES_NOT_EXIST); } - } else if (DELETE == method) { + } else if (HttpMethod.DELETE.equals(method)) { if (!bucket.exists()) { signalObjectError(ctx, bucketName, null, S3ErrorCode.NoSuchBucket, ERROR_BUCKET_DOES_NOT_EXIST); } else { @@ -465,7 +466,7 @@ private void bucket(WebContext ctx, String bucketName) { signalObjectSuccess(ctx); ctx.respondWith().status(HttpResponseStatus.OK); } - } else if (PUT == method) { + } else if (HttpMethod.PUT.equals(method)) { bucket.create(); signalObjectSuccess(ctx); ctx.respondWith().status(HttpResponseStatus.OK); @@ -492,15 +493,15 @@ private void readObject(WebContext ctx, String bucketName, String key) throws IO } HttpMethod method = ctx.getRequest().method(); - if (HEAD == method) { + if (HttpMethod.HEAD.equals(method)) { getObject(ctx, bucket, key, false); - } else if (GET == method) { + } else if (HttpMethod.GET.equals(method)) { if (Strings.isFilled(uploadId)) { getPartList(ctx, bucket, key, uploadId); } else { getObject(ctx, bucket, key, true); } - } else if (DELETE == method) { + } else if (HttpMethod.DELETE.equals(method)) { if (Strings.isFilled(uploadId)) { abortMultipartUpload(ctx, uploadId); } else { @@ -530,7 +531,7 @@ private void writeObject(WebContext ctx, String bucketName, String key, InputStr } HttpMethod method = ctx.getRequest().method(); - if (PUT == method) { + if (HttpMethod.PUT.equals(method)) { Value copy = ctx.getHeaderValue("x-amz-copy-source"); if (copy.isFilled()) { copyObject(ctx, bucket, key, copy.asString()); @@ -539,7 +540,7 @@ private void writeObject(WebContext ctx, String bucketName, String key, InputStr } else { putObject(ctx, bucket, key, in); } - } else if (POST == method) { + } else if (HttpMethod.POST.equals(method)) { if (ctx.hasParameter("uploads")) { startMultipartUpload(ctx, bucket, key); } else if (Strings.isFilled(uploadId)) { From 8d8ea8e4d299c44c9f6e53284e5d9164644a6af7 Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Fri, 23 Apr 2021 12:13:47 +0200 Subject: [PATCH 5/7] =?UTF-8?q?Formats=20code=20=F0=9F=93=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/ninja/Bucket.java | 9 ++++----- src/main/java/ninja/BucketMigrator.java | 2 +- src/main/java/ninja/S3Dispatcher.java | 20 +++++++++---------- src/main/java/ninja/Storage.java | 6 ++---- src/main/java/ninja/StoredObject.java | 2 +- src/main/java/ninja/errors/S3ErrorCode.java | 15 +++++--------- .../ninja/queries/S3QuerySynthesizer.java | 3 +-- 7 files changed, 24 insertions(+), 33 deletions(-) diff --git a/src/main/java/ninja/Bucket.java b/src/main/java/ninja/Bucket.java index 68975f1..ea80f45 100644 --- a/src/main/java/ninja/Bucket.java +++ b/src/main/java/ninja/Bucket.java @@ -67,7 +67,8 @@ public class Bucket { private final File publicMarker; - private static final Cache publicAccessCache = CacheManager.createLocalCache("public-bucket-access"); + private static final Cache publicAccessCache = + CacheManager.createLocalCache("public-bucket-access"); /** * Creates a new bucket based on the given directory. @@ -195,7 +196,7 @@ public void outputObjects(XMLStructuredOutput output, int limit, @Nullable Strin /** * Very simplified stand-in for {@link Files#walkFileTree(Path, FileVisitor)} where we control the traversal order. * - * @param path the start path. + * @param path the start path. * @param visitor the visitor processing the files. * @throws IOException forwarded from nested I/O operations. */ @@ -205,9 +206,7 @@ private void walkFileTreeOurWay(Path path, FileVisitor visitor) th } try (Stream children = Files.list(path)) { - children.filter(p -> filterObjects(p.toFile())) - .sorted(Bucket::compareUtf8Binary) - .forEach(p -> { + children.filter(p -> filterObjects(p.toFile())).sorted(Bucket::compareUtf8Binary).forEach(p -> { try { BasicFileAttributes attrs = Files.readAttributes(p, BasicFileAttributes.class); visitor.visitFile(p, attrs); diff --git a/src/main/java/ninja/BucketMigrator.java b/src/main/java/ninja/BucketMigrator.java index 15b4a60..93e06d3 100644 --- a/src/main/java/ninja/BucketMigrator.java +++ b/src/main/java/ninja/BucketMigrator.java @@ -79,7 +79,7 @@ private static void migratePublicMarkerVersion1To2(Bucket bucket) { * The legacy file name is considered as-is and URL-encoded for general UTF-8 support. The properties file is * prefixed with $, avoiding name clashes with other object files (where $ would be encoded). * - * @param bucket the bucket to migrate + * @param bucket the bucket to migrate * @param legacyObject the legacy object to migrate */ private static void migrateObjectVersion1To2(Bucket bucket, File legacyObject) { diff --git a/src/main/java/ninja/S3Dispatcher.java b/src/main/java/ninja/S3Dispatcher.java index 44ca715..0db07ee 100644 --- a/src/main/java/ninja/S3Dispatcher.java +++ b/src/main/java/ninja/S3Dispatcher.java @@ -285,7 +285,9 @@ private static S3Request parseRequest(WebContext ctx) { // we treat the first parameter without value as query string Iterator parameterIterator = ctx.getParameterNames().iterator(); String firstParameter = parameterIterator.hasNext() ? parameterIterator.next() : null; - String query = Strings.isFilled(firstParameter) && Strings.isEmpty(ctx.getParameter(firstParameter)) ? firstParameter : null; + String query = Strings.isFilled(firstParameter) && Strings.isEmpty(ctx.getParameter(firstParameter)) ? + firstParameter : + null; // chop off potential port from host Tuple hostAndPort = Strings.split(ctx.getHeader("Host"), ":"); @@ -521,8 +523,7 @@ private void readObject(WebContext ctx, String bucketName, String key) throws IO * @param in the data to process * @throws IOException in case of IO errors and there like */ - private void writeObject(WebContext ctx, String bucketName, String key, InputStreamHandler in) - throws IOException { + private void writeObject(WebContext ctx, String bucketName, String key, InputStreamHandler in) throws IOException { Bucket bucket = storage.getBucket(bucketName); String uploadId = ctx.get("uploadId").asString(); @@ -581,7 +582,9 @@ private boolean objectCheckAuth(WebContext ctx, Bucket bucket, String key) { bucket.getName(), key, S3ErrorCode.BadDigest, - Strings.apply("Invalid Hash (Expected: %s, Found: %s)", expectedHash, hash)); + Strings.apply("Invalid Hash (Expected: %s, Found: %s)", + expectedHash, + hash)); log.log(ctx.getRequest().method().name(), ctx.getRequestedURI(), APILog.Result.REJECTED, @@ -834,7 +837,8 @@ private void startMultipartUpload(WebContext ctx, Bucket bucket, String id) { private void storePropertiesInUploadDir(Map properties, String uploadId) { Properties props = new Properties(); properties.forEach(props::setProperty); - try (FileOutputStream propsOut = new FileOutputStream(new File(getUploadDir(uploadId), TEMPORARY_PROPERTIES_FILENAME))) { + try (FileOutputStream propsOut = new FileOutputStream(new File(getUploadDir(uploadId), + TEMPORARY_PROPERTIES_FILENAME))) { props.store(propsOut, ""); } catch (IOException e) { Exceptions.handle(e); @@ -960,11 +964,7 @@ private void completeMultipartUpload(WebContext ctx, out.endOutput(); } catch (IOException e) { Exceptions.ignore(e); - errorSynthesizer.synthesiseError(ctx, - null, - null, - S3ErrorCode.InternalError, - "Could not build response"); + errorSynthesizer.synthesiseError(ctx, null, null, S3ErrorCode.InternalError, "Could not build response"); } } diff --git a/src/main/java/ninja/Storage.java b/src/main/java/ninja/Storage.java index c8fae3a..f1bbd28 100644 --- a/src/main/java/ninja/Storage.java +++ b/src/main/java/ninja/Storage.java @@ -132,10 +132,8 @@ public Bucket getBucket(String name) { // this check may apply again if (Strings.areEqual(name, "ui")) { throw Exceptions.createHandled() - .withSystemErrorMessage( - "Bucket name \"%s\" is reserved for internal use.", - name) - .handle(); + .withSystemErrorMessage("Bucket name \"%s\" is reserved for internal use.", name) + .handle(); } return new Bucket(new File(getBaseDir(), name)); diff --git a/src/main/java/ninja/StoredObject.java b/src/main/java/ninja/StoredObject.java index 0140065..bc6893f 100644 --- a/src/main/java/ninja/StoredObject.java +++ b/src/main/java/ninja/StoredObject.java @@ -56,7 +56,7 @@ public StoredObject(File file) { * Creates a new object within the given bucket folder and the given key. * * @param folder the bucket's folder - * @param key the object's key + * @param key the object's key */ public StoredObject(File folder, String key) { this(new File(folder, encodeKey(key))); diff --git a/src/main/java/ninja/errors/S3ErrorCode.java b/src/main/java/ninja/errors/S3ErrorCode.java index 8f604bc..1935f0f 100644 --- a/src/main/java/ninja/errors/S3ErrorCode.java +++ b/src/main/java/ninja/errors/S3ErrorCode.java @@ -15,16 +15,11 @@ * along with their respective {@linkplain HttpResponseStatus HTTP response codes}. */ public enum S3ErrorCode { - AccessDenied(HttpResponseStatus.FORBIDDEN), - BadDigest(HttpResponseStatus.BAD_REQUEST), - IncompleteBody(HttpResponseStatus.BAD_REQUEST), - InternalError(HttpResponseStatus.INTERNAL_SERVER_ERROR), - InvalidDigest(HttpResponseStatus.BAD_REQUEST), - InvalidRequest(HttpResponseStatus.BAD_REQUEST), - NoSuchBucket(HttpResponseStatus.NOT_FOUND), - NoSuchBucketPolicy(HttpResponseStatus.NOT_FOUND), - NoSuchKey(HttpResponseStatus.NOT_FOUND), - NoSuchLifecycleConfiguration(HttpResponseStatus.NOT_FOUND), + AccessDenied(HttpResponseStatus.FORBIDDEN), BadDigest(HttpResponseStatus.BAD_REQUEST), + IncompleteBody(HttpResponseStatus.BAD_REQUEST), InternalError(HttpResponseStatus.INTERNAL_SERVER_ERROR), + InvalidDigest(HttpResponseStatus.BAD_REQUEST), InvalidRequest(HttpResponseStatus.BAD_REQUEST), + NoSuchBucket(HttpResponseStatus.NOT_FOUND), NoSuchBucketPolicy(HttpResponseStatus.NOT_FOUND), + NoSuchKey(HttpResponseStatus.NOT_FOUND), NoSuchLifecycleConfiguration(HttpResponseStatus.NOT_FOUND), NoSuchUpload(HttpResponseStatus.NOT_FOUND); private final HttpResponseStatus httpStatusCode; diff --git a/src/main/java/ninja/queries/S3QuerySynthesizer.java b/src/main/java/ninja/queries/S3QuerySynthesizer.java index c67340f..5f5cdd7 100644 --- a/src/main/java/ninja/queries/S3QuerySynthesizer.java +++ b/src/main/java/ninja/queries/S3QuerySynthesizer.java @@ -27,8 +27,7 @@ public interface S3QuerySynthesizer { * @param key the requested object's key, potentially null. * @param query the query string. */ - void processQuery(@Nonnull WebContext ctx, - @Nullable Bucket bucket, + void processQuery(@Nonnull WebContext ctx, @Nullable Bucket bucket, @Nullable String key, @Nonnull String query); } From 84fde8bbd218b57fe44a8cccd0db9562082aca33 Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Fri, 23 Apr 2021 12:21:40 +0200 Subject: [PATCH 6/7] =?UTF-8?q?Renames=20parameters=20`ctx`=20to=20`webCon?= =?UTF-8?q?text`=20=F0=9F=93=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/ninja/Aws4HashCalculator.java | 45 ++- src/main/java/ninja/AwsHashCalculator.java | 14 +- .../java/ninja/AwsLegacyHashCalculator.java | 22 +- src/main/java/ninja/S3Dispatcher.java | 380 +++++++++--------- .../java/ninja/errors/S3ErrorSynthesizer.java | 14 +- .../ninja/queries/BucketAclSynthesizer.java | 4 +- .../ninja/queries/BucketCorsSynthesizer.java | 4 +- .../queries/BucketLifecycleSynthesizer.java | 4 +- .../queries/BucketLocationSynthesizer.java | 4 +- .../queries/BucketPolicySynthesizer.java | 4 +- .../BucketRequestPaymentSynthesizer.java | 4 +- .../ninja/queries/S3QuerySynthesizer.java | 11 +- 12 files changed, 264 insertions(+), 246 deletions(-) diff --git a/src/main/java/ninja/Aws4HashCalculator.java b/src/main/java/ninja/Aws4HashCalculator.java index 70509c8..a7093c0 100644 --- a/src/main/java/ninja/Aws4HashCalculator.java +++ b/src/main/java/ninja/Aws4HashCalculator.java @@ -48,29 +48,29 @@ public class Aws4HashCalculator { /** * Determines if the given request contains an AWS4 auth token. * - * @param ctx the request to check + * @param webContext the request to check * @return true if the request contains an AWS4 auth token, false otherwise. */ - public boolean supports(final WebContext ctx) { - return AWS_AUTH4_PATTERN.matcher(ctx.getHeaderValue("Authorization").asString("")).matches() - || X_AMZ_CREDENTIAL_PATTERN.matcher(ctx.get("X-Amz-Credential").asString("")).matches(); + public boolean supports(final WebContext webContext) { + return AWS_AUTH4_PATTERN.matcher(webContext.getHeaderValue("Authorization").asString("")).matches() + || X_AMZ_CREDENTIAL_PATTERN.matcher(webContext.get("X-Amz-Credential").asString("")).matches(); } /** * Computes the authentication hash as specified by the AWS SDK for verification purposes. * - * @param ctx the current request to fetch parameters from + * @param webContext the current request to fetch parameters from * @param pathPrefix the path prefix to preped to the {@link S3Dispatcher#getEffectiveURI(WebContext) effective URI} * of the request * @return the computes hash value * @throws Exception when hashing fails */ - public String computeHash(WebContext ctx, String pathPrefix) throws Exception { - Matcher matcher = AWS_AUTH4_PATTERN.matcher(ctx.getHeaderValue("Authorization").asString("")); + public String computeHash(WebContext webContext, String pathPrefix) throws Exception { + Matcher matcher = AWS_AUTH4_PATTERN.matcher(webContext.getHeaderValue("Authorization").asString("")); if (!matcher.matches()) { // If the header doesn't match, let's try an URL parameter as we might be processing a presigned URL - matcher = X_AMZ_CREDENTIAL_PATTERN.matcher(ctx.get("X-Amz-Credential").asString("")); + matcher = X_AMZ_CREDENTIAL_PATTERN.matcher(webContext.get("X-Amz-Credential").asString("")); if (!matcher.matches()) { throw new IllegalArgumentException("Unknown AWS4 auth pattern"); } @@ -83,24 +83,27 @@ public String computeHash(WebContext ctx, String pathPrefix) throws Exception { // For header based requests, the signed headers are in the "Credentials" header, for presigned URLs // an extra parameter is given... - String signedHeaders = matcher.groupCount() == 7 ? matcher.group(6) : ctx.get("X-Amz-SignedHeaders").asString(); + String signedHeaders = + matcher.groupCount() == 7 ? matcher.group(6) : webContext.get("X-Amz-SignedHeaders").asString(); byte[] dateKey = hmacSHA256(("AWS4" + storage.getAwsSecretKey()).getBytes(StandardCharsets.UTF_8), date); byte[] dateRegionKey = hmacSHA256(dateKey, region); byte[] dateRegionServiceKey = hmacSHA256(dateRegionKey, service); byte[] signingKey = hmacSHA256(dateRegionServiceKey, serviceType); - byte[] signedData = hmacSHA256(signingKey, buildStringToSign(ctx, signedHeaders, region, service, serviceType)); + byte[] signedData = + hmacSHA256(signingKey, buildStringToSign(webContext, signedHeaders, region, service, serviceType)); return BaseEncoding.base16().lowerCase().encode(signedData); } - private String buildStringToSign(final WebContext ctx, + private String buildStringToSign(final WebContext webContext, String signedHeaders, String region, String service, String serviceType) { - final StringBuilder canonicalRequest = buildCanonicalRequest(ctx, signedHeaders); - final String amazonDateHeader = ctx.getHeaderValue("x-amz-date").asString(ctx.get("X-Amz-Date").asString()); + final StringBuilder canonicalRequest = buildCanonicalRequest(webContext, signedHeaders); + final String amazonDateHeader = + webContext.getHeaderValue("x-amz-date").asString(webContext.get("X-Amz-Date").asString()); return "AWS4-HMAC-SHA256\n" + amazonDateHeader + "\n" @@ -115,30 +118,30 @@ private String buildStringToSign(final WebContext ctx, + hashedCanonicalRequest(canonicalRequest); } - private StringBuilder buildCanonicalRequest(final WebContext ctx, final String signedHeaders) { - StringBuilder canonicalRequest = new StringBuilder(ctx.getRequest().method().name()); + private StringBuilder buildCanonicalRequest(final WebContext webContext, final String signedHeaders) { + StringBuilder canonicalRequest = new StringBuilder(webContext.getRequest().method().name()); canonicalRequest.append("\n"); - canonicalRequest.append(ctx.getRawRequestedURI()); + canonicalRequest.append(webContext.getRawRequestedURI()); canonicalRequest.append("\n"); - appendCanonicalQueryString(ctx, canonicalRequest); + appendCanonicalQueryString(webContext, canonicalRequest); for (String name : signedHeaders.split(";")) { canonicalRequest.append(name.trim()); canonicalRequest.append(":"); - canonicalRequest.append(Strings.join(ctx.getRequest().headers().getAll(name), ",").trim()); + canonicalRequest.append(Strings.join(webContext.getRequest().headers().getAll(name), ",").trim()); canonicalRequest.append("\n"); } canonicalRequest.append("\n"); canonicalRequest.append(signedHeaders); canonicalRequest.append("\n"); - canonicalRequest.append(ctx.getHeaderValue("x-amz-content-sha256").asString("UNSIGNED-PAYLOAD")); + canonicalRequest.append(webContext.getHeaderValue("x-amz-content-sha256").asString("UNSIGNED-PAYLOAD")); return canonicalRequest; } - private void appendCanonicalQueryString(WebContext ctx, StringBuilder canonicalRequest) { - QueryStringDecoder qsd = new QueryStringDecoder(ctx.getRequest().uri(), StandardCharsets.UTF_8); + private void appendCanonicalQueryString(WebContext webContext, StringBuilder canonicalRequest) { + QueryStringDecoder qsd = new QueryStringDecoder(webContext.getRequest().uri(), StandardCharsets.UTF_8); List>> queryString = Tuple.fromMap(qsd.parameters()); queryString.sort(Comparator.comparing(Tuple::getFirst)); diff --git a/src/main/java/ninja/AwsHashCalculator.java b/src/main/java/ninja/AwsHashCalculator.java index d979e6d..015f3ba 100644 --- a/src/main/java/ninja/AwsHashCalculator.java +++ b/src/main/java/ninja/AwsHashCalculator.java @@ -36,23 +36,23 @@ public class AwsHashCalculator { /** * Computes the authentication hash as specified by the AWS SDK for verification purposes. * - * @param ctx the current request to fetch parameters from + * @param webContext the current request to fetch parameters from * @param pathPrefix the path prefix to append to the current uri * @return the computes hash value */ - public String computeHash(WebContext ctx, String pathPrefix) { + public String computeHash(WebContext webContext, String pathPrefix) { try { - return doComputeHash(ctx, pathPrefix); + return doComputeHash(webContext, pathPrefix); } catch (Exception e) { throw Exceptions.handle(UserContext.LOG, e); } } - private String doComputeHash(final WebContext ctx, final String pathPrefix) throws Exception { - if (aws4HashCalculator.supports(ctx)) { - return aws4HashCalculator.computeHash(ctx, pathPrefix); + private String doComputeHash(final WebContext webContext, final String pathPrefix) throws Exception { + if (aws4HashCalculator.supports(webContext)) { + return aws4HashCalculator.computeHash(webContext, pathPrefix); } else { - return legacyHashCalculator.computeHash(ctx, pathPrefix); + return legacyHashCalculator.computeHash(webContext, pathPrefix); } } } diff --git a/src/main/java/ninja/AwsLegacyHashCalculator.java b/src/main/java/ninja/AwsLegacyHashCalculator.java index cea78e2..07980dd 100644 --- a/src/main/java/ninja/AwsLegacyHashCalculator.java +++ b/src/main/java/ninja/AwsLegacyHashCalculator.java @@ -62,27 +62,27 @@ public class AwsLegacyHashCalculator { /** * Computes the authentication hash as specified by the AWS SDK for verification purposes. * - * @param ctx the current request to fetch parameters from + * @param webContext the current request to fetch parameters from * @param pathPrefix the path prefix to preped to the {@link S3Dispatcher#getEffectiveURI(WebContext) effective URI} * of the request * @return the computes hash value * @throws Exception when hashing fails */ - public String computeHash(WebContext ctx, String pathPrefix) throws Exception { - StringBuilder stringToSign = new StringBuilder(ctx.getRequest().method().name()); + public String computeHash(WebContext webContext, String pathPrefix) throws Exception { + StringBuilder stringToSign = new StringBuilder(webContext.getRequest().method().name()); stringToSign.append("\n"); - stringToSign.append(ctx.getHeaderValue("Content-MD5").asString("")); + stringToSign.append(webContext.getHeaderValue("Content-MD5").asString("")); stringToSign.append("\n"); - stringToSign.append(ctx.getHeaderValue("Content-Type").asString("")); + stringToSign.append(webContext.getHeaderValue("Content-Type").asString("")); stringToSign.append("\n"); - String date = ctx.get("Expires").asString(ctx.getHeaderValue("Date").asString("")); - if (ctx.getHeaderValue("x-amz-date").isNull()) { + String date = webContext.get("Expires").asString(webContext.getHeaderValue("Date").asString("")); + if (webContext.getHeaderValue("x-amz-date").isNull()) { stringToSign.append(date); } stringToSign.append("\n"); - HttpHeaders requestHeaders = ctx.getRequest().headers(); + HttpHeaders requestHeaders = webContext.getRequest().headers(); List headers = requestHeaders.names() .stream() .filter(this::relevantAmazonHeader) @@ -95,14 +95,14 @@ public String computeHash(WebContext ctx, String pathPrefix) throws Exception { stringToSign.append("\n"); } - stringToSign.append(pathPrefix).append('/').append(S3Dispatcher.getEffectiveURI(ctx)); + stringToSign.append(pathPrefix).append('/').append(S3Dispatcher.getEffectiveURI(webContext)); char separator = '?'; - for (String parameterName : ctx.getParameterNames().stream().sorted().collect(Collectors.toList())) { + for (String parameterName : webContext.getParameterNames().stream().sorted().collect(Collectors.toList())) { // Skip parameters that aren't part of the canonical signed string if (SIGNED_PARAMETERS.contains(parameterName)) { stringToSign.append(separator).append(parameterName); - String parameterValue = ctx.get(parameterName).asString(); + String parameterValue = webContext.get(parameterName).asString(); if (Strings.isFilled(parameterValue)) { stringToSign.append("=").append(parameterValue); } diff --git a/src/main/java/ninja/S3Dispatcher.java b/src/main/java/ninja/S3Dispatcher.java index 0db07ee..05149dc 100644 --- a/src/main/java/ninja/S3Dispatcher.java +++ b/src/main/java/ninja/S3Dispatcher.java @@ -184,15 +184,15 @@ public int getPriority() { } @Override - public Callback preparePreDispatch(WebContext ctx) { - S3Request request = parseRequest(ctx); + public Callback preparePreDispatch(WebContext webContext) { + S3Request request = parseRequest(webContext); if (request.uri.equals(UI_PATH) || request.uri.startsWith(UI_PATH_PREFIX)) { return null; } if (Strings.isFilled(request.query) && !Strings.areEqual(request.query, "uploads")) { - forwardQueryToSynthesizer(ctx, request); + forwardQueryToSynthesizer(webContext, request); return null; } @@ -205,15 +205,15 @@ public Callback preparePreDispatch(WebContext ctx) { return null; } - InputStreamHandler handler = createInputStreamHandler(ctx); - ctx.setContentHandler(handler); + InputStreamHandler handler = createInputStreamHandler(webContext); + webContext.setContentHandler(handler); return req -> writeObject(req, request.bucket, request.key, handler); } - private InputStreamHandler createInputStreamHandler(WebContext ctx) { - if (aws4HashCalculator.supports(ctx) - && HttpMethod.PUT.equals(ctx.getRequest().method()) - && ctx.getHeader("x-amz-decoded-content-length") != null) { + private InputStreamHandler createInputStreamHandler(WebContext webContext) { + if (aws4HashCalculator.supports(webContext) + && HttpMethod.PUT.equals(webContext.getRequest().method()) + && webContext.getHeader("x-amz-decoded-content-length") != null) { return new SignedChunkHandler(); } else { return new InputStreamHandler(); @@ -221,25 +221,25 @@ private InputStreamHandler createInputStreamHandler(WebContext ctx) { } @Override - public DispatchDecision dispatch(WebContext ctx) throws Exception { - S3Request request = parseRequest(ctx); + public DispatchDecision dispatch(WebContext webContext) throws Exception { + S3Request request = parseRequest(webContext); if (request.uri.equals(UI_PATH) || request.uri.startsWith(UI_PATH_PREFIX)) { return DispatchDecision.CONTINUE; } if (Strings.isFilled(request.query)) { - forwardQueryToSynthesizer(ctx, request); + forwardQueryToSynthesizer(webContext, request); return DispatchDecision.DONE; } if (Strings.isEmpty(request.bucket)) { - listBuckets(ctx); + listBuckets(webContext); return DispatchDecision.DONE; } if (Strings.isEmpty(request.key)) { - bucket(ctx, request.bucket); + bucket(webContext, request.bucket); return DispatchDecision.DONE; } @@ -248,7 +248,7 @@ public DispatchDecision dispatch(WebContext ctx) throws Exception { return DispatchDecision.CONTINUE; } - readObject(ctx, request.bucket, request.key); + readObject(webContext, request.bucket, request.key); return DispatchDecision.DONE; } @@ -258,11 +258,11 @@ public DispatchDecision dispatch(WebContext ctx) throws Exception { * As we have to support legacy URIs which have an /s3 prefix, we cut this here, and * also the first "/" and only return the effective URI to process. * - * @param ctx the current request + * @param webContext the current request * @return the effective URI to process */ - public static String getEffectiveURI(WebContext ctx) { - String uri = ctx.getRequestedURI(); + public static String getEffectiveURI(WebContext webContext) { + String uri = webContext.getRequestedURI(); if (uri.startsWith("/s3")) { uri = uri.substring(3); } @@ -276,21 +276,21 @@ public static String getEffectiveURI(WebContext ctx) { /** * Parses a S3 request from the given HTTP request. * - * @param ctx the HTTP request to parse. + * @param webContext the HTTP request to parse. * @return a structured {@link S3Request}. */ - private static S3Request parseRequest(WebContext ctx) { - String uri = getEffectiveURI(ctx); + private static S3Request parseRequest(WebContext webContext) { + String uri = getEffectiveURI(webContext); // we treat the first parameter without value as query string - Iterator parameterIterator = ctx.getParameterNames().iterator(); + Iterator parameterIterator = webContext.getParameterNames().iterator(); String firstParameter = parameterIterator.hasNext() ? parameterIterator.next() : null; - String query = Strings.isFilled(firstParameter) && Strings.isEmpty(ctx.getParameter(firstParameter)) ? + String query = Strings.isFilled(firstParameter) && Strings.isEmpty(webContext.getParameter(firstParameter)) ? firstParameter : null; // chop off potential port from host - Tuple hostAndPort = Strings.split(ctx.getHeader("Host"), ":"); + Tuple hostAndPort = Strings.split(webContext.getHeader("Host"), ":"); String host = hostAndPort.getFirst(); // check whether the host contains a subdomain by matching against the list of local domains @@ -318,10 +318,10 @@ private static S3Request parseRequest(WebContext ctx) { return request; } - private void forwardQueryToSynthesizer(WebContext ctx, S3Request request) { + private void forwardQueryToSynthesizer(WebContext webContext, S3Request request) { Bucket bucket = storage.getBucket(request.bucket); if (!bucket.exists()) { - errorSynthesizer.synthesiseError(ctx, + errorSynthesizer.synthesiseError(webContext, bucket.getName(), request.key, S3ErrorCode.NoSuchBucket, @@ -331,10 +331,10 @@ private void forwardQueryToSynthesizer(WebContext ctx, S3Request request) { S3QuerySynthesizer synthesizer = globalContext.getPart(request.query, S3QuerySynthesizer.class); if (synthesizer != null) { - synthesizer.processQuery(ctx, bucket, request.key, request.query); + synthesizer.processQuery(webContext, bucket, request.key, request.query); } else { Log.BACKGROUND.WARN("Received unknown query '%s'.", request.query); - errorSynthesizer.synthesiseError(ctx, + errorSynthesizer.synthesiseError(webContext, request.bucket, request.key, S3ErrorCode.InvalidRequest, @@ -345,10 +345,10 @@ private void forwardQueryToSynthesizer(WebContext ctx, S3Request request) { /** * Extracts the given hash from the given request. Returns null if no hash was given. */ - private String getAuthHash(WebContext ctx) { - Value authorizationHeaderValue = ctx.getHeaderValue(HttpHeaderNames.AUTHORIZATION); + private String getAuthHash(WebContext webContext) { + Value authorizationHeaderValue = webContext.getHeaderValue(HttpHeaderNames.AUTHORIZATION); if (!authorizationHeaderValue.isFilled()) { - return ctx.get("Signature").asString(ctx.get("X-Amz-Signature").asString()); + return webContext.get("Signature").asString(webContext.get("X-Amz-Signature").asString()); } String authentication = Strings.isEmpty(authorizationHeaderValue.getString()) ? "" : authorizationHeaderValue.getString(); @@ -368,14 +368,18 @@ private String getAuthHash(WebContext ctx) { /** * Writes an API error to the log */ - private void signalObjectError(WebContext ctx, String bucket, String key, S3ErrorCode errorCode, String message) { - if (HttpMethod.HEAD.equals(ctx.getRequest().method())) { - ctx.respondWith().status(errorCode.getHttpStatusCode()); + private void signalObjectError(WebContext webContext, + String bucket, + String key, + S3ErrorCode errorCode, + String message) { + if (HttpMethod.HEAD.equals(webContext.getRequest().method())) { + webContext.respondWith().status(errorCode.getHttpStatusCode()); } else { - errorSynthesizer.synthesiseError(ctx, bucket, key, errorCode, message); + errorSynthesizer.synthesiseError(webContext, bucket, key, errorCode, message); } - log.log(ctx.getRequest().method().name(), - message + " - " + ctx.getRequestedURI(), + log.log(webContext.getRequest().method().name(), + message + " - " + webContext.getRequestedURI(), APILog.Result.ERROR, CallContext.getCurrent().getWatch()); } @@ -383,9 +387,9 @@ private void signalObjectError(WebContext ctx, String bucket, String key, S3Erro /** * Writes an API success entry to the log */ - private void signalObjectSuccess(WebContext ctx) { - log.log(ctx.getRequest().method().name(), - ctx.getRequestedURI(), + private void signalObjectSuccess(WebContext webContext) { + log.log(webContext.getRequest().method().name(), + webContext.getRequestedURI(), APILog.Result.OK, CallContext.getCurrent().getWatch()); } @@ -393,21 +397,21 @@ private void signalObjectSuccess(WebContext ctx) { /** * GET a list of all buckets * - * @param ctx the context describing the current request + * @param webContext the context describing the current request */ - private void listBuckets(WebContext ctx) { - HttpMethod method = ctx.getRequest().method(); + private void listBuckets(WebContext webContext) { + HttpMethod method = webContext.getRequest().method(); if (HttpMethod.GET.equals(method)) { List buckets = storage.getBuckets(); - Response response = ctx.respondWith(); + Response response = webContext.respondWith(); response.setHeader(HTTP_HEADER_NAME_CONTENT_TYPE, CONTENT_TYPE_XML); XMLStructuredOutput out = response.xml(); out.beginOutput("ListAllMyBucketsResult", Attribute.set("xmlns", "http://s3.amazonaws.com/doc/2006-03-01/")); - out.property("hint", "Goto: " + ctx.getBaseURL() + "/ui to visit the admin UI"); + out.property("hint", "Goto: " + webContext.getBaseURL() + "/ui to visit the admin UI"); outputOwnerInfo(out, "Owner"); out.beginObject("Buckets"); @@ -421,7 +425,7 @@ private void listBuckets(WebContext ctx) { out.endObject(); out.endOutput(); } else { - throw new IllegalArgumentException(ctx.getRequest().method().name()); + throw new IllegalArgumentException(webContext.getRequest().method().name()); } } @@ -435,45 +439,45 @@ private void outputOwnerInfo(XMLStructuredOutput out, String name) { /** * Dispatching method handling bucket specific calls without content (HEAD, DELETE, GET and PUT) * - * @param ctx the context describing the current request + * @param webContext the context describing the current request * @param bucketName name of the bucket of interest */ - private void bucket(WebContext ctx, String bucketName) { + private void bucket(WebContext webContext, String bucketName) { Bucket bucket = storage.getBucket(bucketName); - if (!objectCheckAuth(ctx, bucket, null)) { + if (!objectCheckAuth(webContext, bucket, null)) { return; } - HttpMethod method = ctx.getRequest().method(); + HttpMethod method = webContext.getRequest().method(); if (HttpMethod.HEAD.equals(method)) { if (bucket.exists()) { - signalObjectSuccess(ctx); - ctx.respondWith().status(HttpResponseStatus.OK); + signalObjectSuccess(webContext); + webContext.respondWith().status(HttpResponseStatus.OK); } else { - signalObjectError(ctx, bucketName, null, S3ErrorCode.NoSuchBucket, ERROR_BUCKET_DOES_NOT_EXIST); + signalObjectError(webContext, bucketName, null, S3ErrorCode.NoSuchBucket, ERROR_BUCKET_DOES_NOT_EXIST); } } else if (HttpMethod.GET.equals(method)) { if (bucket.exists()) { - listObjects(ctx, bucket); + listObjects(webContext, bucket); } else { - signalObjectError(ctx, bucketName, null, S3ErrorCode.NoSuchBucket, ERROR_BUCKET_DOES_NOT_EXIST); + signalObjectError(webContext, bucketName, null, S3ErrorCode.NoSuchBucket, ERROR_BUCKET_DOES_NOT_EXIST); } } else if (HttpMethod.DELETE.equals(method)) { if (!bucket.exists()) { - signalObjectError(ctx, bucketName, null, S3ErrorCode.NoSuchBucket, ERROR_BUCKET_DOES_NOT_EXIST); + signalObjectError(webContext, bucketName, null, S3ErrorCode.NoSuchBucket, ERROR_BUCKET_DOES_NOT_EXIST); } else { bucket.delete(); - signalObjectSuccess(ctx); - ctx.respondWith().status(HttpResponseStatus.OK); + signalObjectSuccess(webContext); + webContext.respondWith().status(HttpResponseStatus.OK); } } else if (HttpMethod.PUT.equals(method)) { bucket.create(); - signalObjectSuccess(ctx); - ctx.respondWith().status(HttpResponseStatus.OK); + signalObjectSuccess(webContext); + webContext.respondWith().status(HttpResponseStatus.OK); } else { - throw new IllegalArgumentException(ctx.getRequest().method().name()); + throw new IllegalArgumentException(webContext.getRequest().method().name()); } } @@ -481,83 +485,84 @@ private void bucket(WebContext ctx, String bucketName) { * Dispatching method handling all object specific calls which either read or delete the object but do not provide * any data. * - * @param ctx the context describing the current request + * @param webContext the context describing the current request * @param bucketName the name of the bucket which contains the object (must exist) * @param key the key of the object of interest * @throws IOException in case of IO errors and there like */ - private void readObject(WebContext ctx, String bucketName, String key) throws IOException { + private void readObject(WebContext webContext, String bucketName, String key) throws IOException { Bucket bucket = storage.getBucket(bucketName); - String uploadId = ctx.get("uploadId").asString(); + String uploadId = webContext.get("uploadId").asString(); - if (!checkObjectRequest(ctx, bucket, key)) { + if (!checkObjectRequest(webContext, bucket, key)) { return; } - HttpMethod method = ctx.getRequest().method(); + HttpMethod method = webContext.getRequest().method(); if (HttpMethod.HEAD.equals(method)) { - getObject(ctx, bucket, key, false); + getObject(webContext, bucket, key, false); } else if (HttpMethod.GET.equals(method)) { if (Strings.isFilled(uploadId)) { - getPartList(ctx, bucket, key, uploadId); + getPartList(webContext, bucket, key, uploadId); } else { - getObject(ctx, bucket, key, true); + getObject(webContext, bucket, key, true); } } else if (HttpMethod.DELETE.equals(method)) { if (Strings.isFilled(uploadId)) { - abortMultipartUpload(ctx, uploadId); + abortMultipartUpload(webContext, uploadId); } else { - deleteObject(ctx, bucket, key); + deleteObject(webContext, bucket, key); } } else { - throw new IllegalArgumentException(ctx.getRequest().method().name()); + throw new IllegalArgumentException(webContext.getRequest().method().name()); } } /** * Dispatching method handling all object specific calls which write / provide data. * - * @param ctx the context describing the current request + * @param webContext the context describing the current request * @param bucketName the name of the bucket which contains the object (must exist) * @param key the key of the object of interest * @param in the data to process * @throws IOException in case of IO errors and there like */ - private void writeObject(WebContext ctx, String bucketName, String key, InputStreamHandler in) throws IOException { + private void writeObject(WebContext webContext, String bucketName, String key, InputStreamHandler in) + throws IOException { Bucket bucket = storage.getBucket(bucketName); - String uploadId = ctx.get("uploadId").asString(); + String uploadId = webContext.get("uploadId").asString(); - if (!checkObjectRequest(ctx, bucket, key)) { + if (!checkObjectRequest(webContext, bucket, key)) { return; } - HttpMethod method = ctx.getRequest().method(); + HttpMethod method = webContext.getRequest().method(); if (HttpMethod.PUT.equals(method)) { - Value copy = ctx.getHeaderValue("x-amz-copy-source"); + Value copy = webContext.getHeaderValue("x-amz-copy-source"); if (copy.isFilled()) { - copyObject(ctx, bucket, key, copy.asString()); - } else if (ctx.hasParameter("partNumber") && Strings.isFilled(uploadId)) { - multiObject(ctx, uploadId, ctx.get("partNumber").asString(), in); + copyObject(webContext, bucket, key, copy.asString()); + } else if (webContext.hasParameter("partNumber") && Strings.isFilled(uploadId)) { + multiObject(webContext, uploadId, webContext.get("partNumber").asString(), in); } else { - putObject(ctx, bucket, key, in); + putObject(webContext, bucket, key, in); } } else if (HttpMethod.POST.equals(method)) { - if (ctx.hasParameter("uploads")) { - startMultipartUpload(ctx, bucket, key); + if (webContext.hasParameter("uploads")) { + startMultipartUpload(webContext, bucket, key); } else if (Strings.isFilled(uploadId)) { - completeMultipartUpload(ctx, bucket, key, uploadId, in); + completeMultipartUpload(webContext, bucket, key, uploadId, in); } } else { - throw new IllegalArgumentException(ctx.getRequest().method().name()); + throw new IllegalArgumentException(webContext.getRequest().method().name()); } } - private boolean checkObjectRequest(WebContext ctx, Bucket bucket, String id) { + private boolean checkObjectRequest(WebContext webContext, Bucket bucket, String id) { if (Strings.isEmpty(id)) { - signalObjectError(ctx, bucket.getName(), id, S3ErrorCode.NoSuchKey, "Please provide an object id."); + signalObjectError(webContext, bucket.getName(), id, S3ErrorCode.NoSuchKey, "Please provide an object id."); return false; } - if (!objectCheckAuth(ctx, bucket, id)) { + if (!objectCheckAuth(webContext, bucket, id)) { return false; } @@ -565,41 +570,45 @@ private boolean checkObjectRequest(WebContext ctx, Bucket bucket, String id) { if (storage.isAutocreateBuckets()) { bucket.create(); } else { - signalObjectError(ctx, bucket.getName(), id, S3ErrorCode.NoSuchBucket, ERROR_BUCKET_DOES_NOT_EXIST); + signalObjectError(webContext, + bucket.getName(), + id, + S3ErrorCode.NoSuchBucket, + ERROR_BUCKET_DOES_NOT_EXIST); return false; } } return true; } - private boolean objectCheckAuth(WebContext ctx, Bucket bucket, String key) { - String hash = getAuthHash(ctx); + private boolean objectCheckAuth(WebContext webContext, Bucket bucket, String key) { + String hash = getAuthHash(webContext); if (Strings.isFilled(hash)) { - String expectedHash = hashCalculator.computeHash(ctx, ""); - String alternativeHash = hashCalculator.computeHash(ctx, "/s3"); + String expectedHash = hashCalculator.computeHash(webContext, ""); + String alternativeHash = hashCalculator.computeHash(webContext, "/s3"); if (!expectedHash.equals(hash) && !alternativeHash.equals(hash)) { - errorSynthesizer.synthesiseError(ctx, + errorSynthesizer.synthesiseError(webContext, bucket.getName(), key, S3ErrorCode.BadDigest, Strings.apply("Invalid Hash (Expected: %s, Found: %s)", expectedHash, hash)); - log.log(ctx.getRequest().method().name(), - ctx.getRequestedURI(), + log.log(webContext.getRequest().method().name(), + webContext.getRequestedURI(), APILog.Result.REJECTED, CallContext.getCurrent().getWatch()); return false; } } - if (bucket.isPrivate() && !ctx.get("noAuth").isFilled() && Strings.isEmpty(hash)) { - errorSynthesizer.synthesiseError(ctx, + if (bucket.isPrivate() && !webContext.get("noAuth").isFilled() && Strings.isEmpty(hash)) { + errorSynthesizer.synthesiseError(webContext, bucket.getName(), key, S3ErrorCode.AccessDenied, "Authentication required"); - log.log(ctx.getRequest().method().name(), - ctx.getRequestedURI(), + log.log(webContext.getRequest().method().name(), + webContext.getRequestedURI(), APILog.Result.REJECTED, CallContext.getCurrent().getWatch()); return false; @@ -611,15 +620,15 @@ private boolean objectCheckAuth(WebContext ctx, Bucket bucket, String key) { /** * Handles GET /bucket * - * @param ctx the context describing the current request - * @param bucket the bucket of which the contents should be listed + * @param webContext the context describing the current request + * @param bucket the bucket of which the contents should be listed */ - private void listObjects(WebContext ctx, Bucket bucket) { - int maxKeys = ctx.get("max-keys").asInt(1000); - String marker = ctx.get("marker").asString(); - String prefix = ctx.get("prefix").asString(); + private void listObjects(WebContext webContext, Bucket bucket) { + int maxKeys = webContext.get("max-keys").asInt(1000); + String marker = webContext.get("marker").asString(); + String prefix = webContext.get("prefix").asString(); - Response response = ctx.respondWith(); + Response response = webContext.respondWith(); response.setHeader(HTTP_HEADER_NAME_CONTENT_TYPE, CONTENT_TYPE_XML); bucket.outputObjects(response.xml(), maxKeys, marker, prefix); @@ -628,43 +637,43 @@ private void listObjects(WebContext ctx, Bucket bucket) { /** * Handles DELETE /bucket/id * - * @param ctx the context describing the current request - * @param bucket the bucket containing the object to delete - * @param id name of the object to delete + * @param webContext the context describing the current request + * @param bucket the bucket containing the object to delete + * @param id name of the object to delete */ - private void deleteObject(final WebContext ctx, final Bucket bucket, final String id) { + private void deleteObject(final WebContext webContext, final Bucket bucket, final String id) { StoredObject object = bucket.getObject(id); object.delete(); - ctx.respondWith().status(HttpResponseStatus.OK); - signalObjectSuccess(ctx); + webContext.respondWith().status(HttpResponseStatus.OK); + signalObjectSuccess(webContext); } /** * Handles PUT /bucket/id * - * @param ctx the context describing the current request - * @param bucket the bucket containing the object to upload - * @param id name of the object to upload + * @param webContext the context describing the current request + * @param bucket the bucket containing the object to upload + * @param id name of the object to upload */ - private void putObject(WebContext ctx, Bucket bucket, String id, InputStreamHandler inputStream) + private void putObject(WebContext webContext, Bucket bucket, String id, InputStreamHandler inputStream) throws IOException { StoredObject object = bucket.getObject(id); if (inputStream == null) { - signalObjectError(ctx, bucket.getName(), id, S3ErrorCode.IncompleteBody, "No content posted"); + signalObjectError(webContext, bucket.getName(), id, S3ErrorCode.IncompleteBody, "No content posted"); return; } try (FileOutputStream out = new FileOutputStream(object.getFile())) { ByteStreams.copy(inputStream, out); } - Map properties = parseUploadProperties(ctx); + Map properties = parseUploadProperties(webContext); byte[] hash = Hasher.md5().hashFile(object.getFile()).toHash(); String md5 = BaseEncoding.base64().encode(hash); String contentMd5 = properties.get("Content-MD5"); if (properties.containsKey("Content-MD5") && !md5.equals(contentMd5)) { object.delete(); - signalObjectError(ctx, + signalObjectError(webContext, bucket.getName(), id, S3ErrorCode.BadDigest, @@ -675,19 +684,19 @@ private void putObject(WebContext ctx, Bucket bucket, String id, InputStreamHand properties.put(HTTP_HEADER_NAME_ETAG, etag); object.setProperties(properties); - Response response = ctx.respondWith(); + Response response = webContext.respondWith(); response.addHeader(HTTP_HEADER_NAME_ETAG, etag(etag)).status(HttpResponseStatus.OK); response.addHeader(HttpHeaderNames.ACCESS_CONTROL_EXPOSE_HEADERS, HTTP_HEADER_NAME_ETAG); - signalObjectSuccess(ctx); + signalObjectSuccess(webContext); } - private Map parseUploadProperties(WebContext ctx) { + private Map parseUploadProperties(WebContext webContext) { Map properties = Maps.newTreeMap(); - for (String name : ctx.getRequest().headers().names()) { + for (String name : webContext.getRequest().headers().names()) { String nameLower = name.toLowerCase(); if (nameLower.startsWith("x-amz-meta-") || "content-md5".equals(nameLower) || "content-type".equals( nameLower) || "x-amz-acl".equals(nameLower)) { - properties.put(name, ctx.getHeader(name)); + properties.put(name, webContext.getHeader(name)); } } return properties; @@ -700,14 +709,14 @@ private String etag(String etag) { /** * Handles GET /bucket/id with an x-amz-copy-source header. * - * @param ctx the context describing the current request - * @param bucket the bucket containing the object to use as destination - * @param id name of the object to use as destination + * @param webContext the context describing the current request + * @param bucket the bucket containing the object to use as destination + * @param id name of the object to use as destination */ - private void copyObject(WebContext ctx, Bucket bucket, String id, String copy) throws IOException { + private void copyObject(WebContext webContext, Bucket bucket, String id, String copy) throws IOException { StoredObject object = bucket.getObject(id); if (!copy.contains(PATH_DELIMITER)) { - signalObjectError(ctx, + signalObjectError(webContext, null, null, S3ErrorCode.InvalidRequest, @@ -718,7 +727,7 @@ private void copyObject(WebContext ctx, Bucket bucket, String id, String copy) t String srcId = copy.substring(copy.lastIndexOf(PATH_DELIMITER) + 1); Bucket srcBucket = storage.getBucket(srcBucketName); if (!srcBucket.exists()) { - signalObjectError(ctx, + signalObjectError(webContext, srcBucketName, srcId, S3ErrorCode.NoSuchBucket, @@ -727,7 +736,7 @@ private void copyObject(WebContext ctx, Bucket bucket, String id, String copy) t } StoredObject src = srcBucket.getObject(srcId); if (!src.exists()) { - signalObjectError(ctx, + signalObjectError(webContext, srcBucketName, srcId, S3ErrorCode.NoSuchKey, @@ -740,7 +749,8 @@ private void copyObject(WebContext ctx, Bucket bucket, String id, String copy) t } String etag = BaseEncoding.base16().encode(Hasher.md5().hashFile(object.getFile()).toHash()).toLowerCase(); - XMLStructuredOutput structuredOutput = ctx.respondWith().addHeader(HTTP_HEADER_NAME_ETAG, etag(etag)).xml(); + XMLStructuredOutput structuredOutput = + webContext.respondWith().addHeader(HTTP_HEADER_NAME_ETAG, etag(etag)).xml(); structuredOutput.beginOutput("CopyObjectResult"); structuredOutput.beginObject("LastModified"); structuredOutput.text(ISO8601_INSTANT.format(object.getLastModifiedInstant())); @@ -749,29 +759,29 @@ private void copyObject(WebContext ctx, Bucket bucket, String id, String copy) t structuredOutput.text(etag(etag)); structuredOutput.endObject(); structuredOutput.endOutput(); - signalObjectSuccess(ctx); + signalObjectSuccess(webContext); } /** * Handles GET /bucket/id * - * @param ctx the context describing the current request - * @param bucket the bucket containing the object to download - * @param id name of the object to use as download + * @param webContext the context describing the current request + * @param bucket the bucket containing the object to download + * @param id name of the object to use as download */ - private void getObject(WebContext ctx, Bucket bucket, String id, boolean sendFile) throws IOException { + private void getObject(WebContext webContext, Bucket bucket, String id, boolean sendFile) throws IOException { StoredObject object = bucket.getObject(id); if (!object.exists()) { - signalObjectError(ctx, bucket.getName(), id, S3ErrorCode.NoSuchKey, "Object does not exist"); + signalObjectError(webContext, bucket.getName(), id, S3ErrorCode.NoSuchKey, "Object does not exist"); return; } - Response response = ctx.respondWith(); + Response response = webContext.respondWith(); Map properties = object.getProperties(); for (Map.Entry entry : properties.entrySet()) { response.addHeader(entry.getKey(), entry.getValue()); } - for (Map.Entry entry : getOverridenHeaders(ctx).entrySet()) { + for (Map.Entry entry : getOverridenHeaders(webContext).entrySet()) { response.setHeader(entry.getKey(), entry.getValue()); } @@ -795,26 +805,26 @@ private void getObject(WebContext ctx, Bucket bucket, String id, boolean sendFil response.addHeader(HttpHeaderNames.CONTENT_LENGTH, object.getFile().length()); response.status(HttpResponseStatus.OK); } - signalObjectSuccess(ctx); + signalObjectSuccess(webContext); } /** * Handles POST /bucket/id?uploads * - * @param ctx the context describing the current request - * @param bucket the bucket containing the object to upload - * @param id name of the object to upload + * @param webContext the context describing the current request + * @param bucket the bucket containing the object to upload + * @param id name of the object to upload */ - private void startMultipartUpload(WebContext ctx, Bucket bucket, String id) { - Response response = ctx.respondWith(); + private void startMultipartUpload(WebContext webContext, Bucket bucket, String id) { + Response response = webContext.respondWith(); Map properties = Maps.newTreeMap(); - for (String name : ctx.getRequest().headers().names()) { + for (String name : webContext.getRequest().headers().names()) { String nameLower = name.toLowerCase(); if (nameLower.startsWith("x-amz-meta-") || "content-md5".equals(nameLower) || "content-type".equals( nameLower) || "x-amz-acl".equals(nameLower)) { - properties.put(name, ctx.getHeader(name)); - response.addHeader(name, ctx.getHeader(name)); + properties.put(name, webContext.getHeader(name)); + response.addHeader(name, webContext.getHeader(name)); } } response.setHeader(HTTP_HEADER_NAME_CONTENT_TYPE, CONTENT_TYPE_XML); @@ -848,14 +858,14 @@ private void storePropertiesInUploadDir(Map properties, String u /** * Handles PUT /bucket/id?uploadId=X&partNumber=Y * - * @param ctx the context describing the current request + * @param webContext the context describing the current request * @param uploadId the multipart upload this part belongs to * @param partNumber the number of this part in the complete upload * @param part input stream with the content of this part */ - private void multiObject(WebContext ctx, String uploadId, String partNumber, InputStreamHandler part) { + private void multiObject(WebContext webContext, String uploadId, String partNumber, InputStreamHandler part) { if (!multipartUploads.contains(uploadId)) { - errorSynthesizer.synthesiseError(ctx, + errorSynthesizer.synthesiseError(webContext, null, null, S3ErrorCode.NoSuchUpload, @@ -874,12 +884,12 @@ private void multiObject(WebContext ctx, String uploadId, String partNumber, Inp part.close(); String etag = BaseEncoding.base16().encode(Hasher.md5().hashFile(partFile).toHash()).toLowerCase(); - ctx.respondWith() - .setHeader(HTTP_HEADER_NAME_ETAG, etag) - .addHeader(HttpHeaderNames.ACCESS_CONTROL_EXPOSE_HEADERS, HTTP_HEADER_NAME_ETAG) - .status(HttpResponseStatus.OK); + webContext.respondWith() + .setHeader(HTTP_HEADER_NAME_ETAG, etag) + .addHeader(HttpHeaderNames.ACCESS_CONTROL_EXPOSE_HEADERS, HTTP_HEADER_NAME_ETAG) + .status(HttpResponseStatus.OK); } catch (IOException e) { - errorSynthesizer.synthesiseError(ctx, + errorSynthesizer.synthesiseError(webContext, null, null, S3ErrorCode.InternalError, @@ -890,19 +900,19 @@ private void multiObject(WebContext ctx, String uploadId, String partNumber, Inp /** * Handles POST /bucket/id?uploadId=X * - * @param ctx the context describing the current request - * @param bucket the bucket containing the object to upload - * @param id name of the object to upload - * @param uploadId the multipart upload that should be completed - * @param in input stream with xml listing uploaded parts + * @param webContext the context describing the current request + * @param bucket the bucket containing the object to upload + * @param id name of the object to upload + * @param uploadId the multipart upload that should be completed + * @param in input stream with xml listing uploaded parts */ - private void completeMultipartUpload(WebContext ctx, + private void completeMultipartUpload(WebContext webContext, Bucket bucket, String id, final String uploadId, InputStreamHandler in) { if (!multipartUploads.remove(uploadId)) { - errorSynthesizer.synthesiseError(ctx, + errorSynthesizer.synthesiseError(webContext, null, null, S3ErrorCode.NoSuchUpload, @@ -933,7 +943,7 @@ private void completeMultipartUpload(WebContext ctx, file.deleteOnExit(); if (!file.exists()) { - errorSynthesizer.synthesiseError(ctx, + errorSynthesizer.synthesiseError(webContext, null, null, S3ErrorCode.NoSuchUpload, @@ -955,7 +965,7 @@ private void completeMultipartUpload(WebContext ctx, properties.put(HTTP_HEADER_NAME_ETAG, etag); object.setProperties(properties); - XMLStructuredOutput out = ctx.respondWith().xml(); + XMLStructuredOutput out = webContext.respondWith().xml(); out.beginOutput("CompleteMultipartUploadResult"); out.property("Location", ""); out.property(RESPONSE_BUCKET, bucket.getName()); @@ -964,7 +974,11 @@ private void completeMultipartUpload(WebContext ctx, out.endOutput(); } catch (IOException e) { Exceptions.ignore(e); - errorSynthesizer.synthesiseError(ctx, null, null, S3ErrorCode.InternalError, "Could not build response"); + errorSynthesizer.synthesiseError(webContext, + null, + null, + S3ErrorCode.InternalError, + "Could not build response"); } } @@ -1010,12 +1024,12 @@ private void combine(List parts, FileChannel out) throws IOException { /** * Handles DELETE /bucket/id?uploadId=X * - * @param ctx the context describing the current request - * @param uploadId the multipart upload that should be cancelled + * @param webContext the context describing the current request + * @param uploadId the multipart upload that should be cancelled */ - private void abortMultipartUpload(WebContext ctx, String uploadId) { + private void abortMultipartUpload(WebContext webContext, String uploadId) { multipartUploads.remove(uploadId); - ctx.respondWith().status(HttpResponseStatus.OK); + webContext.respondWith().status(HttpResponseStatus.OK); delete(getUploadDir(uploadId)); } @@ -1030,13 +1044,13 @@ private static void delete(File file) { /** * Handles GET /bucket/id?uploadId=uploadId * - * @param ctx the context describing the current request - * @param bucket the bucket containing the object to download - * @param id name of the object to use as download + * @param webContext the context describing the current request + * @param bucket the bucket containing the object to download + * @param id name of the object to use as download */ - private void getPartList(WebContext ctx, Bucket bucket, String id, String uploadId) { + private void getPartList(WebContext webContext, Bucket bucket, String id, String uploadId) { if (!multipartUploads.contains(uploadId)) { - errorSynthesizer.synthesiseError(ctx, + errorSynthesizer.synthesiseError(webContext, null, null, S3ErrorCode.NoSuchUpload, @@ -1044,7 +1058,7 @@ private void getPartList(WebContext ctx, Bucket bucket, String id, String upload return; } - Response response = ctx.respondWith(); + Response response = webContext.respondWith(); response.setHeader(HTTP_HEADER_NAME_CONTENT_TYPE, CONTENT_TYPE_XML); @@ -1058,8 +1072,8 @@ private void getPartList(WebContext ctx, Bucket bucket, String id, String upload outputOwnerInfo(out, "Owner"); File uploadDir = getUploadDir(uploadId); - int marker = ctx.get("part-number-marker").asInt(0); - int maxParts = ctx.get("max-parts").asInt(0); + int marker = webContext.get("part-number-marker").asInt(0); + int maxParts = webContext.get("max-parts").asInt(0); FileFilter filter = file -> !Strings.areEqual(file.getName(), TEMPORARY_PROPERTIES_FILENAME); File[] parts = Objects.requireNonNull(uploadDir.listFiles(filter)); @@ -1089,11 +1103,11 @@ private void getPartList(WebContext ctx, Bucket bucket, String id, String upload out.endOutput(); } - private Map getOverridenHeaders(WebContext ctx) { + private Map getOverridenHeaders(WebContext webContext) { Map overrides = Maps.newTreeMap(); for (Map.Entry entry : headerOverrides.entrySet()) { String header = entry.getValue(); - String value = ctx.getParameter(entry.getKey()); + String value = webContext.getParameter(entry.getKey()); if (value != null) { overrides.put(header, value); } diff --git a/src/main/java/ninja/errors/S3ErrorSynthesizer.java b/src/main/java/ninja/errors/S3ErrorSynthesizer.java index de4c2dd..6a15728 100644 --- a/src/main/java/ninja/errors/S3ErrorSynthesizer.java +++ b/src/main/java/ninja/errors/S3ErrorSynthesizer.java @@ -26,19 +26,19 @@ public class S3ErrorSynthesizer { /** * Synthesizes an error response. * - * @param ctx the request to process - * @param bucket the requested bucket, potentially null - * @param key the requested object's key, potentially null - * @param code the error code to send - * @param message a human-readable description of the error + * @param webContext the request to process + * @param bucket the requested bucket, potentially null + * @param key the requested object's key, potentially null + * @param code the error code to send + * @param message a human-readable description of the error */ - public void synthesiseError(@Nonnull WebContext ctx, + public void synthesiseError(@Nonnull WebContext webContext, @Nullable String bucket, @Nullable String key, @Nonnull S3ErrorCode code, @Nullable String message) { XMLStructuredOutput xml = - new XMLStructuredOutput(ctx.respondWith().outputStream(code.getHttpStatusCode(), "text/xml")); + new XMLStructuredOutput(webContext.respondWith().outputStream(code.getHttpStatusCode(), "text/xml")); String resource = null; if (Strings.isFilled(bucket)) { diff --git a/src/main/java/ninja/queries/BucketAclSynthesizer.java b/src/main/java/ninja/queries/BucketAclSynthesizer.java index 249d262..19b9557 100644 --- a/src/main/java/ninja/queries/BucketAclSynthesizer.java +++ b/src/main/java/ninja/queries/BucketAclSynthesizer.java @@ -23,11 +23,11 @@ public class BucketAclSynthesizer implements S3QuerySynthesizer { @Override - public void processQuery(@Nonnull WebContext ctx, + public void processQuery(@Nonnull WebContext webContext, @Nullable Bucket bucket, @Nullable String key, @Nonnull String query) { - XMLStructuredOutput xml = ctx.respondWith().xml(); + XMLStructuredOutput xml = webContext.respondWith().xml(); xml.beginOutput("GetBucketAclOutput"); xml.endOutput(); } diff --git a/src/main/java/ninja/queries/BucketCorsSynthesizer.java b/src/main/java/ninja/queries/BucketCorsSynthesizer.java index 3700300..124b047 100644 --- a/src/main/java/ninja/queries/BucketCorsSynthesizer.java +++ b/src/main/java/ninja/queries/BucketCorsSynthesizer.java @@ -23,11 +23,11 @@ public class BucketCorsSynthesizer implements S3QuerySynthesizer { @Override - public void processQuery(@Nonnull WebContext ctx, + public void processQuery(@Nonnull WebContext webContext, @Nullable Bucket bucket, @Nullable String key, @Nonnull String query) { - XMLStructuredOutput xml = ctx.respondWith().xml(); + XMLStructuredOutput xml = webContext.respondWith().xml(); xml.beginOutput("GetBucketCorsOutput"); xml.endOutput(); } diff --git a/src/main/java/ninja/queries/BucketLifecycleSynthesizer.java b/src/main/java/ninja/queries/BucketLifecycleSynthesizer.java index 7f8b2f6..3041016 100644 --- a/src/main/java/ninja/queries/BucketLifecycleSynthesizer.java +++ b/src/main/java/ninja/queries/BucketLifecycleSynthesizer.java @@ -29,11 +29,11 @@ public class BucketLifecycleSynthesizer implements S3QuerySynthesizer { private S3ErrorSynthesizer errorSynthesizer; @Override - public void processQuery(@Nonnull WebContext ctx, + public void processQuery(@Nonnull WebContext webContext, @Nullable Bucket bucket, @Nullable String key, @Nonnull String query) { - errorSynthesizer.synthesiseError(ctx, + errorSynthesizer.synthesiseError(webContext, bucket.getName(), key, S3ErrorCode.NoSuchLifecycleConfiguration, diff --git a/src/main/java/ninja/queries/BucketLocationSynthesizer.java b/src/main/java/ninja/queries/BucketLocationSynthesizer.java index 6269f93..193e3d5 100644 --- a/src/main/java/ninja/queries/BucketLocationSynthesizer.java +++ b/src/main/java/ninja/queries/BucketLocationSynthesizer.java @@ -24,11 +24,11 @@ public class BucketLocationSynthesizer implements S3QuerySynthesizer { @Override - public void processQuery(@Nonnull WebContext ctx, + public void processQuery(@Nonnull WebContext webContext, @Nullable Bucket bucket, @Nullable String key, @Nonnull String query) { - XMLStructuredOutput xml = ctx.respondWith().xml(); + XMLStructuredOutput xml = webContext.respondWith().xml(); xml.beginOutput("GetBucketLocationOutput"); xml.property("LocationConstraint", "EU"); xml.endOutput(); diff --git a/src/main/java/ninja/queries/BucketPolicySynthesizer.java b/src/main/java/ninja/queries/BucketPolicySynthesizer.java index 4b102b4..d35cdc3 100644 --- a/src/main/java/ninja/queries/BucketPolicySynthesizer.java +++ b/src/main/java/ninja/queries/BucketPolicySynthesizer.java @@ -29,11 +29,11 @@ public class BucketPolicySynthesizer implements S3QuerySynthesizer { private S3ErrorSynthesizer errorSynthesizer; @Override - public void processQuery(@Nonnull WebContext ctx, + public void processQuery(@Nonnull WebContext webContext, @Nullable Bucket bucket, @Nullable String key, @Nonnull String query) { - errorSynthesizer.synthesiseError(ctx, + errorSynthesizer.synthesiseError(webContext, bucket.getName(), key, S3ErrorCode.NoSuchBucketPolicy, diff --git a/src/main/java/ninja/queries/BucketRequestPaymentSynthesizer.java b/src/main/java/ninja/queries/BucketRequestPaymentSynthesizer.java index 5d7632b..27b6943 100644 --- a/src/main/java/ninja/queries/BucketRequestPaymentSynthesizer.java +++ b/src/main/java/ninja/queries/BucketRequestPaymentSynthesizer.java @@ -24,11 +24,11 @@ public class BucketRequestPaymentSynthesizer implements S3QuerySynthesizer { @Override - public void processQuery(@Nonnull WebContext ctx, + public void processQuery(@Nonnull WebContext webContext, @Nullable Bucket bucket, @Nullable String key, @Nonnull String query) { - XMLStructuredOutput xml = ctx.respondWith().xml(); + XMLStructuredOutput xml = webContext.respondWith().xml(); xml.beginOutput("GetBucketRequestPaymentOutput"); xml.property("Payer", "BucketOwner"); xml.endOutput(); diff --git a/src/main/java/ninja/queries/S3QuerySynthesizer.java b/src/main/java/ninja/queries/S3QuerySynthesizer.java index 5f5cdd7..caff475 100644 --- a/src/main/java/ninja/queries/S3QuerySynthesizer.java +++ b/src/main/java/ninja/queries/S3QuerySynthesizer.java @@ -22,12 +22,13 @@ public interface S3QuerySynthesizer { /** * Synthesizes a response for the given query. * - * @param ctx the request to process. - * @param bucket the requested bucket, potentially null. - * @param key the requested object's key, potentially null. - * @param query the query string. + * @param webContext the request to process. + * @param bucket the requested bucket, potentially null. + * @param key the requested object's key, potentially null. + * @param query the query string. */ - void processQuery(@Nonnull WebContext ctx, @Nullable Bucket bucket, + void processQuery(@Nonnull WebContext webContext, + @Nullable Bucket bucket, @Nullable String key, @Nonnull String query); } From 156752bbe8d53846068b3b653693274f4bf15eff Mon Sep 17 00:00:00 2001 From: Jakob Vogel Date: Fri, 23 Apr 2021 13:25:10 +0200 Subject: [PATCH 7/7] =?UTF-8?q?Back-tracks=20to=20GA=20releases=20?= =?UTF-8?q?=F0=9F=9B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Suggested by @andyHa during code review. --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 9dcc909..1b369b3 100644 --- a/pom.xml +++ b/pom.xml @@ -16,8 +16,8 @@ http://s3ninja.net - dev-21.2 - dev-33.4.2 + ga-1.0.1 + ga-1.1.0