diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SSHKeyPairAsyncClient.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SSHKeyPairAsyncClient.java index 9a8e4f589e..04c14bd4ba 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SSHKeyPairAsyncClient.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/features/SSHKeyPairAsyncClient.java @@ -21,6 +21,7 @@ package org.jclouds.cloudstack.features; import com.google.common.util.concurrent.ListenableFuture; import org.jclouds.cloudstack.domain.SshKeyPair; import org.jclouds.cloudstack.filters.AuthenticationFilter; +import org.jclouds.cloudstack.filters.ReEncodeQueryWithDefaultURLEncoder; import org.jclouds.cloudstack.options.ListSSHKeyPairsOptions; import org.jclouds.rest.annotations.ExceptionParser; import org.jclouds.rest.annotations.OnlyElement; @@ -65,6 +66,7 @@ public interface SSHKeyPairAsyncClient { @QueryParams(keys = "command", values = "registerSSHKeyPair") @SelectJson("keypair") @Consumes(MediaType.APPLICATION_JSON) + @RequestFilters(ReEncodeQueryWithDefaultURLEncoder.class) ListenableFuture registerSSHKeyPair(@QueryParam("name") String name, @QueryParam("publickey") String publicKey); /** diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/filters/ReEncodeQueryWithDefaultURLEncoder.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/filters/ReEncodeQueryWithDefaultURLEncoder.java new file mode 100644 index 0000000000..c221c89077 --- /dev/null +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/filters/ReEncodeQueryWithDefaultURLEncoder.java @@ -0,0 +1,57 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.cloudstack.filters; + +import com.google.common.collect.Multimap; +import com.google.inject.Inject; +import com.google.inject.Provider; +import org.jclouds.http.HttpException; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpRequestFilter; +import org.jclouds.http.utils.ModifyRequest; + +import javax.ws.rs.core.UriBuilder; + +import static com.google.common.collect.Iterables.getOnlyElement; + +/** + * By default, jclouds controls encoding based on rules which are different + * + * @author Adrian Cole + */ +public class ReEncodeQueryWithDefaultURLEncoder implements HttpRequestFilter { + private final Provider builders; + + @Inject + public ReEncodeQueryWithDefaultURLEncoder(Provider builders) { + this.builders = builders; + } + + @Override + public HttpRequest filter(HttpRequest request) throws HttpException { + UriBuilder builder = builders.get(); + builder.uri(request.getEndpoint()); + Multimap map = ModifyRequest.parseQueryToMap(request.getEndpoint().getQuery()); + builder.replaceQuery(""); + for (String key : map.keySet()) + builder.queryParam(key, getOnlyElement(map.get(key))); + return request.toBuilder().endpoint(builder.build()).build(); + } + +} \ No newline at end of file diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java index fcd3aafa00..641b1259c3 100644 --- a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java +++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/compute/CloudStackExperimentLiveTest.java @@ -131,7 +131,7 @@ public class CloudStackExperimentLiveTest extends BaseCloudStackClientLiveTest { } } - @Test + @Test(enabled = false) public void testCreateWindowsMachineWithKeyPairAndCheckIfTheGeneratedPasswordIsEncrypted() throws RunNodesException, NoSuchAlgorithmException, CertificateException { // final Map sshKey = SshKeys.generate(); diff --git a/apis/cloudstack/src/test/java/org/jclouds/cloudstack/filters/ReEncodeQueryWithJavaNetURLEncoderTest.java b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/filters/ReEncodeQueryWithJavaNetURLEncoderTest.java new file mode 100644 index 0000000000..0d1b58d7a4 --- /dev/null +++ b/apis/cloudstack/src/test/java/org/jclouds/cloudstack/filters/ReEncodeQueryWithJavaNetURLEncoderTest.java @@ -0,0 +1,63 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.cloudstack.filters; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.sun.jersey.api.uri.UriBuilderImpl; +import org.jclouds.http.HttpRequest; +import org.jclouds.util.Strings2; +import org.testng.annotations.Test; + +import javax.ws.rs.core.UriBuilder; +import java.net.URI; +import java.net.URLEncoder; + +import static org.testng.Assert.assertEquals; + +/** + * @author Adrian Cole + */ +@Test(groups = "unit", testName = "ReEncodeQueryWithJavaNetURLEncoder") +public class ReEncodeQueryWithJavaNetURLEncoderTest { + + @Test + public void testReUrlEncode() { + String input = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCc903twxU2zcQnIJdXv61RwZNZW94uId9qz08fgsBJsCOnHNIC4+L9k" + + "DOA2IHV9cUfEDBm1Be5TbpadWwSbS/05E+FARH2/MCO932UgcKUq5PGymS0249fLCBPci5zoLiG5vIym+1ij1hL/nHvkK99NIwe7io+Lmp" + + "9OcF3PTsm3Rgh5T09cRHGX9horp0VoAVa9vKJx6C1/IEHVnG8p0YPPa1lmemvx5kNBEiyoNQNYa34EiFkcJfP6rqNgvY8h/j4nE9SXoUCC" + + "/g6frhMFMOL0tzYqvz0Lczqm1Oh4RnSn3O9X4R934p28qqAobe337hmlLUdb6H5zuf+NwCh0HdZ"; + + String defaultJcloudsEncodedRequest = "http://localhost?foo=" + Strings2.urlEncode(input); + @SuppressWarnings("deprecation") + String defaultEncodedRequest = "http://localhost?foo=" + URLEncoder.encode(input); + assert !defaultJcloudsEncodedRequest.equals(defaultEncodedRequest); + + HttpRequest request = new HttpRequest("GET", URI.create("http://localhost?foo=" + Strings2.urlEncode(input))); + request = Guice.createInjector(new AbstractModule() { + + @Override + protected void configure() { + bind(UriBuilder.class).to(UriBuilderImpl.class); + } + + }).getInstance(ReEncodeQueryWithDefaultURLEncoder.class).filter(request); + assertEquals(request.getEndpoint().toASCIIString(), "http://localhost?foo=" + URLEncoder.encode(input)); + } +} diff --git a/core/src/test/java/org/jclouds/http/utils/ModifyRequestTest.java b/core/src/test/java/org/jclouds/http/utils/ModifyRequestTest.java index fd9985a209..fa9fd3409d 100644 --- a/core/src/test/java/org/jclouds/http/utils/ModifyRequestTest.java +++ b/core/src/test/java/org/jclouds/http/utils/ModifyRequestTest.java @@ -18,22 +18,23 @@ */ package org.jclouds.http.utils; -import static org.jclouds.http.utils.ModifyRequest.parseQueryToMap; -import static org.testng.Assert.assertEquals; - -import java.net.URI; - -import javax.ws.rs.core.MediaType; - -import org.jclouds.http.HttpRequest; -import org.jclouds.io.Payload; -import org.jclouds.io.Payloads; -import org.testng.annotations.Test; - import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.Multimap; +import org.jclouds.http.HttpRequest; +import org.jclouds.io.Payload; +import org.jclouds.io.Payloads; +import org.jclouds.util.Strings2; +import org.testng.annotations.Test; + +import javax.ws.rs.core.MediaType; +import java.net.URI; +import java.util.Set; + +import static org.jclouds.http.utils.ModifyRequest.parseQueryToMap; +import static org.testng.Assert.assertEquals; /** * @author Adrian Cole @@ -130,4 +131,17 @@ public class ModifyRequestTest { assert valueForSig.equals("123") : "Expected the value for 'v' to be '123', found: " + valueForSig; } + @Test + public void testParseQueryEncodedWithDefaultJavaEncoder() { + String key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCc903twxU2zcQnIJdXv61RwZNZW94uId9qz08fgsBJsCOnHNIC4+L9k" + + "DOA2IHV9cUfEDBm1Be5TbpadWwSbS/05E+FARH2/MCO932UgcKUq5PGymS0249fLCBPci5zoLiG5vIym+1ij1hL/nHvkK99NIwe7io+Lmp" + + "9OcF3PTsm3Rgh5T09cRHGX9horp0VoAVa9vKJx6C1/IEHVnG8p0YPPa1lmemvx5kNBEiyoNQNYa34EiFkcJfP6rqNgvY8h/j4nE9SXoUCC" + + "/g6frhMFMOL0tzYqvz0Lczqm1Oh4RnSn3O9X4R934p28qqAobe337hmlLUdb6H5zuf+NwCh0HdZ"; + String queryString = "publickey=" + Strings2.urlEncode(key); + Multimap parsedMap = parseQueryToMap(queryString); + + Set expected = ImmutableSet.of(Strings2.urlEncode(key)); + assertEquals(parsedMap.get("publickey"), expected); + } + } diff --git a/core/src/test/java/org/jclouds/util/Strings2Test.java b/core/src/test/java/org/jclouds/util/Strings2Test.java index c94a4c17aa..4e0b32085b 100644 --- a/core/src/test/java/org/jclouds/util/Strings2Test.java +++ b/core/src/test/java/org/jclouds/util/Strings2Test.java @@ -18,11 +18,12 @@ */ package org.jclouds.util; -import static org.testng.Assert.assertEquals; - +import com.google.common.collect.ImmutableMap; import org.testng.annotations.Test; -import com.google.common.collect.ImmutableMap; +import static org.jclouds.util.Strings2.urlDecode; +import static org.jclouds.util.Strings2.urlEncode; +import static org.testng.Assert.assertEquals; /** * @author Adrian Cole @@ -36,13 +37,21 @@ public class Strings2Test { } public void testNoDoubleEncode() { - assertEquals(Strings2.urlEncode("/read-tests/%73%6f%6d%65%20%66%69%6c%65", '/'), + assertEquals(urlEncode("/read-tests/%73%6f%6d%65%20%66%69%6c%65", '/'), "/read-tests/%73%6f%6d%65%20%66%69%6c%65"); - assertEquals(Strings2.urlEncode("/read-tests/ tep", '/'), "/read-tests/%20tep"); + assertEquals(urlEncode("/read-tests/ tep", '/'), "/read-tests/%20tep"); } public void testReplaceTokens() { assertEquals(Strings2.replaceTokens("hello {where}", ImmutableMap.of("where", "world")), "hello world"); } + public void testUrlEncodeDecodeShouldGiveTheSameString() { + String actual = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCc903twxU2zcQnIJdXv61RwZNZW94uId9qz08fgsBJsCOnHNIC4+L9k" + + "DOA2IHV9cUfEDBm1Be5TbpadWwSbS/05E+FARH2/MCO932UgcKUq5PGymS0249fLCBPci5zoLiG5vIym+1ij1hL/nHvkK99NIwe7io+Lmp" + + "9OcF3PTsm3Rgh5T09cRHGX9horp0VoAVa9vKJx6C1/IEHVnG8p0YPPa1lmemvx5kNBEiyoNQNYa34EiFkcJfP6rqNgvY8h/j4nE9SXoUCC" + + "/g6frhMFMOL0tzYqvz0Lczqm1Oh4RnSn3O9X4R934p28qqAobe337hmlLUdb6H5zuf+NwCh0HdZ"; + assertEquals(actual, urlDecode(urlEncode(actual))); + } + }