You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by ga...@apache.org on 2017/05/08 18:10:25 UTC

[23/50] jclouds git commit: JCLOUDS-1008: Use @Encoded with GCS.

JCLOUDS-1008: Use @Encoded with GCS.

Google cloud storage should use the @Encoded annotation with the
object names to make sure that the object is percent-encoded prior to
being submitted in the path of the request. This was previously broken
because the default path encoding ignores "/" and encodes the entire
string. The @Encoded annotation instructs jclouds annotation processor
to not encode the parameters to which it is attached and not to encode
the entire request path. Parameters that are not annotated with
@Encoded are URL encoded prior to being add to the path.


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/2385ba90
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/2385ba90
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/2385ba90

Branch: refs/heads/master
Commit: 2385ba901e98d89e6cabd2b32028e1b3cac3992e
Parents: 0612579
Author: Timur Alperovich <ti...@gmail.com>
Authored: Sat Sep 26 15:16:13 2015 -0700
Committer: Ignasi Barrera <na...@apache.org>
Committed: Wed Oct 21 10:40:04 2015 +0200

----------------------------------------------------------------------
 .../blobstore/GoogleCloudStorageBlobStore.java  | 47 +++++--------
 .../features/ObjectAccessControlsApi.java       | 32 ++++-----
 .../googlecloudstorage/features/ObjectApi.java  | 74 +++++++++++---------
 ...ogleCloudStorageBlobIntegrationLiveTest.java |  1 +
 .../features/ObjectApiMockTest.java             | 16 +++++
 .../src/test/resources/object_encoded_get.json  | 21 ++++++
 6 files changed, 114 insertions(+), 77 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/2385ba90/providers/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/blobstore/GoogleCloudStorageBlobStore.java
----------------------------------------------------------------------
diff --git a/providers/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/blobstore/GoogleCloudStorageBlobStore.java b/providers/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/blobstore/GoogleCloudStorageBlobStore.java
index e852704..5dbb7cd 100644
--- a/providers/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/blobstore/GoogleCloudStorageBlobStore.java
+++ b/providers/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/blobstore/GoogleCloudStorageBlobStore.java
@@ -20,8 +20,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.io.BaseEncoding.base64;
 import static org.jclouds.googlecloudstorage.domain.DomainResourceReferences.ObjectRole.READER;
 
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
 import java.util.List;
 import java.util.Set;
 
@@ -41,11 +39,11 @@ import org.jclouds.blobstore.domain.internal.BlobImpl;
 import org.jclouds.blobstore.domain.internal.PageSetImpl;
 import org.jclouds.blobstore.functions.BlobToHttpGetOptions;
 import org.jclouds.blobstore.internal.BaseBlobStore;
+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.options.CopyOptions;
 import org.jclouds.blobstore.strategy.internal.FetchBlobMetadata;
 import org.jclouds.blobstore.util.BlobUtils;
 import org.jclouds.collect.Memoized;
@@ -73,11 +71,10 @@ import org.jclouds.http.HttpResponseException;
 import org.jclouds.io.ContentMetadata;
 import org.jclouds.io.Payload;
 import org.jclouds.io.PayloadSlicer;
+import org.jclouds.util.Strings2;
 
-import com.google.common.base.Charsets;
 import com.google.common.base.Function;
 import com.google.common.base.Supplier;
