Skip to content

Commit 63030e0

Browse files
authored
URL-encode blob name parameters (Azure#19670)
* URL-encode blob name parameters * add targeted tests * update recordings
1 parent c8c9838 commit 63030e0

File tree

6 files changed

+393
-370
lines changed

6 files changed

+393
-370
lines changed

sdk/storage/azblob/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
* Corrected signing of User Delegation SAS. Fixes [#19372](https://github.com/Azure/azure-sdk-for-go/issues/19372) and [#19454](https://github.com/Azure/azure-sdk-for-go/issues/19454)
2222
* Added formatting of start and expiry time in [SetAccessPolicy](https://learn.microsoft.com/rest/api/storageservices/set-container-acl#request-body). Fixes [#18712](https://github.com/Azure/azure-sdk-for-go/issues/18712)
2323
* Uploading block blobs larger than 256MB can fail in some cases with error `net/http: HTTP/1.x transport connection broken`.
24+
* Blob name parameters are URL-encoded before constructing the complete blob URL.
2425

2526
### Other Changes
2627

sdk/storage/azblob/container/client.go

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"context"
1111
"errors"
1212
"net/http"
13+
"net/url"
1314
"time"
1415

1516
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
@@ -106,41 +107,38 @@ func (c *Client) URL() string {
106107
return c.generated().Endpoint()
107108
}
108109

109-
// NewBlobClient creates a new BlobClient object by concatenating blobName to the end of
110-
// Client's URL. The new BlobClient uses the same request policy pipeline as the Client.
111-
// To change the pipeline, create the BlobClient and then call its WithPipeline method passing in the
112-
// desired pipeline object. Or, call this package's NewBlobClient instead of calling this object's
113-
// NewBlobClient method.
110+
// NewBlobClient creates a new blob.Client object by concatenating blobName to the end of
111+
// Client's URL. The blob name will be URL-encoded.
112+
// The new blob.Client uses the same request policy pipeline as this Client.
114113
func (c *Client) NewBlobClient(blobName string) *blob.Client {
114+
blobName = url.PathEscape(blobName)
115115
blobURL := runtime.JoinPaths(c.URL(), blobName)
116116
return (*blob.Client)(base.NewBlobClient(blobURL, c.generated().Pipeline(), c.sharedKey()))
117117
}
118118

119-
// NewAppendBlobClient creates a new AppendBlobURL object by concatenating blobName to the end of
120-
// Client's URL. The new AppendBlobURL uses the same request policy pipeline as the Client.
121-
// To change the pipeline, create the AppendBlobURL and then call its WithPipeline method passing in the
122-
// desired pipeline object. Or, call this package's NewAppendBlobClient instead of calling this object's
123-
// NewAppendBlobClient method.
119+
// NewAppendBlobClient creates a new appendblob.Client object by concatenating blobName to the end of
120+
// this Client's URL. The blob name will be URL-encoded.
121+
// The new appendblob.Client uses the same request policy pipeline as this Client.
124122
func (c *Client) NewAppendBlobClient(blobName string) *appendblob.Client {
123+
blobName = url.PathEscape(blobName)
125124
blobURL := runtime.JoinPaths(c.URL(), blobName)
126125
return (*appendblob.Client)(base.NewAppendBlobClient(blobURL, c.generated().Pipeline(), c.sharedKey()))
127126
}
128127

129-
// NewBlockBlobClient creates a new BlockBlobClient object by concatenating blobName to the end of
130-
// Client's URL. The new BlockBlobClient uses the same request policy pipeline as the Client.
131-
// To change the pipeline, create the BlockBlobClient and then call its WithPipeline method passing in the
132-
// desired pipeline object. Or, call this package's NewBlockBlobClient instead of calling this object's
133-
// NewBlockBlobClient method.
128+
// NewBlockBlobClient creates a new blockblob.Client object by concatenating blobName to the end of
129+
// this Client's URL. The blob name will be URL-encoded.
130+
// The new blockblob.Client uses the same request policy pipeline as this Client.
134131
func (c *Client) NewBlockBlobClient(blobName string) *blockblob.Client {
132+
blobName = url.PathEscape(blobName)
135133
blobURL := runtime.JoinPaths(c.URL(), blobName)
136134
return (*blockblob.Client)(base.NewBlockBlobClient(blobURL, c.generated().Pipeline(), c.sharedKey()))
137135
}
138136

139-
// NewPageBlobClient creates a new PageBlobURL object by concatenating blobName to the end of Client's URL. The new PageBlobURL uses the same request policy pipeline as the Client.
140-
// To change the pipeline, create the PageBlobURL and then call its WithPipeline method passing in the
141-
// desired pipeline object. Or, call this package's NewPageBlobClient instead of calling this object's
142-
// NewPageBlobClient method.
137+
// NewPageBlobClient creates a new pageblob.Client object by concatenating blobName to the end of
138+
// this Client's URL. The blob name will be URL-encoded.
139+
// The new pageblob.Client uses the same request policy pipeline as this Client.
143140
func (c *Client) NewPageBlobClient(blobName string) *pageblob.Client {
141+
blobName = url.PathEscape(blobName)
144142
blobURL := runtime.JoinPaths(c.URL(), blobName)
145143
return (*pageblob.Client)(base.NewPageBlobClient(blobURL, c.generated().Pipeline(), c.sharedKey()))
146144
}

sdk/storage/azblob/container/client_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package container_test
99
import (
1010
"context"
1111
"fmt"
12+
"net/url"
1213
"sort"
1314
"strconv"
1415
"strings"
@@ -2156,3 +2157,29 @@ func (s *ContainerRecordedTestsSuite) TestSetAccessPolicyWithNullId() {
21562157
_require.Nil(err)
21572158
_require.Len(resp.SignedIdentifiers, 0)
21582159
}
2160+
2161+
func (s *ContainerUnrecordedTestsSuite) TestBlobNameSpecialCharacters() {
2162+
_require := require.New(s.T())
2163+
2164+
const containerURL = testcommon.FakeStorageURL + "/fakecontainer"
2165+
client, err := container.NewClientWithNoCredential(containerURL, nil)
2166+
_require.NoError(err)
2167+
_require.NotNil(client)
2168+
2169+
blobNames := []string{"foo%5Cbar", "hello? sausage/Hello.txt", "世界.txt"}
2170+
for _, blobName := range blobNames {
2171+
expected := containerURL + "/" + url.PathEscape(blobName)
2172+
2173+
abc := client.NewAppendBlobClient(blobName)
2174+
_require.Equal(expected, abc.URL())
2175+
2176+
bbc := client.NewBlockBlobClient(blobName)
2177+
_require.Equal(expected, bbc.URL())
2178+
2179+
pbc := client.NewPageBlobClient(blobName)
2180+
_require.Equal(expected, pbc.URL())
2181+
2182+
bc := client.NewBlobClient(blobName)
2183+
_require.Equal(expected, bc.URL())
2184+
}
2185+
}

sdk/storage/azblob/service/client.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,8 @@ func (s *Client) URL() string {
120120
return s.generated().Endpoint()
121121
}
122122

123-
// NewContainerClient creates a new ContainerClient object by concatenating containerName to the end of
124-
// Client's URL. The new ContainerClient uses the same request policy pipeline as the Client.
125-
// To change the pipeline, create the ContainerClient and then call its WithPipeline method passing in the
126-
// desired pipeline object. Or, call this package's NewContainerClient instead of calling this object's
127-
// NewContainerClient method.
123+
// NewContainerClient creates a new container.Client object by concatenating containerName to the end of
124+
// this Client's URL. The new container.Client uses the same request policy pipeline as the Client.
128125
func (s *Client) NewContainerClient(containerName string) *container.Client {
129126
containerURL := runtime.JoinPaths(s.generated().Endpoint(), containerName)
130127
return (*container.Client)(base.NewContainerClient(containerURL, s.generated().Pipeline(), s.sharedKey()))

0 commit comments

Comments
 (0)