Skip to content

Commit 30f5932

Browse files
authored
Merge pull request #169 from AtOMiCNebula/jdweiner/Fix-SASTimes-Merge
Fix Go SDK handling of SAS timestamps
2 parents 9f0641b + 295789a commit 30f5932

File tree

2 files changed

+96
-6
lines changed

2 files changed

+96
-6
lines changed

azblob/zc_sas_query_params.go

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package azblob
22

33
import (
4+
"errors"
45
"net"
56
"net/url"
67
"strings"
@@ -25,11 +26,11 @@ const (
2526
func FormatTimesForSASSigning(startTime, expiryTime, snapshotTime time.Time) (string, string, string) {
2627
ss := ""
2728
if !startTime.IsZero() {
28-
ss = startTime.Format(SASTimeFormat) // "yyyy-MM-ddTHH:mm:ssZ"
29+
ss = formatSASTimeWithDefaultFormat(&startTime)
2930
}
3031
se := ""
3132
if !expiryTime.IsZero() {
32-
se = expiryTime.Format(SASTimeFormat) // "yyyy-MM-ddTHH:mm:ssZ"
33+
se = formatSASTimeWithDefaultFormat(&expiryTime)
3334
}
3435
sh := ""
3536
if !snapshotTime.IsZero() {
@@ -40,6 +41,37 @@ func FormatTimesForSASSigning(startTime, expiryTime, snapshotTime time.Time) (st
4041

4142
// SASTimeFormat represents the format of a SAS start or expiry time. Use it when formatting/parsing a time.Time.
4243
const SASTimeFormat = "2006-01-02T15:04:05Z" //"2017-07-27T00:00:00Z" // ISO 8601
44+
var SASTimeFormats = []string{"2006-01-02T15:04:05.0000000Z", SASTimeFormat, "2006-01-02T15:04Z", "2006-01-02"} // ISO 8601 formats, please refer to https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas for more details.
45+
46+
// formatSASTimeWithDefaultFormat format time with ISO 8601 in "yyyy-MM-ddTHH:mm:ssZ".
47+
func formatSASTimeWithDefaultFormat(t *time.Time) string {
48+
return formatSASTime(t, SASTimeFormat) // By default, "yyyy-MM-ddTHH:mm:ssZ" is used
49+
}
50+
51+
// formatSASTime format time with given format, use ISO 8601 in "yyyy-MM-ddTHH:mm:ssZ" by default.
52+
func formatSASTime(t *time.Time, format string) string {
53+
if format != "" {
54+
return t.Format(format)
55+
}
56+
return t.Format(SASTimeFormat) // By default, "yyyy-MM-ddTHH:mm:ssZ" is used
57+
}
58+
59+
// parseSASTimeString try to parse sas time string.
60+
func parseSASTimeString(val string) (t time.Time, timeFormat string, err error) {
61+
for _, sasTimeFormat := range SASTimeFormats {
62+
t, err = time.Parse(sasTimeFormat, val)
63+
if err == nil {
64+
timeFormat = sasTimeFormat
65+
break
66+
}
67+
}
68+
69+
if err != nil {
70+
err = errors.New("fail to parse time with IOS 8601 formats, please refer to https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas for more details")
71+
}
72+
73+
return
74+
}
4375

4476
// https://docs.microsoft.com/en-us/rest/api/storageservices/constructing-a-service-sas
4577

@@ -74,6 +106,10 @@ type SASQueryParameters struct {
74106
signedExpiry time.Time `param:"ske"`
75107
signedService string `param:"sks"`
76108
signedVersion string `param:"skv"`
109+
110+
// private member used for startTime and expiryTime formatting.
111+
stTimeFormat string
112+
seTimeFormat string
77113
}
78114

79115
func (p *SASQueryParameters) SignedOid() string {
@@ -202,9 +238,9 @@ func newSASQueryParameters(values url.Values, deleteSASParametersFromValues bool
202238
case "snapshot":
203239
p.snapshotTime, _ = time.Parse(SnapshotTimeFormat, val)
204240
case "st":
205-
p.startTime, _ = time.Parse(SASTimeFormat, val)
241+
p.startTime, p.stTimeFormat, _ = parseSASTimeString(val)
206242
case "se":
207-
p.expiryTime, _ = time.Parse(SASTimeFormat, val)
243+
p.expiryTime, p.seTimeFormat, _ = parseSASTimeString(val)
208244
case "sip":
209245
dashIndex := strings.Index(val, "-")
210246
if dashIndex == -1 {
@@ -268,10 +304,10 @@ func (p *SASQueryParameters) addToValues(v url.Values) url.Values {
268304
v.Add("spr", string(p.protocol))
269305
}
270306
if !p.startTime.IsZero() {
271-
v.Add("st", p.startTime.Format(SASTimeFormat))
307+
v.Add("st", formatSASTime(&(p.startTime), p.stTimeFormat))
272308
}
273309
if !p.expiryTime.IsZero() {
274-
v.Add("se", p.expiryTime.Format(SASTimeFormat))
310+
v.Add("se", formatSASTime(&(p.expiryTime), p.seTimeFormat))
275311
}
276312
if len(p.ipRange.Start) > 0 {
277313
v.Add("sip", p.ipRange.String())

azblob/zt_url_blob_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"crypto/md5"
66
"errors"
77
"io/ioutil"
8+
"net/url"
89
"os"
910
"strings"
1011
"time"
@@ -1891,3 +1892,56 @@ func (s *aztestsSuite) TestBlobTierInvalidValue(c *chk.C) {
18911892
_, err = blobURL.SetTier(ctx, AccessTierType("garbage"), LeaseAccessConditions{})
18921893
validateStorageError(c, err, ServiceCodeInvalidHeaderValue)
18931894
}
1895+
1896+
func (s *aztestsSuite) TestBlobURLPartsSASQueryTimes(c *chk.C) {
1897+
StartTimesInputs := []string{
1898+
"2020-04-20",
1899+
"2020-04-20T07:00Z",
1900+
"2020-04-20T07:15:00Z",
1901+
"2020-04-20T07:30:00.1234567Z",
1902+
}
1903+
StartTimesExpected := []time.Time{
1904+
time.Date(2020, time.April, 20, 0, 0, 0, 0, time.UTC),
1905+
time.Date(2020, time.April, 20, 7, 0, 0, 0, time.UTC),
1906+
time.Date(2020, time.April, 20, 7, 15, 0, 0, time.UTC),
1907+
time.Date(2020, time.April, 20, 7, 30, 0, 123456700, time.UTC),
1908+
}
1909+
ExpiryTimesInputs := []string{
1910+
"2020-04-21",
1911+
"2020-04-20T08:00Z",
1912+
"2020-04-20T08:15:00Z",
1913+
"2020-04-20T08:30:00.2345678Z",
1914+
}
1915+
ExpiryTimesExpected := []time.Time{
1916+
time.Date(2020, time.April, 21, 0, 0, 0, 0, time.UTC),
1917+
time.Date(2020, time.April, 20, 8, 0, 0, 0, time.UTC),
1918+
time.Date(2020, time.April, 20, 8, 15, 0, 0, time.UTC),
1919+
time.Date(2020, time.April, 20, 8, 30, 0, 234567800, time.UTC),
1920+
}
1921+
1922+
for i := 0; i < len(StartTimesInputs); i++ {
1923+
urlString :=
1924+
"https://myaccount.blob.core.windows.net/mycontainer/mydirectory/myfile.txt?" +
1925+
"se=" + url.QueryEscape(ExpiryTimesInputs[i]) + "&" +
1926+
"sig=NotASignature&" +
1927+
"sp=r&" +
1928+
"spr=https&" +
1929+
"sr=b&" +
1930+
"st=" + url.QueryEscape(StartTimesInputs[i]) + "&" +
1931+
"sv=2019-10-10"
1932+
url, _ := url.Parse(urlString)
1933+
1934+
parts := NewBlobURLParts(*url)
1935+
c.Assert(parts.Scheme, chk.Equals, "https")
1936+
c.Assert(parts.Host, chk.Equals, "myaccount.blob.core.windows.net")
1937+
c.Assert(parts.ContainerName, chk.Equals, "mycontainer")
1938+
c.Assert(parts.BlobName, chk.Equals, "mydirectory/myfile.txt")
1939+
1940+
sas := parts.SAS
1941+
c.Assert(sas.StartTime(), chk.Equals, StartTimesExpected[i])
1942+
c.Assert(sas.ExpiryTime(), chk.Equals, ExpiryTimesExpected[i])
1943+
1944+
uResult := parts.URL()
1945+
c.Assert(uResult.String(), chk.Equals, urlString)
1946+
}
1947+
}

0 commit comments

Comments
 (0)