-import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.hash.HashCode;
@@ -204,12 +201,7 @@ public final class GoogleCloudStorageBlobStore extends BaseBlobStore {
     */
    @Override
    public boolean blobExists(String container, String name) {
-      try {
-         String urlName = name.contains("/") ? URLEncoder.encode(name, Charsets.UTF_8.toString()) : name;
-         return api.getObjectApi().objectExists(container, urlName);
-      } catch (UnsupportedEncodingException e) {
-         throw Throwables.propagate(e);
-      }
+      return api.getObjectApi().objectExists(container, Strings2.urlEncode(name));
    }
 
    /**
@@ -239,12 +231,12 @@ public final class GoogleCloudStorageBlobStore extends BaseBlobStore {
 
    @Override
    public BlobMetadata blobMetadata(String container, String name) {
-      return objectToBlobMetadata.apply(api.getObjectApi().getObject(container, name));
+      return objectToBlobMetadata.apply(api.getObjectApi().getObject(container, Strings2.urlEncode(name)));
    }
 
    @Override
    public Blob getBlob(String container, String name, GetOptions options) {
-      GoogleCloudStorageObject gcsObject = api.getObjectApi().getObject(container, name);
+      GoogleCloudStorageObject gcsObject = api.getObjectApi().getObject(container, Strings2.urlEncode(name));
       if (gcsObject == null) {
          return null;
       }
@@ -252,7 +244,7 @@ public final class GoogleCloudStorageBlobStore extends BaseBlobStore {
       MutableBlobMetadata metadata = objectToBlobMetadata.apply(gcsObject);
       Blob blob = new BlobImpl(metadata);
       // TODO: Does getObject not get the payload?!
-      Payload payload = api.getObjectApi().download(container, name, httpOptions).getPayload();
+      Payload payload = api.getObjectApi().download(container, Strings2.urlEncode(name), httpOptions).getPayload();
       payload.setContentMetadata(metadata.getContentMetadata()); // Doing this first retains it on setPayload.
       blob.setPayload(payload);
       return blob;
@@ -260,18 +252,13 @@ public final class GoogleCloudStorageBlobStore extends BaseBlobStore {
 
    @Override
    public void removeBlob(String container, String name) {
-      String urlName;
-      try {
-         urlName = name.contains("/") ? URLEncoder.encode(name, Charsets.UTF_8.toString()) : name;
-      } catch (UnsupportedEncodingException uee) {
-         throw Throwables.propagate(uee);
-      }
-      api.getObjectApi().deleteObject(container, urlName);
+      api.getObjectApi().deleteObject(container, Strings2.urlEncode(name));
    }
 
    @Override
    public BlobAccess getBlobAccess(String container, String name) {
-      ObjectAccessControls controls = api.getObjectAccessControlsApi().getObjectAccessControls(container, name, "allUsers");
+      ObjectAccessControls controls = api.getObjectAccessControlsApi().getObjectAccessControls(container,
+            Strings2.urlEncode(name), "allUsers");
       if (controls != null && controls.role() == DomainResourceReferences.ObjectRole.READER) {
          return BlobAccess.PUBLIC_READ;
       } else {
@@ -287,9 +274,9 @@ public final class GoogleCloudStorageBlobStore extends BaseBlobStore {
                .bucket(container)
                .role(READER)
                .build();
-         api.getObjectApi().patchObject(container, name, new ObjectTemplate().addAcl(controls));
+         api.getObjectApi().patchObject(container, Strings2.urlEncode(name), new ObjectTemplate().addAcl(controls));
       } else {
-         api.getObjectAccessControlsApi().deleteObjectAccessControls(container, name, "allUsers");
+         api.getObjectAccessControlsApi().deleteObjectAccessControls(container, Strings2.urlEncode(name), "allUsers");
       }
    }
 
@@ -312,7 +299,8 @@ public final class GoogleCloudStorageBlobStore extends BaseBlobStore {
    public String copyBlob(String fromContainer, String fromName, String toContainer, String toName,
          CopyOptions options) {
       if (!options.getContentMetadata().isPresent() && !options.getUserMetadata().isPresent()) {
-         return api.getObjectApi().copyObject(toContainer, toName, fromContainer, fromName).etag();
+         return api.getObjectApi().copyObject(toContainer, Strings2.urlEncode(toName), fromContainer,
+               Strings2.urlEncode(fromName)).etag();
       }
 
       ObjectTemplate template = new ObjectTemplate();
@@ -349,7 +337,8 @@ public final class GoogleCloudStorageBlobStore extends BaseBlobStore {
          template.customMetadata(options.getUserMetadata().get());
       }
 
-      return api.getObjectApi().copyObject(toContainer, toName, fromContainer, fromName, template).etag();
+      return api.getObjectApi().copyObject(toContainer, Strings2.urlEncode(toName), fromContainer,
+            Strings2.urlEncode(fromName), template).etag();
    }
 
    @Override
@@ -372,12 +361,14 @@ public final class GoogleCloudStorageBlobStore extends BaseBlobStore {
    public String completeMultipartUpload(MultipartUpload mpu, List<MultipartPart> parts) {
       ImmutableList.Builder<GoogleCloudStorageObject> builder = ImmutableList.builder();
       for (MultipartPart part : parts) {
-         builder.add(api.getObjectApi().getObject(mpu.containerName(), getMPUPartName(mpu, part.partNumber())));
+         builder.add(api.getObjectApi().getObject(mpu.containerName(),
+               Strings2.urlEncode(getMPUPartName(mpu, part.partNumber()))));
       }
       ObjectTemplate destination = blobMetadataToObjectTemplate.apply(mpu.blobMetadata());
       ComposeObjectTemplate template = ComposeObjectTemplate.builder().fromGoogleCloudStorageObject(builder.build())
             .destination(destination).build();
-      return api.getObjectApi().composeObjects(mpu.containerName(), mpu.blobName(), template).etag();
+      return api.getObjectApi().composeObjects(mpu.containerName(), Strings2.urlEncode(mpu.blobName()), template)
+            .etag();
       // TODO: delete components?
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/2385ba90/providers/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/features/ObjectAccessControlsApi.java
----------------------------------------------------------------------
diff --git a/providers/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/features/ObjectAccessControlsApi.java b/providers/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/features/ObjectAccessControlsApi.java
index ce31029..32b5e7c 100644
--- a/providers/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/features/ObjectAccessControlsApi.java
+++ b/providers/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/features/ObjectAccessControlsApi.java
@@ -23,6 +23,7 @@ import java.util.List;
 import javax.inject.Named;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
+import javax.ws.rs.Encoded;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
 import javax.ws.rs.PUT;
@@ -41,7 +42,6 @@ import org.jclouds.rest.annotations.Fallback;
 import org.jclouds.rest.annotations.PATCH;
 import org.jclouds.rest.annotations.RequestFilters;
 import org.jclouds.rest.annotations.SelectJson;
-import org.jclouds.rest.annotations.SkipEncoding;
 import org.jclouds.rest.binders.BindToJsonPayload;
 
 /**
@@ -49,7 +49,6 @@ import org.jclouds.rest.binders.BindToJsonPayload;
  *
  * @see <a href = " https://developers.google.com/storage/docs/json_api/v1/objectAccessControls "/>
  */
-@SkipEncoding({ '/', '=' })
 @RequestFilters(OAuthFilter.class)
 @Consumes(APPLICATION_JSON)
 public interface ObjectAccessControlsApi {
@@ -74,7 +73,7 @@ public interface ObjectAccessControlsApi {
    @Fallback(NullOnNotFoundOr404.class)
    @Nullable
    ObjectAccessControls getObjectAccessControls(@PathParam("bucket") String bucketName,
-            @PathParam("object") String objectName, @PathParam("entity") String entity);
+            @PathParam("object") @Encoded String objectName, @PathParam("entity") String entity);
 
    /**
     * Returns the acl entry for the specified entity on the specified object.
@@ -97,7 +96,7 @@ public interface ObjectAccessControlsApi {
    @Fallback(NullOnNotFoundOr404.class)
    @Nullable
    ObjectAccessControls getObjectAccessControls(@PathParam("bucket") String bucketName,
-            @PathParam("object") String objectName, @PathParam("entity") String entity,
+            @PathParam("object") @Encoded String objectName, @PathParam("entity") String entity,
             @QueryParam("generation") Long generation);
 
    /**
@@ -116,7 +115,7 @@ public interface ObjectAccessControlsApi {
    @Produces(APPLICATION_JSON)
    @Path("/b/{bucket}/o/{object}/acl")
    ObjectAccessControls createObjectAccessControls(@PathParam("bucket") String bucketName,
-            @PathParam("object") String objectName,
+            @PathParam("object") @Encoded String objectName,
             @BinderParam(BindToJsonPayload.class) ObjectAccessControlsTemplate template);
 
    /**
@@ -137,7 +136,7 @@ public interface ObjectAccessControlsApi {
    @Produces(APPLICATION_JSON)
    @Path("/b/{bucket}/o/{object}/acl")
    ObjectAccessControls createObjectAccessControls(@PathParam("bucket") String bucketName,
-            @PathParam("object") String objectName,
+            @PathParam("object") @Encoded String objectName,
             @BinderParam(BindToJsonPayload.class) ObjectAccessControlsTemplate template,
             @QueryParam("generation") Long generation);
 
@@ -155,8 +154,8 @@ public interface ObjectAccessControlsApi {
    @Named("ObjectAccessControls:delete")
    @DELETE
    @Path("/b/{bucket}/o/{object}/acl/{entity}")
-   void deleteObjectAccessControls(@PathParam("bucket") String bucketName, @PathParam("object") String objectName,
-            @PathParam("entity") String entity);
+   void deleteObjectAccessControls(@PathParam("bucket") String bucketName,
+         @PathParam("object") @Encoded String objectName, @PathParam("entity") String entity);
 
    /**
     * Permanently deletes the acl entry for the specified entity on the specified bucket.
@@ -174,8 +173,9 @@ public interface ObjectAccessControlsApi {
    @Named("ObjectAccessControls:delete")
    @DELETE
    @Path("/b/{bucket}/o/{object}/acl/{entity}")
-   void deleteObjectAccessControls(@PathParam("bucket") String bucketName, @PathParam("object") String objectName,
-            @PathParam("entity") String entity, @QueryParam("generation") Long generation);
+   void deleteObjectAccessControls(@PathParam("bucket") String bucketName,
+         @PathParam("object") @Encoded String objectName, @PathParam("entity") String entity,
+         @QueryParam("generation") Long generation);
 
    /**
     * Retrieves acl entries on a specified object
@@ -193,7 +193,7 @@ public interface ObjectAccessControlsApi {
    @Fallback(NullOnNotFoundOr404.class)
    @Nullable
    List<ObjectAccessControls> listObjectAccessControls(@PathParam("bucket") String bucketName,
-            @PathParam("object") String objectName);
+            @PathParam("object") @Encoded String objectName);
 
    /**
     * Retrieves acl entries on a specified object
@@ -214,7 +214,7 @@ public interface ObjectAccessControlsApi {
    @Fallback(NullOnNotFoundOr404.class)
    @Nullable
    List<ObjectAccessControls> listObjectAccessControls(@PathParam("bucket") String bucketName,
-            @PathParam("object") String objectName, @QueryParam("generation") Long generation);
+            @PathParam("object") @Encoded String objectName, @QueryParam("generation") Long generation);
 
    /**
     * Updates an acl entry on the specified object
@@ -237,7 +237,7 @@ public interface ObjectAccessControlsApi {
    @Produces(APPLICATION_JSON)
    @Path("/b/{bucket}/o/{object}/acl/{entity}")
    ObjectAccessControls updateObjectAccessControls(@PathParam("bucket") String bucketName,
-            @PathParam("object") String objectName, @PathParam("entity") String entity,
+            @PathParam("object") @Encoded String objectName, @PathParam("entity") String entity,
             @BinderParam(BindToJsonPayload.class) ObjectAccessControlsTemplate template);
 
    /**
@@ -262,7 +262,7 @@ public interface ObjectAccessControlsApi {
    @Produces(APPLICATION_JSON)
    @Path("/b/{bucket}/o/{object}/acl/{entity}")
    ObjectAccessControls updateObjectAccessControls(@PathParam("bucket") String bucketName,
-            @PathParam("object") String objectName, @PathParam("entity") String entity,
+            @PathParam("object") @Encoded String objectName, @PathParam("entity") String entity,
             @BinderParam(BindToJsonPayload.class) ObjectAccessControlsTemplate template,
             @QueryParam("generation") Long generation);
 
@@ -286,7 +286,7 @@ public interface ObjectAccessControlsApi {
    @Produces(APPLICATION_JSON)
    @Path("/b/{bucket}/o/{object}/acl/{entity}")
    ObjectAccessControls patchObjectAccessControls(@PathParam("bucket") String bucketName,
-            @PathParam("object") String objectName, @PathParam("entity") String entity,
+            @PathParam("object") @Encoded String objectName, @PathParam("entity") String entity,
             @BinderParam(BindToJsonPayload.class) ObjectAccessControlsTemplate template);
 
    /**
@@ -311,7 +311,7 @@ public interface ObjectAccessControlsApi {
    @Produces(APPLICATION_JSON)
    @Path("/b/{bucket}/o/{object}/acl/{entity}")
    ObjectAccessControls patchObjectAccessControls(@PathParam("bucket") String bucketName,
-            @PathParam("object") String objectName, @PathParam("entity") String entity,
+            @PathParam("object") @Encoded String objectName, @PathParam("entity") String entity,
             @BinderParam(BindToJsonPayload.class) ObjectAccessControlsTemplate template,
             @QueryParam("generation") Long generation);
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/2385ba90/providers/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/features/ObjectApi.java
----------------------------------------------------------------------
diff --git a/providers/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/features/ObjectApi.java b/providers/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/features/ObjectApi.java
index ff8dfde..4e4f5b9 100644
--- a/providers/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/features/ObjectApi.java
+++ b/providers/google-cloud-storage/src/main/java/org/jclouds/googlecloudstorage/features/ObjectApi.java
@@ -21,6 +21,7 @@ import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
 import javax.inject.Named;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
+import javax.ws.rs.Encoded;
 import javax.ws.rs.GET;
 import javax.ws.rs.HeaderParam;
 import javax.ws.rs.POST;
@@ -60,7 +61,6 @@ import org.jclouds.rest.annotations.PayloadParam;
 import org.jclouds.rest.annotations.QueryParams;
 import org.jclouds.rest.annotations.RequestFilters;
 import org.jclouds.rest.annotations.ResponseParser;
-import org.jclouds.rest.annotations.SkipEncoding;
 import org.jclouds.rest.binders.BindToJsonPayload;
 
 /**
@@ -68,7 +68,6 @@ import org.jclouds.rest.binders.BindToJsonPayload;
  *
  * @see <a href="https://developers.google.com/storage/docs/json_api/v1/objects"/>
  */
-@SkipEncoding({ '/', '=' })
 @RequestFilters(OAuthFilter.class)
 public interface ObjectApi {
 
@@ -87,7 +86,7 @@ public interface ObjectApi {
    @Path("storage/v1/b/{bucket}/o/{object}")
    @Fallback(FalseOnNotFoundOr404.class)
    @Nullable
-   boolean objectExists(@PathParam("bucket") String bucketName, @PathParam("object") String objectName);
+   boolean objectExists(@PathParam("bucket") String bucketName, @PathParam("object") @Encoded String objectName);
 
    /**
     * Retrieve an object metadata
@@ -105,7 +104,8 @@ public interface ObjectApi {
    @Consumes(APPLICATION_JSON)
    @Fallback(NullOnNotFoundOr404.class)
    @Nullable
-   GoogleCloudStorageObject getObject(@PathParam("bucket") String bucketName, @PathParam("object") String objectName);
+   GoogleCloudStorageObject getObject(@PathParam("bucket") String bucketName,
+         @PathParam("object") @Encoded String objectName);
 
    /**
     * Retrieves objects metadata
@@ -126,8 +126,8 @@ public interface ObjectApi {
    @Consumes(APPLICATION_JSON)
    @Fallback(NullOnNotFoundOr404.class)
    @Nullable
-   GoogleCloudStorageObject getObject(@PathParam("bucket") String bucketName, @PathParam("object") String objectName,
-         HttpRequestOptions options);
+   GoogleCloudStorageObject getObject(@PathParam("bucket") String bucketName,
+         @PathParam("object") @Encoded String objectName, HttpRequestOptions options);
 
    /**
     * Retrieve an object or their metadata
@@ -146,7 +146,7 @@ public interface ObjectApi {
    @ResponseParser(ParseToPayloadEnclosing.class)
    @Fallback(NullOnNotFoundOr404.class)
    @Nullable
-   PayloadEnclosing download(@PathParam("bucket") String bucketName, @PathParam("object") String objectName);
+   PayloadEnclosing download(@PathParam("bucket") String bucketName, @PathParam("object") @Encoded String objectName);
 
    /**
     * Retrieves objects
@@ -167,7 +167,8 @@ public interface ObjectApi {
    @Path("storage/v1/b/{bucket}/o/{object}")
    @ResponseParser(ParseToPayloadEnclosing.class)
    @Fallback(NullOnNotFoundOr404.class)
-   @Nullable PayloadEnclosing download(@PathParam("bucket") String bucketName, @PathParam("object") String objectName,
+   @Nullable
+   PayloadEnclosing download(@PathParam("bucket") String bucketName, @PathParam("object") @Encoded String objectName,
          HttpRequestOptions options);
 
    /**
@@ -204,7 +205,7 @@ public interface ObjectApi {
    @DELETE
    @Path("storage/v1/b/{bucket}/o/{object}")
    @Fallback(FalseOnNotFoundOr404.class)
-   boolean deleteObject(@PathParam("bucket") String bucketName, @PathParam("object") String objectName);
+   boolean deleteObject(@PathParam("bucket") String bucketName, @PathParam("object") @Encoded String objectName);
 
    /**
     * Deletes an object and its metadata. Deletions are permanent if versioning is not enabled for the bucket, or if the
@@ -221,7 +222,7 @@ public interface ObjectApi {
    @DELETE
    @Path("storage/v1/b/{bucket}/o/{object}")
    @Fallback(FalseOnNotFoundOr404.class)
-   boolean deleteObject(@PathParam("bucket") String bucketName, @PathParam("object") String objectName,
+   boolean deleteObject(@PathParam("bucket") String bucketName, @PathParam("object") @Encoded String objectName,
             DeleteObjectOptions options);
 
    /**
@@ -271,8 +272,9 @@ public interface ObjectApi {
    @Produces(APPLICATION_JSON)
    @Path("storage/v1/b/{bucket}/o/{object}")
    @Fallback(NullOnNotFoundOr404.class)
-   GoogleCloudStorageObject updateObject(@PathParam("bucket") String bucketName, @PathParam("object") String objectName,
-            @BinderParam(BindToJsonPayload.class) ObjectTemplate objectTemplate);
+   GoogleCloudStorageObject updateObject(@PathParam("bucket") String bucketName,
+         @PathParam("object") @Encoded String objectName,
+         @BinderParam(BindToJsonPayload.class) ObjectTemplate objectTemplate);
 
    /**
     * Updates an object
@@ -294,8 +296,9 @@ public interface ObjectApi {
    @Produces(APPLICATION_JSON)
    @Path("storage/v1/b/{bucket}/o/{object}")
    @Fallback(NullOnNotFoundOr404.class)
-   GoogleCloudStorageObject updateObject(@PathParam("bucket") String bucketName, @PathParam("object") String objectName,
-            @BinderParam(BindToJsonPayload.class) ObjectTemplate objectTemplate, UpdateObjectOptions options);
+   GoogleCloudStorageObject updateObject(@PathParam("bucket") String bucketName,
+         @PathParam("object") @Encoded String objectName,
+         @BinderParam(BindToJsonPayload.class) ObjectTemplate objectTemplate, UpdateObjectOptions options);
 
    /**
     * Updates an object according to patch semantics
@@ -315,8 +318,9 @@ public interface ObjectApi {
    @Produces(APPLICATION_JSON)
    @Path("storage/v1/b/{bucket}/o/{object}")
    @Fallback(NullOnNotFoundOr404.class)
-   GoogleCloudStorageObject patchObject(@PathParam("bucket") String bucketName, @PathParam("object") String objectName,
-            @BinderParam(BindToJsonPayload.class) ObjectTemplate objectTemplate);
+   GoogleCloudStorageObject patchObject(@PathParam("bucket") String bucketName,
+         @PathParam("object") @Encoded String objectName,
+         @BinderParam(BindToJsonPayload.class) ObjectTemplate objectTemplate);
 
    /**
     * Updates an object according to patch semantics
@@ -338,8 +342,9 @@ public interface ObjectApi {
    @Produces(APPLICATION_JSON)
    @Path("storage/v1/b/{bucket}/o/{object}")
    @Fallback(NullOnNotFoundOr404.class)
-   GoogleCloudStorageObject patchObject(@PathParam("bucket") String bucketName, @PathParam("object") String objectName,
-            @BinderParam(BindToJsonPayload.class) ObjectTemplate objectTemplate, UpdateObjectOptions options);
+   GoogleCloudStorageObject patchObject(@PathParam("bucket") String bucketName,
+         @PathParam("object") @Encoded String objectName,
+         @BinderParam(BindToJsonPayload.class) ObjectTemplate objectTemplate, UpdateObjectOptions options);
 
    /**
     * Concatenates a list of existing objects into a new object in the same bucket.
@@ -358,7 +363,7 @@ public interface ObjectApi {
    @Consumes(APPLICATION_JSON)
    @Path("storage/v1/b/{destinationBucket}/o/{destinationObject}/compose")
    GoogleCloudStorageObject composeObjects(@PathParam("destinationBucket") String destinationBucket,
-            @PathParam("destinationObject") String destinationObject,
+            @PathParam("destinationObject") @Encoded String destinationObject,
             @BinderParam(BindToJsonPayload.class) ComposeObjectTemplate composeObjectTemplate);
 
    /**
@@ -380,7 +385,7 @@ public interface ObjectApi {
    @Consumes(APPLICATION_JSON)
    @Path("storage/v1/b/{destinationBucket}/o/{destinationObject}/compose")
    GoogleCloudStorageObject composeObjects(@PathParam("destinationBucket") String destinationBucket,
-            @PathParam("destinationObject") String destinationObject,
+            @PathParam("destinationObject") @Encoded String destinationObject,
             @BinderParam(BindToJsonPayload.class) ComposeObjectTemplate composeObjectTemplate,
             ComposeObjectOptions options);
 
@@ -403,8 +408,9 @@ public interface ObjectApi {
    @Consumes(APPLICATION_JSON)
    @Path("/storage/v1/b/{sourceBucket}/o/{sourceObject}/copyTo/b/{destinationBucket}/o/{destinationObject}")
    GoogleCloudStorageObject copyObject(@PathParam("destinationBucket") String destinationBucket,
-            @PathParam("destinationObject") String destinationObject, @PathParam("sourceBucket") String sourceBucket,
-            @PathParam("sourceObject") String sourceObject);
+         @PathParam("destinationObject") @Encoded String destinationObject,
+         @PathParam("sourceBucket") String sourceBucket,
+         @PathParam("sourceObject") @Encoded String sourceObject);
 
     /**
      * Copies an object to a specified location with updated metadata.
@@ -427,8 +433,10 @@ public interface ObjectApi {
     @Consumes(APPLICATION_JSON)
     @Path("/storage/v1/b/{sourceBucket}/o/{sourceObject}/copyTo/b/{destinationBucket}/o/{destinationObject}")
     GoogleCloudStorageObject copyObject(@PathParam("destinationBucket") String destinationBucket,
-                                        @PathParam("destinationObject") String destinationObject, @PathParam("sourceBucket") String sourceBucket,
-                                        @PathParam("sourceObject") String sourceObject, @BinderParam(BindToJsonPayload.class) ObjectTemplate template);
+          @PathParam("destinationObject") @Encoded String destinationObject,
+          @PathParam("sourceBucket") String sourceBucket,
+          @PathParam("sourceObject") @Encoded String sourceObject,
+          @BinderParam(BindToJsonPayload.class) ObjectTemplate template);
 
    /**
     * Copies an object to a specified location. Optionally overrides metadata.
@@ -451,8 +459,9 @@ public interface ObjectApi {
    @Consumes(APPLICATION_JSON)
    @Path("/storage/v1/b/{sourceBucket}/o/{sourceObject}/copyTo/b/{destinationBucket}/o/{destinationObject}")
    GoogleCloudStorageObject copyObject(@PathParam("destinationBucket") String destinationBucket,
-            @PathParam("destinationObject") String destinationObject, @PathParam("sourceBucket") String sourceBucket,
-            @PathParam("sourceObject") String sourceObject, CopyObjectOptions options);
+         @PathParam("destinationObject") @Encoded String destinationObject,
+         @PathParam("sourceBucket") String sourceBucket,
+         @PathParam("sourceObject") @Encoded String sourceObject, CopyObjectOptions options);
 
    /**
     * Stores a new object with metadata.
@@ -495,9 +504,8 @@ public interface ObjectApi {
    @Consumes(APPLICATION_JSON)
    @Path("/storage/v1/b/{sourceBucket}/o/{sourceObject}/rewriteTo/b/{destinationBucket}/o/{destinationObject}")
    RewriteResponse rewriteObjects(@PathParam("destinationBucket") String destinationBucket,
-            @PathParam("destinationObject") String destinationObject,
-            @PathParam("sourceBucket") String sourceBucket,
-            @PathParam("sourceObject") String sourceObject);
+         @PathParam("destinationObject") @Encoded String destinationObject,
+         @PathParam("sourceBucket") String sourceBucket, @PathParam("sourceObject") @Encoded String sourceObject);
 
    /**
     * Rewrites a source object to a destination object.
@@ -520,8 +528,8 @@ public interface ObjectApi {
    @Consumes(APPLICATION_JSON)
    @Path("/storage/v1/b/{sourceBucket}/o/{sourceObject}/rewriteTo/b/{destinationBucket}/o/{destinationObject}")
    RewriteResponse rewriteObjects(@PathParam("destinationBucket") String destinationBucket,
-            @PathParam("destinationObject") String destinationObject,
-            @PathParam("sourceBucket") String sourceBucket,
-            @PathParam("sourceObject") String sourceObject,
-            RewriteObjectOptions options);
+         @PathParam("destinationObject") @Encoded String destinationObject,
+         @PathParam("sourceBucket") String sourceBucket,
+         @PathParam("sourceObject") @Encoded String sourceObject,
+         RewriteObjectOptions options);
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/2385ba90/providers/google-cloud-storage/src/test/java/org/jclouds/googlecloudstorage/blobstore/integration/GoogleCloudStorageBlobIntegrationLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/google-cloud-storage/src/test/java/org/jclouds/googlecloudstorage/blobstore/integration/GoogleCloudStorageBlobIntegrationLiveTest.java b/providers/google-cloud-storage/src/test/java/org/jclouds/googlecloudstorage/blobstore/integration/GoogleCloudStorageBlobIntegrationLiveTest.java
index 162c45a..db1bcd8 100644
--- a/providers/google-cloud-storage/src/test/java/org/jclouds/googlecloudstorage/blobstore/integration/GoogleCloudStorageBlobIntegrationLiveTest.java
+++ b/providers/google-cloud-storage/src/test/java/org/jclouds/googlecloudstorage/blobstore/integration/GoogleCloudStorageBlobIntegrationLiveTest.java
@@ -143,6 +143,7 @@ public class GoogleCloudStorageBlobIntegrationLiveTest extends BaseBlobIntegrati
 
       return new Object[][] { { "file.xml", "text/xml", file, realObject },
                { "string.xml", "text/xml", realObject, realObject },
+               { "stringwith/slash.xml", "text/xml", realObject, realObject },
                { "bytes.xml", "application/octet-stream", realObject.getBytes(), realObject } };
    }
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/2385ba90/providers/google-cloud-storage/src/test/java/org/jclouds/googlecloudstorage/features/ObjectApiMockTest.java
----------------------------------------------------------------------
diff --git a/providers/google-cloud-storage/src/test/java/org/jclouds/googlecloudstorage/features/ObjectApiMockTest.java b/providers/google-cloud-storage/src/test/java/org/jclouds/googlecloudstorage/features/ObjectApiMockTest.java
index 9906a90..35e9827 100644
--- a/providers/google-cloud-storage/src/test/java/org/jclouds/googlecloudstorage/features/ObjectApiMockTest.java
+++ b/providers/google-cloud-storage/src/test/java/org/jclouds/googlecloudstorage/features/ObjectApiMockTest.java
@@ -40,6 +40,7 @@ import org.jclouds.googlecloudstorage.parse.ParseGoogleCloudStorageObjectListTes
 import org.jclouds.googlecloudstorage.parse.ParseObjectRewriteResponse;
 import org.jclouds.http.internal.PayloadEnclosingImpl;
 import org.jclouds.io.PayloadEnclosing;
+import org.jclouds.util.Strings2;
 import org.testng.annotations.Test;
 
 import com.google.common.net.MediaType;
@@ -57,6 +58,13 @@ public class ObjectApiMockTest extends BaseGoogleCloudStorageApiMockTest {
       assertSent(server, "GET", "/storage/v1/b/test/o/file_name", null);
    }
 
+   public void existsEncoded() throws Exception {
+      server.enqueue(jsonResponse("/object_encoded_get.json"));
+
+      assertTrue(objectApi().objectExists("test", Strings2.urlEncode("dir/file name")));
+      assertSent(server, "GET", "/storage/v1/b/test/o/dir%2Ffile%20name", null);
+   }
+
    public void exists_4xx() throws Exception {
       server.enqueue(response404());
 
@@ -120,6 +128,14 @@ public class ObjectApiMockTest extends BaseGoogleCloudStorageApiMockTest {
       assertSent(server, "DELETE", "/storage/v1/b/test/o/object_name", null);
    }
 
+   public void delete_encoded() throws Exception {
+      server.enqueue(new MockResponse());
+
+      // TODO: Should this be returning True on not found?
+      assertTrue(objectApi().deleteObject("test", Strings2.urlEncode("dir/object name")));
+      assertSent(server, "DELETE", "/storage/v1/b/test/o/dir%2Fobject%20name", null);
+   }
+
    public void list() throws Exception {
       server.enqueue(jsonResponse("/object_list.json"));
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/2385ba90/providers/google-cloud-storage/src/test/resources/object_encoded_get.json
----------------------------------------------------------------------
diff --git a/providers/google-cloud-storage/src/test/resources/object_encoded_get.json b/providers/google-cloud-storage/src/test/resources/object_encoded_get.json
new file mode 100644
index 0000000..7fdb843
--- /dev/null
+++ b/providers/google-cloud-storage/src/test/resources/object_encoded_get.json
@@ -0,0 +1,21 @@
+{
+  "kind": "storage#object",
+  "id": "test/dir%2Ffile%20name/1000",
+  "selfLink": "https://www.googleapis.com/storage/v1/b/test/o/dir%2Ffile%20name",
+  "name": "dir%2Ffile%20name",
+  "bucket": "test",
+  "generation": "1000",
+  "metageneration": "8",
+  "contentType": "application/x-tar",
+  "updated": "2014-09-27T00:01:44.819",
+  "storageClass": "STANDARD",
+  "size": "1000",
+  "md5Hash": "md5Hash",
+  "mediaLink": "https://www.googleapis.com/download/storage/v1/b/test/o/dir%2Ffile%20name?generation=1000&alt=media",
+  "owner": {
+    "entity": "entity",
+    "entityId": "entityId"
+  },
+  "crc32c": "crc32c",
+  "etag": "etag"
+}