diff --git a/.drone.yml b/.drone.yml index 3f21fd6..2a5da85 100644 --- a/.drone.yml +++ b/.drone.yml @@ -59,6 +59,7 @@ steps: settings: repo: scireum/s3-ninja tags: + - latest - "${DRONE_TAG}" environment: DOCKER_PASSWORD: diff --git a/pom.xml b/pom.xml index f0087da..3e0b043 100644 --- a/pom.xml +++ b/pom.xml @@ -16,8 +16,8 @@ http://s3ninja.net - dev-23.0.0 - dev-35.0.0 + dev-23.5.0 + dev-39.8.0 diff --git a/src/main/java/ninja/Aws4HashCalculator.java b/src/main/java/ninja/Aws4HashCalculator.java index a7093c0..f6f999a 100644 --- a/src/main/java/ninja/Aws4HashCalculator.java +++ b/src/main/java/ninja/Aws4HashCalculator.java @@ -60,7 +60,7 @@ public boolean supports(final WebContext webContext) { * Computes the authentication hash as specified by the AWS SDK for verification purposes. * * @param webContext the current request to fetch parameters from - * @param pathPrefix the path prefix to preped to the {@link S3Dispatcher#getEffectiveURI(WebContext) effective URI} + * @param pathPrefix the path prefix to preped to the {@link S3Dispatcher#getEffectiveURI(String) effective URI} * of the request * @return the computes hash value * @throws Exception when hashing fails diff --git a/src/main/java/ninja/AwsLegacyHashCalculator.java b/src/main/java/ninja/AwsLegacyHashCalculator.java index 07980dd..d5feaf5 100644 --- a/src/main/java/ninja/AwsLegacyHashCalculator.java +++ b/src/main/java/ninja/AwsLegacyHashCalculator.java @@ -63,7 +63,7 @@ public class AwsLegacyHashCalculator { * Computes the authentication hash as specified by the AWS SDK for verification purposes. * * @param webContext the current request to fetch parameters from - * @param pathPrefix the path prefix to preped to the {@link S3Dispatcher#getEffectiveURI(WebContext) effective URI} + * @param pathPrefix the path prefix to preped to the {@link S3Dispatcher#getEffectiveURI(String) effective URI} * of the request * @return the computes hash value * @throws Exception when hashing fails @@ -95,7 +95,7 @@ public String computeHash(WebContext webContext, String pathPrefix) throws Excep stringToSign.append("\n"); } - stringToSign.append(pathPrefix).append('/').append(S3Dispatcher.getEffectiveURI(webContext)); + stringToSign.append(pathPrefix).append('/').append(S3Dispatcher.getEffectiveURI(webContext.getRawRequestedURI())); char separator = '?'; for (String parameterName : webContext.getParameterNames().stream().sorted().collect(Collectors.toList())) { diff --git a/src/main/java/ninja/ListFileTreeVisitor.java b/src/main/java/ninja/ListFileTreeVisitor.java index 05e1711..6576de5 100644 --- a/src/main/java/ninja/ListFileTreeVisitor.java +++ b/src/main/java/ninja/ListFileTreeVisitor.java @@ -55,9 +55,9 @@ protected ListFileTreeVisitor(XMLStructuredOutput output, @Override public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException { File file = path.toFile(); - String name = file.getName(); + String name = StoredObject.decodeKey(file.getName()); - if (!file.isFile() || name.startsWith("$")) { + if (!file.isFile() || file.getName().startsWith("$")) { return FileVisitResult.CONTINUE; } if (!markerReached) { diff --git a/src/main/java/ninja/NinjaController.java b/src/main/java/ninja/NinjaController.java index 1b4368c..480e941 100644 --- a/src/main/java/ninja/NinjaController.java +++ b/src/main/java/ninja/NinjaController.java @@ -118,7 +118,7 @@ private void buckets(WebContext webContext) { try { buckets = storage.getBuckets(); } catch (HandledException e) { - UserContext.message(Message.error(e.getMessage())); + UserContext.message(Message.error().withTextMessage(e.getMessage())); } webContext.respondWith() .template("/templates/index.html.pasta", @@ -151,21 +151,21 @@ public void bucket(WebContext webContext, String bucketName) { // handle /ui/[bucket]?create if (webContext.hasParameter("create")) { if (bucket.exists()) { - UserContext.message(Message.error("Bucket does already exist.")); + UserContext.message(Message.error().withTextMessage("Bucket does already exist.")); webContext.respondWith().redirectTemporarily("/ui/" + bucket.getEncodedName()); return; } bucket.create(); - UserContext.message(Message.info("Bucket successfully created.")); + UserContext.message(Message.info().withTextMessage("Bucket successfully created.")); webContext.respondWith().redirectTemporarily("/ui/" + bucket.getEncodedName()); return; } // from this point on, make sure that the bucket exists if (!bucket.exists()) { - UserContext.message(Message.error("Bucket does not exist.")); + UserContext.message(Message.error().withTextMessage("Bucket does not exist.")); webContext.respondWith().redirectTemporarily("/ui"); return; } @@ -174,7 +174,7 @@ public void bucket(WebContext webContext, String bucketName) { if (webContext.hasParameter("make-public")) { bucket.makePublic(); - UserContext.message(Message.info("ACLs successfully changed")); + UserContext.message(Message.info().withTextMessage("ACLs successfully changed")); webContext.respondWith().redirectTemporarily("/ui/" + bucket.getEncodedName()); return; } @@ -183,7 +183,7 @@ public void bucket(WebContext webContext, String bucketName) { if (webContext.hasParameter("make-private")) { bucket.makePrivate(); - UserContext.message(Message.info("ACLs successfully changed")); + UserContext.message(Message.info().withTextMessage("ACLs successfully changed")); webContext.respondWith().redirectTemporarily("/ui/" + bucket.getEncodedName()); return; } @@ -192,7 +192,7 @@ public void bucket(WebContext webContext, String bucketName) { if (webContext.hasParameter("delete")) { bucket.delete(); - UserContext.message(Message.info("Bucket successfully deleted.")); + UserContext.message(Message.info().withTextMessage("Bucket successfully deleted.")); webContext.respondWith().redirectTemporarily("/ui"); return; } @@ -274,7 +274,7 @@ private void objects(WebContext webContext, Bucket bucket) { public void object(WebContext webContext, String bucketName, List idParts) { Bucket bucket = storage.getBucket(bucketName); if (!bucket.exists()) { - UserContext.message(Message.error("Bucket does not exist.")); + UserContext.message(Message.error().withTextMessage("Bucket does not exist.")); webContext.respondWith().redirectTemporarily("/ui"); return; } @@ -282,7 +282,7 @@ public void object(WebContext webContext, String bucketName, List idPart String id = String.join("/", idParts); StoredObject object = bucket.getObject(id); if (!object.exists()) { - UserContext.message(Message.error("Object does not exist.")); + UserContext.message(Message.error().withTextMessage("Object does not exist.")); webContext.respondWith().redirectTemporarily("/ui/" + bucket.getEncodedName()); return; } @@ -291,7 +291,7 @@ public void object(WebContext webContext, String bucketName, List idPart if (webContext.hasParameter("delete")) { object.delete(); - UserContext.message(Message.info("Object successfully deleted.")); + UserContext.message(Message.info().withTextMessage("Object successfully deleted.")); webContext.respondWith().redirectTemporarily("/ui/" + bucket.getEncodedName()); return; } diff --git a/src/main/java/ninja/S3Dispatcher.java b/src/main/java/ninja/S3Dispatcher.java index 070dbd1..7c3d542 100644 --- a/src/main/java/ninja/S3Dispatcher.java +++ b/src/main/java/ninja/S3Dispatcher.java @@ -258,11 +258,10 @@ public DispatchDecision dispatch(WebContext webContext) 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 webContext the current request + * @param uri the requested URI * @return the effective URI to process */ - public static String getEffectiveURI(WebContext webContext) { - String uri = webContext.getRequestedURI(); + public static String getEffectiveURI(String uri) { if (uri.startsWith("/s3")) { uri = uri.substring(3); } @@ -270,7 +269,7 @@ public static String getEffectiveURI(WebContext webContext) { uri = uri.substring(1); } - return Strings.urlEncode(uri).replace("+", "%20").replace("%2F", "/"); + return uri; } /** @@ -280,7 +279,7 @@ public static String getEffectiveURI(WebContext webContext) { * @return a structured {@link S3Request}. */ private static S3Request parseRequest(WebContext webContext) { - String uri = getEffectiveURI(webContext); + String uri = getEffectiveURI(webContext.getRequestedURI()); // we treat the first parameter without value as query string Iterator parameterIterator = webContext.getParameterNames().iterator(); diff --git a/src/site/index.html b/src/site/index.html index 17b9564..f3ccb75 100644 --- a/src/site/index.html +++ b/src/site/index.html @@ -59,7 +59,7 @@

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:7
  4. +
  5. Run like docker run -p 9444:9000 scireum/s3-ninja:latest
  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. 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.
  9. diff --git a/src/test/java/BaseAWSSpec.groovy b/src/test/java/BaseAWSSpec.groovy index 2ea2bde..0ac4dc1 100644 --- a/src/test/java/BaseAWSSpec.groovy +++ b/src/test/java/BaseAWSSpec.groovy @@ -171,6 +171,43 @@ abstract class BaseAWSSpec extends BaseSpecification { summaries.get(1).getKey() == key2 } + // reported in https://github.com/scireum/s3ninja/issues/180 + def "PUT and then LIST with prefix work as expected"() { + given: + def bucketName = DEFAULT_BUCKET_NAME + def key1 = DEFAULT_KEY + "/Eins" + def key2 = DEFAULT_KEY + "/Zwei" + def key3 = "a/key/with a different/prefix/Drei" + def client = getClient() + when: + if (client.doesBucketExist(bucketName)) { + client.deleteBucket(bucketName) + } + client.createBucket(bucketName) + and: + client.putObject( + bucketName, + key1, + new ByteArrayInputStream("Eins".getBytes(Charsets.UTF_8)), + new ObjectMetadata()) + client.putObject( + bucketName, + key2, + new ByteArrayInputStream("Zwei".getBytes(Charsets.UTF_8)), + new ObjectMetadata()) + client.putObject( + bucketName, + key3, + new ByteArrayInputStream("Drei".getBytes(Charsets.UTF_8)), + new ObjectMetadata()) + then: + def listing = client.listObjects(bucketName, DEFAULT_KEY + '/') + def summaries = listing.getObjectSummaries() + summaries.size() == 2 + summaries.get(0).getKey() == key1 + summaries.get(1).getKey() == key2 + } + def "PUT and then DELETE work as expected"() { given: def bucketName = DEFAULT_BUCKET_NAME