diff --git a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/ContainerAPITest.groovy b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/ContainerAPITest.groovy
index 9342fabf9e83..c61edbdd6f6c 100644
--- a/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/ContainerAPITest.groovy
+++ b/sdk/storage/azure-storage-blob/src/test/java/com/azure/storage/blob/ContainerAPITest.groovy
@@ -28,7 +28,9 @@ import com.azure.storage.blob.options.BlobSetAccessTierOptions
import com.azure.storage.blob.options.PageBlobCreateOptions
import com.azure.storage.blob.specialized.AppendBlobClient
import com.azure.storage.blob.specialized.BlobClientBase
+import com.azure.storage.common.StorageSharedKeyCredential
import com.azure.storage.common.Utility
+import com.azure.storage.common.implementation.StorageImplUtils
import reactor.test.StepVerifier
import spock.lang.Requires
import spock.lang.Unroll
@@ -909,6 +911,20 @@ class ContainerAPITest extends APISpec {
.verifyComplete()
}
+ def "List blobs prefix with comma"() {
+ setup:
+ def prefix = generateBlobName() + ", " + generateBlobName()
+ def b = cc.getBlobClient(prefix).getBlockBlobClient()
+ b.upload(defaultInputStream.get(), defaultData.remaining())
+
+ when:
+ ListBlobsOptions options = new ListBlobsOptions().setPrefix(prefix)
+ def blob = cc.listBlobs(options, null).iterator().next()
+
+ then:
+ blob.getName() == prefix
+ }
+
def "List blobs flat options fail"() {
when:
new ListBlobsOptions().setMaxResultsPerPage(0)
diff --git a/sdk/storage/azure-storage-blob/src/test/resources/session-records/ContainerAPITestlistblobsprefixwithcomma.json b/sdk/storage/azure-storage-blob/src/test/resources/session-records/ContainerAPITestlistblobsprefixwithcomma.json
new file mode 100644
index 000000000000..ec53974375a2
--- /dev/null
+++ b/sdk/storage/azure-storage-blob/src/test/resources/session-records/ContainerAPITestlistblobsprefixwithcomma.json
@@ -0,0 +1,112 @@
+{
+ "networkCallRecords" : [ {
+ "Method" : "PUT",
+ "Uri" : "https://REDACTED.blob.core.windows.net/jtclistblobsprefixwithcomma097102c4f575e414a74be?restype=container",
+ "Headers" : {
+ "x-ms-version" : "2020-02-10",
+ "User-Agent" : "azsdk-java-azure-storage-blob/12.10.0-beta.1 (11.0.7; Windows 10; 10.0)",
+ "x-ms-client-request-id" : "4197dd95-684f-4280-af03-5754c699537a"
+ },
+ "Response" : {
+ "x-ms-version" : "2020-02-10",
+ "Server" : "Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0",
+ "ETag" : "0x8D88C040CD150A7",
+ "Last-Modified" : "Wed, 18 Nov 2020 20:53:49 GMT",
+ "retry-after" : "0",
+ "Content-Length" : "0",
+ "StatusCode" : "201",
+ "x-ms-request-id" : "5a0b9c11-c01e-0073-35ec-bd7d3a000000",
+ "Date" : "Wed, 18 Nov 2020 20:53:49 GMT",
+ "x-ms-client-request-id" : "4197dd95-684f-4280-af03-5754c699537a"
+ },
+ "Exception" : null
+ }, {
+ "Method" : "PUT",
+ "Uri" : "https://REDACTED.blob.core.windows.net/jtclistblobsprefixwithcomma097102c4f575e414a74be/javabloblistblobsprefixwithcomma199461a7d43fde3c03%2C%20javabloblistblobsprefixwithcomma235482222869197945",
+ "Headers" : {
+ "x-ms-version" : "2020-02-10",
+ "User-Agent" : "azsdk-java-azure-storage-blob/12.10.0-beta.1 (11.0.7; Windows 10; 10.0)",
+ "x-ms-client-request-id" : "e98643a2-72d7-4436-89f2-2cdac3d230c2",
+ "Content-Type" : "application/octet-stream"
+ },
+ "Response" : {
+ "x-ms-version" : "2020-02-10",
+ "Server" : "Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0",
+ "x-ms-content-crc64" : "6RYQPwaVsyQ=",
+ "Last-Modified" : "Wed, 18 Nov 2020 20:53:50 GMT",
+ "x-ms-version-id" : "2020-11-18T20:53:50.0957813Z",
+ "retry-after" : "0",
+ "StatusCode" : "201",
+ "x-ms-request-server-encrypted" : "true",
+ "Date" : "Wed, 18 Nov 2020 20:53:50 GMT",
+ "Content-MD5" : "wh+Wm18D0z1D4E+PE252gg==",
+ "ETag" : "0x8D88C040D212075",
+ "Content-Length" : "0",
+ "x-ms-request-id" : "5a0b9c51-c01e-0073-69ec-bd7d3a000000",
+ "x-ms-client-request-id" : "e98643a2-72d7-4436-89f2-2cdac3d230c2"
+ },
+ "Exception" : null
+ }, {
+ "Method" : "GET",
+ "Uri" : "https://REDACTED.blob.core.windows.net/jtclistblobsprefixwithcomma097102c4f575e414a74be?prefix=javabloblistblobsprefixwithcomma199461a7d43fde3c03%2C%20javabloblistblobsprefixwithcomma235482222869197945&restype=container&comp=list",
+ "Headers" : {
+ "x-ms-version" : "2020-02-10",
+ "User-Agent" : "azsdk-java-azure-storage-blob/12.10.0-beta.1 (11.0.7; Windows 10; 10.0)",
+ "x-ms-client-request-id" : "3e79fbb3-6e16-43ac-b977-91dab3004419"
+ },
+ "Response" : {
+ "Transfer-Encoding" : "chunked",
+ "x-ms-version" : "2020-02-10",
+ "Server" : "Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0",
+ "retry-after" : "0",
+ "StatusCode" : "200",
+ "x-ms-request-id" : "5a0b9c60-c01e-0073-74ec-bd7d3a000000",
+ "Body" : "javabloblistblobsprefixwithcomma199461a7d43fde3c03, javabloblistblobsprefixwithcomma235482222869197945javabloblistblobsprefixwithcomma199461a7d43fde3c03, javabloblistblobsprefixwithcomma2354822228691979452020-11-18T20:53:50.0957813ZtrueWed, 18 Nov 2020 20:53:50 GMTWed, 18 Nov 2020 20:53:50 GMT0x8D88C040D2120757application/octet-streamwh+Wm18D0z1D4E+PE252gg==Wed, 18 Nov 2020 20:53:50 GMTBlockBlobHottrueunlockedavailabletrue",
+ "Date" : "Wed, 18 Nov 2020 20:53:50 GMT",
+ "x-ms-client-request-id" : "3e79fbb3-6e16-43ac-b977-91dab3004419",
+ "Content-Type" : "application/xml"
+ },
+ "Exception" : null
+ }, {
+ "Method" : "GET",
+ "Uri" : "https://REDACTED.blob.core.windows.net?prefix=jtclistblobsprefixwithcomma&comp=list",
+ "Headers" : {
+ "x-ms-version" : "2020-02-10",
+ "User-Agent" : "azsdk-java-azure-storage-blob/12.10.0-beta.1 (11.0.7; Windows 10; 10.0)",
+ "x-ms-client-request-id" : "3e01836f-8769-45a6-926e-d3df8cd56a2c"
+ },
+ "Response" : {
+ "Transfer-Encoding" : "chunked",
+ "x-ms-version" : "2020-02-10",
+ "Server" : "Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0",
+ "retry-after" : "0",
+ "StatusCode" : "200",
+ "x-ms-request-id" : "5a0b9c6a-c01e-0073-7bec-bd7d3a000000",
+ "Body" : "jtclistblobsprefixwithcommajtclistblobsprefixwithcomma097102c4f575e414a74beWed, 18 Nov 2020 20:53:49 GMT\"0x8D88C040CD150A7\"unlockedavailable$account-encryption-keyfalsefalsefalse",
+ "Date" : "Wed, 18 Nov 2020 20:53:50 GMT",
+ "x-ms-client-request-id" : "3e01836f-8769-45a6-926e-d3df8cd56a2c",
+ "Content-Type" : "application/xml"
+ },
+ "Exception" : null
+ }, {
+ "Method" : "DELETE",
+ "Uri" : "https://REDACTED.blob.core.windows.net/jtclistblobsprefixwithcomma097102c4f575e414a74be?restype=container",
+ "Headers" : {
+ "x-ms-version" : "2020-02-10",
+ "User-Agent" : "azsdk-java-azure-storage-blob/12.10.0-beta.1 (11.0.7; Windows 10; 10.0)",
+ "x-ms-client-request-id" : "1bce1177-17a1-4aa4-b4b2-a6b84369685c"
+ },
+ "Response" : {
+ "x-ms-version" : "2020-02-10",
+ "Server" : "Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0",
+ "retry-after" : "0",
+ "Content-Length" : "0",
+ "StatusCode" : "202",
+ "x-ms-request-id" : "5a0b9c78-c01e-0073-04ec-bd7d3a000000",
+ "Date" : "Wed, 18 Nov 2020 20:53:50 GMT",
+ "x-ms-client-request-id" : "1bce1177-17a1-4aa4-b4b2-a6b84369685c"
+ },
+ "Exception" : null
+ } ],
+ "variables" : [ "jtclistblobsprefixwithcomma097102c4f575e414a74be", "javabloblistblobsprefixwithcomma199461a7d43fde3c03", "javabloblistblobsprefixwithcomma235482222869197945" ]
+}
\ No newline at end of file
diff --git a/sdk/storage/azure-storage-common/CHANGELOG.md b/sdk/storage/azure-storage-common/CHANGELOG.md
index 73074886b777..217ee9a2978d 100644
--- a/sdk/storage/azure-storage-common/CHANGELOG.md
+++ b/sdk/storage/azure-storage-common/CHANGELOG.md
@@ -2,6 +2,7 @@
## 12.10.0-beta.1 (Unreleased)
- Added ability to specify timeout units in RequestRetryOptions.
+- Fixed bug where query params were being parsed incorrectly if an encoded comma was the query value.
## 12.9.0 (2020-11-11)
- GA release
diff --git a/sdk/storage/azure-storage-common/src/main/java/com/azure/storage/common/implementation/StorageImplUtils.java b/sdk/storage/azure-storage-common/src/main/java/com/azure/storage/common/implementation/StorageImplUtils.java
index 86b46678d915..121e8916b8cc 100644
--- a/sdk/storage/azure-storage-common/src/main/java/com/azure/storage/common/implementation/StorageImplUtils.java
+++ b/sdk/storage/azure-storage-common/src/main/java/com/azure/storage/common/implementation/StorageImplUtils.java
@@ -63,7 +63,18 @@ public static Map parseQueryString(final String queryString) {
* @return a mapping of query string pieces as key-value pairs.
*/
public static Map parseQueryStringSplitValues(final String queryString) {
- return parseQueryStringHelper(queryString, (value) -> urlDecode(value).split(","));
+ // We need to first split by comma and then decode each piece since we don't want to confuse legitimate separate
+ // query values from query values that container a comma.
+ // Example 1: prefix=a%2cb => prefix={decode(a%2cb)} => prefix={"a,b"}
+ // Example 2: prefix=a,b => prefix={decode(a),decode(b)} => prefix={"a", "b"}
+ return parseQueryStringHelper(queryString, value -> {
+ String[] v = value.split(",");
+ String[] ret = new String[v.length];
+ for (int i = 0; i < v.length; i++) {
+ ret[i] = urlDecode(v[i]);
+ }
+ return ret;
+ });
}
private static Map parseQueryStringHelper(final String queryString,