storage: V4 signature query params and encoding.
Add conformance tests and fix formatting and encoding to
align with standard. Add query parameters to SignedURLOptions.
Fixes #1744
Fixes #1747
Change-Id: Iabd509d7444e47c21f825d4e8dff32fbb2558a9f
Reviewed-on: https://code-review.googlesource.com/c/gocloud/+/52070
Reviewed-by: Tyler Bui-Palsulich <tbp@google.com>
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Frank Natividad <franknatividad@google.com>
diff --git a/storage/conformance_test.go b/storage/conformance_test.go
index d102ca6..c2fbaa9 100644
--- a/storage/conformance_test.go
+++ b/storage/conformance_test.go
@@ -73,13 +73,21 @@
return time.Unix(tc.Timestamp.Seconds, 0).UTC()
}
+ qp := url.Values{}
+ if tc.QueryParameters != nil {
+ for k, v := range tc.QueryParameters {
+ qp.Add(k, v)
+ }
+ }
+
gotURL, err := SignedURL(tc.Bucket, tc.Object, &SignedURLOptions{
- GoogleAccessID: googleAccessID,
- PrivateKey: []byte(privateKey),
- Method: tc.Method,
- Expires: utcNow().Add(time.Duration(tc.Expiration) * time.Second),
- Scheme: SigningSchemeV4,
- Headers: headersAsSlice(tc.Headers),
+ GoogleAccessID: googleAccessID,
+ PrivateKey: []byte(privateKey),
+ Method: tc.Method,
+ Expires: utcNow().Add(time.Duration(tc.Expiration) * time.Second),
+ Scheme: SigningSchemeV4,
+ Headers: headersAsSlice(tc.Headers),
+ QueryParameters: qp,
})
if err != nil {
t.Fatal(err)
diff --git a/storage/internal/test/conformance/test.pb.go b/storage/internal/test/conformance/test.pb.go
index b18dc05..e13d0ca 100644
--- a/storage/internal/test/conformance/test.pb.go
+++ b/storage/internal/test/conformance/test.pb.go
@@ -1,15 +1,13 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: test.proto
-package storage_v1_tests
+package google_cloud_conformance_storage_v1
import (
fmt "fmt"
-
- proto "github.com/golang/protobuf/proto"
-
math "math"
+ proto "github.com/golang/protobuf/proto"
timestamp "github.com/golang/protobuf/ptypes/timestamp"
)
@@ -22,29 +20,59 @@
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
-const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
+type SigningV4Test_UrlStyle int32
+
+const (
+ SigningV4Test_PATH_STYLE SigningV4Test_UrlStyle = 0
+ SigningV4Test_VIRTUAL_HOSTED_STYLE SigningV4Test_UrlStyle = 1
+ SigningV4Test_BUCKET_BOUND_DOMAIN SigningV4Test_UrlStyle = 2
+)
+
+var SigningV4Test_UrlStyle_name = map[int32]string{
+ 0: "PATH_STYLE",
+ 1: "VIRTUAL_HOSTED_STYLE",
+ 2: "BUCKET_BOUND_DOMAIN",
+}
+
+var SigningV4Test_UrlStyle_value = map[string]int32{
+ "PATH_STYLE": 0,
+ "VIRTUAL_HOSTED_STYLE": 1,
+ "BUCKET_BOUND_DOMAIN": 2,
+}
+
+func (x SigningV4Test_UrlStyle) String() string {
+ return proto.EnumName(SigningV4Test_UrlStyle_name, int32(x))
+}
+
+func (SigningV4Test_UrlStyle) EnumDescriptor() ([]byte, []int) {
+ return fileDescriptor_c161fcfdc0c3ff1e, []int{1, 0}
+}
type TestFile struct {
- SigningV4Tests []*SigningV4Test `protobuf:"bytes,1,rep,name=signing_v4_tests,json=signingV4Tests,proto3" json:"signing_v4_tests,omitempty"`
- XXX_NoUnkeyedLiteral struct{} `json:"-"`
- XXX_unrecognized []byte `json:"-"`
- XXX_sizecache int32 `json:"-"`
+ SigningV4Tests []*SigningV4Test `protobuf:"bytes,1,rep,name=signing_v4_tests,json=signingV4Tests,proto3" json:"signing_v4_tests,omitempty"`
+ PostPolicyV4Tests []*PostPolicyV4Test `protobuf:"bytes,2,rep,name=post_policy_v4_tests,json=postPolicyV4Tests,proto3" json:"post_policy_v4_tests,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
}
func (m *TestFile) Reset() { *m = TestFile{} }
func (m *TestFile) String() string { return proto.CompactTextString(m) }
func (*TestFile) ProtoMessage() {}
func (*TestFile) Descriptor() ([]byte, []int) {
- return fileDescriptor_test_7c0b908fadc4a1ba, []int{0}
+ return fileDescriptor_c161fcfdc0c3ff1e, []int{0}
}
+
func (m *TestFile) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_TestFile.Unmarshal(m, b)
}
func (m *TestFile) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_TestFile.Marshal(b, m, deterministic)
}
-func (dst *TestFile) XXX_Merge(src proto.Message) {
- xxx_messageInfo_TestFile.Merge(dst, src)
+func (m *TestFile) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_TestFile.Merge(m, src)
}
func (m *TestFile) XXX_Size() int {
return xxx_messageInfo_TestFile.Size(m)
@@ -62,35 +90,49 @@
return nil
}
+func (m *TestFile) GetPostPolicyV4Tests() []*PostPolicyV4Test {
+ if m != nil {
+ return m.PostPolicyV4Tests
+ }
+ return nil
+}
+
type SigningV4Test struct {
- FileName string `protobuf:"bytes,1,opt,name=fileName,proto3" json:"fileName,omitempty"`
- Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"`
- Bucket string `protobuf:"bytes,3,opt,name=bucket,proto3" json:"bucket,omitempty"`
- Object string `protobuf:"bytes,4,opt,name=object,proto3" json:"object,omitempty"`
- Method string `protobuf:"bytes,5,opt,name=method,proto3" json:"method,omitempty"`
- Expiration int64 `protobuf:"varint,6,opt,name=expiration,proto3" json:"expiration,omitempty"`
- Timestamp *timestamp.Timestamp `protobuf:"bytes,7,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
- ExpectedUrl string `protobuf:"bytes,8,opt,name=expectedUrl,proto3" json:"expectedUrl,omitempty"`
- Headers map[string]string `protobuf:"bytes,9,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
- XXX_NoUnkeyedLiteral struct{} `json:"-"`
- XXX_unrecognized []byte `json:"-"`
- XXX_sizecache int32 `json:"-"`
+ FileName string `protobuf:"bytes,1,opt,name=fileName,proto3" json:"fileName,omitempty"`
+ Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"`
+ Bucket string `protobuf:"bytes,3,opt,name=bucket,proto3" json:"bucket,omitempty"`
+ Object string `protobuf:"bytes,4,opt,name=object,proto3" json:"object,omitempty"`
+ Method string `protobuf:"bytes,5,opt,name=method,proto3" json:"method,omitempty"`
+ Expiration int64 `protobuf:"varint,6,opt,name=expiration,proto3" json:"expiration,omitempty"`
+ Timestamp *timestamp.Timestamp `protobuf:"bytes,7,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
+ ExpectedUrl string `protobuf:"bytes,8,opt,name=expectedUrl,proto3" json:"expectedUrl,omitempty"`
+ Headers map[string]string `protobuf:"bytes,9,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+ QueryParameters map[string]string `protobuf:"bytes,10,rep,name=query_parameters,json=queryParameters,proto3" json:"query_parameters,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+ Scheme string `protobuf:"bytes,11,opt,name=scheme,proto3" json:"scheme,omitempty"`
+ UrlStyle SigningV4Test_UrlStyle `protobuf:"varint,12,opt,name=urlStyle,proto3,enum=google.cloud.conformance.storage.v1.SigningV4Test_UrlStyle" json:"urlStyle,omitempty"`
+ BucketBoundDomain string `protobuf:"bytes,13,opt,name=bucketBoundDomain,proto3" json:"bucketBoundDomain,omitempty"`
+ ExpectedCanonicalRequest string `protobuf:"bytes,14,opt,name=expectedCanonicalRequest,proto3" json:"expectedCanonicalRequest,omitempty"`
+ ExpectedStringToSign string `protobuf:"bytes,15,opt,name=expectedStringToSign,proto3" json:"expectedStringToSign,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
}
func (m *SigningV4Test) Reset() { *m = SigningV4Test{} }
func (m *SigningV4Test) String() string { return proto.CompactTextString(m) }
func (*SigningV4Test) ProtoMessage() {}
func (*SigningV4Test) Descriptor() ([]byte, []int) {
- return fileDescriptor_test_7c0b908fadc4a1ba, []int{1}
+ return fileDescriptor_c161fcfdc0c3ff1e, []int{1}
}
+
func (m *SigningV4Test) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_SigningV4Test.Unmarshal(m, b)
}
func (m *SigningV4Test) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_SigningV4Test.Marshal(b, m, deterministic)
}
-func (dst *SigningV4Test) XXX_Merge(src proto.Message) {
- xxx_messageInfo_SigningV4Test.Merge(dst, src)
+func (m *SigningV4Test) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_SigningV4Test.Merge(m, src)
}
func (m *SigningV4Test) XXX_Size() int {
return xxx_messageInfo_SigningV4Test.Size(m)
@@ -164,36 +206,422 @@
return nil
}
-func init() {
- proto.RegisterType((*TestFile)(nil), "storage.v1.tests.TestFile")
- proto.RegisterType((*SigningV4Test)(nil), "storage.v1.tests.SigningV4Test")
- proto.RegisterMapType((map[string]string)(nil), "storage.v1.tests.SigningV4Test.HeadersEntry")
+func (m *SigningV4Test) GetQueryParameters() map[string]string {
+ if m != nil {
+ return m.QueryParameters
+ }
+ return nil
}
-func init() { proto.RegisterFile("test.proto", fileDescriptor_test_7c0b908fadc4a1ba) }
+func (m *SigningV4Test) GetScheme() string {
+ if m != nil {
+ return m.Scheme
+ }
+ return ""
+}
-var fileDescriptor_test_7c0b908fadc4a1ba = []byte{
- // 337 bytes of a gzipped FileDescriptorProto
- 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x90, 0x4d, 0x4f, 0xf2, 0x40,
- 0x14, 0x85, 0x53, 0xfa, 0xf2, 0x75, 0x79, 0x35, 0x64, 0x62, 0xcc, 0xa4, 0x0b, 0x69, 0x58, 0x75,
- 0x61, 0x86, 0x88, 0x2c, 0x08, 0x7b, 0x89, 0x6e, 0x5c, 0x54, 0x70, 0x4b, 0xfa, 0x71, 0x29, 0x23,
- 0x6d, 0xa7, 0xe9, 0x0c, 0x04, 0x7e, 0x88, 0xff, 0xd7, 0x74, 0x06, 0xb0, 0xba, 0x71, 0x37, 0xe7,
- 0x39, 0xe7, 0xde, 0xc9, 0x3d, 0x00, 0x0a, 0xa5, 0x62, 0x45, 0x29, 0x94, 0x20, 0x7d, 0xa9, 0x44,
- 0x19, 0x24, 0xc8, 0xf6, 0x0f, 0xac, 0xc2, 0xd2, 0x19, 0x24, 0x42, 0x24, 0x29, 0x8e, 0xb4, 0x1f,
- 0xee, 0xd6, 0x23, 0xc5, 0x33, 0x94, 0x2a, 0xc8, 0x0a, 0x33, 0x32, 0x5c, 0x42, 0x67, 0x81, 0x52,
- 0xcd, 0x79, 0x8a, 0xe4, 0x05, 0xfa, 0x92, 0x27, 0x39, 0xcf, 0x93, 0xd5, 0x7e, 0xb2, 0xd2, 0x0b,
- 0xa8, 0xe5, 0xda, 0x5e, 0x6f, 0x3c, 0x60, 0xbf, 0x37, 0xb3, 0x37, 0x93, 0x7c, 0x9f, 0x54, 0xe3,
- 0xfe, 0xb5, 0xac, 0x4b, 0x39, 0xfc, 0xb4, 0xe1, 0xea, 0x47, 0x82, 0x38, 0xd0, 0x59, 0xf3, 0x14,
- 0x5f, 0x83, 0x0c, 0xa9, 0xe5, 0x5a, 0x5e, 0xd7, 0xbf, 0x68, 0xe2, 0x42, 0x2f, 0x46, 0x19, 0x95,
- 0xbc, 0x50, 0x5c, 0xe4, 0xb4, 0xa1, 0xed, 0x3a, 0x22, 0xb7, 0xd0, 0x0a, 0x77, 0xd1, 0x16, 0x15,
- 0xb5, 0xb5, 0x79, 0x52, 0x15, 0x17, 0xe1, 0x07, 0x46, 0x8a, 0xfe, 0x33, 0xdc, 0xa8, 0x8a, 0x67,
- 0xa8, 0x36, 0x22, 0xa6, 0x4d, 0xc3, 0x8d, 0x22, 0x77, 0x00, 0x78, 0x28, 0x78, 0x19, 0xe8, 0x8f,
- 0x5a, 0xae, 0xe5, 0xd9, 0x7e, 0x8d, 0x90, 0x29, 0x74, 0x2f, 0x0d, 0xd1, 0xb6, 0x6b, 0x79, 0xbd,
- 0xb1, 0xc3, 0x4c, 0x87, 0xec, 0xdc, 0x21, 0x5b, 0x9c, 0x13, 0xfe, 0x77, 0xb8, 0xba, 0x01, 0x0f,
- 0x05, 0x46, 0x0a, 0xe3, 0x65, 0x99, 0xd2, 0x8e, 0xb9, 0xa1, 0x86, 0xc8, 0x1c, 0xda, 0x1b, 0x0c,
- 0x62, 0x2c, 0x25, 0xed, 0xea, 0x56, 0xef, 0xff, 0x68, 0x95, 0x3d, 0x9b, 0xf8, 0x53, 0xae, 0xca,
- 0xa3, 0x7f, 0x1e, 0x76, 0x66, 0xf0, 0xbf, 0x6e, 0x90, 0x3e, 0xd8, 0x5b, 0x3c, 0x9e, 0x4a, 0xad,
- 0x9e, 0xe4, 0x06, 0x9a, 0xfb, 0x20, 0xdd, 0xe1, 0xa9, 0x49, 0x23, 0x66, 0x8d, 0xa9, 0x15, 0xb6,
- 0xf4, 0x11, 0x8f, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x36, 0x21, 0xc6, 0x48, 0x36, 0x02, 0x00,
- 0x00,
+func (m *SigningV4Test) GetUrlStyle() SigningV4Test_UrlStyle {
+ if m != nil {
+ return m.UrlStyle
+ }
+ return SigningV4Test_PATH_STYLE
+}
+
+func (m *SigningV4Test) GetBucketBoundDomain() string {
+ if m != nil {
+ return m.BucketBoundDomain
+ }
+ return ""
+}
+
+func (m *SigningV4Test) GetExpectedCanonicalRequest() string {
+ if m != nil {
+ return m.ExpectedCanonicalRequest
+ }
+ return ""
+}
+
+func (m *SigningV4Test) GetExpectedStringToSign() string {
+ if m != nil {
+ return m.ExpectedStringToSign
+ }
+ return ""
+}
+
+type ConditionalMatches struct {
+ Expression []string `protobuf:"bytes,1,rep,name=expression,proto3" json:"expression,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *ConditionalMatches) Reset() { *m = ConditionalMatches{} }
+func (m *ConditionalMatches) String() string { return proto.CompactTextString(m) }
+func (*ConditionalMatches) ProtoMessage() {}
+func (*ConditionalMatches) Descriptor() ([]byte, []int) {
+ return fileDescriptor_c161fcfdc0c3ff1e, []int{2}
+}
+
+func (m *ConditionalMatches) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_ConditionalMatches.Unmarshal(m, b)
+}
+func (m *ConditionalMatches) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_ConditionalMatches.Marshal(b, m, deterministic)
+}
+func (m *ConditionalMatches) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_ConditionalMatches.Merge(m, src)
+}
+func (m *ConditionalMatches) XXX_Size() int {
+ return xxx_messageInfo_ConditionalMatches.Size(m)
+}
+func (m *ConditionalMatches) XXX_DiscardUnknown() {
+ xxx_messageInfo_ConditionalMatches.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ConditionalMatches proto.InternalMessageInfo
+
+func (m *ConditionalMatches) GetExpression() []string {
+ if m != nil {
+ return m.Expression
+ }
+ return nil
+}
+
+type PolicyConditions struct {
+ SuccessActionStatus string `protobuf:"bytes,1,opt,name=successActionStatus,proto3" json:"successActionStatus,omitempty"`
+ SuccessActionRedirect string `protobuf:"bytes,2,opt,name=successActionRedirect,proto3" json:"successActionRedirect,omitempty"`
+ Matches []*ConditionalMatches `protobuf:"bytes,3,rep,name=matches,proto3" json:"matches,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *PolicyConditions) Reset() { *m = PolicyConditions{} }
+func (m *PolicyConditions) String() string { return proto.CompactTextString(m) }
+func (*PolicyConditions) ProtoMessage() {}
+func (*PolicyConditions) Descriptor() ([]byte, []int) {
+ return fileDescriptor_c161fcfdc0c3ff1e, []int{3}
+}
+
+func (m *PolicyConditions) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_PolicyConditions.Unmarshal(m, b)
+}
+func (m *PolicyConditions) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_PolicyConditions.Marshal(b, m, deterministic)
+}
+func (m *PolicyConditions) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_PolicyConditions.Merge(m, src)
+}
+func (m *PolicyConditions) XXX_Size() int {
+ return xxx_messageInfo_PolicyConditions.Size(m)
+}
+func (m *PolicyConditions) XXX_DiscardUnknown() {
+ xxx_messageInfo_PolicyConditions.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_PolicyConditions proto.InternalMessageInfo
+
+func (m *PolicyConditions) GetSuccessActionStatus() string {
+ if m != nil {
+ return m.SuccessActionStatus
+ }
+ return ""
+}
+
+func (m *PolicyConditions) GetSuccessActionRedirect() string {
+ if m != nil {
+ return m.SuccessActionRedirect
+ }
+ return ""
+}
+
+func (m *PolicyConditions) GetMatches() []*ConditionalMatches {
+ if m != nil {
+ return m.Matches
+ }
+ return nil
+}
+
+type PolicyInput struct {
+ Scheme string `protobuf:"bytes,1,opt,name=scheme,proto3" json:"scheme,omitempty"`
+ Bucket string `protobuf:"bytes,2,opt,name=bucket,proto3" json:"bucket,omitempty"`
+ Object string `protobuf:"bytes,3,opt,name=object,proto3" json:"object,omitempty"`
+ Expiration int64 `protobuf:"varint,4,opt,name=expiration,proto3" json:"expiration,omitempty"`
+ Timestamp *timestamp.Timestamp `protobuf:"bytes,5,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
+ Headers map[string]string `protobuf:"bytes,6,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+ Conditions *PolicyConditions `protobuf:"bytes,7,opt,name=conditions,proto3" json:"conditions,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *PolicyInput) Reset() { *m = PolicyInput{} }
+func (m *PolicyInput) String() string { return proto.CompactTextString(m) }
+func (*PolicyInput) ProtoMessage() {}
+func (*PolicyInput) Descriptor() ([]byte, []int) {
+ return fileDescriptor_c161fcfdc0c3ff1e, []int{4}
+}
+
+func (m *PolicyInput) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_PolicyInput.Unmarshal(m, b)
+}
+func (m *PolicyInput) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_PolicyInput.Marshal(b, m, deterministic)
+}
+func (m *PolicyInput) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_PolicyInput.Merge(m, src)
+}
+func (m *PolicyInput) XXX_Size() int {
+ return xxx_messageInfo_PolicyInput.Size(m)
+}
+func (m *PolicyInput) XXX_DiscardUnknown() {
+ xxx_messageInfo_PolicyInput.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_PolicyInput proto.InternalMessageInfo
+
+func (m *PolicyInput) GetScheme() string {
+ if m != nil {
+ return m.Scheme
+ }
+ return ""
+}
+
+func (m *PolicyInput) GetBucket() string {
+ if m != nil {
+ return m.Bucket
+ }
+ return ""
+}
+
+func (m *PolicyInput) GetObject() string {
+ if m != nil {
+ return m.Object
+ }
+ return ""
+}
+
+func (m *PolicyInput) GetExpiration() int64 {
+ if m != nil {
+ return m.Expiration
+ }
+ return 0
+}
+
+func (m *PolicyInput) GetTimestamp() *timestamp.Timestamp {
+ if m != nil {
+ return m.Timestamp
+ }
+ return nil
+}
+
+func (m *PolicyInput) GetHeaders() map[string]string {
+ if m != nil {
+ return m.Headers
+ }
+ return nil
+}
+
+func (m *PolicyInput) GetConditions() *PolicyConditions {
+ if m != nil {
+ return m.Conditions
+ }
+ return nil
+}
+
+type PolicyOutput struct {
+ Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"`
+ Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"`
+ Fields map[string]string `protobuf:"bytes,3,rep,name=fields,proto3" json:"fields,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+ ExpectedDecodedPolicy string `protobuf:"bytes,4,opt,name=expectedDecodedPolicy,proto3" json:"expectedDecodedPolicy,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *PolicyOutput) Reset() { *m = PolicyOutput{} }
+func (m *PolicyOutput) String() string { return proto.CompactTextString(m) }
+func (*PolicyOutput) ProtoMessage() {}
+func (*PolicyOutput) Descriptor() ([]byte, []int) {
+ return fileDescriptor_c161fcfdc0c3ff1e, []int{5}
+}
+
+func (m *PolicyOutput) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_PolicyOutput.Unmarshal(m, b)
+}
+func (m *PolicyOutput) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_PolicyOutput.Marshal(b, m, deterministic)
+}
+func (m *PolicyOutput) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_PolicyOutput.Merge(m, src)
+}
+func (m *PolicyOutput) XXX_Size() int {
+ return xxx_messageInfo_PolicyOutput.Size(m)
+}
+func (m *PolicyOutput) XXX_DiscardUnknown() {
+ xxx_messageInfo_PolicyOutput.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_PolicyOutput proto.InternalMessageInfo
+
+func (m *PolicyOutput) GetUrl() string {
+ if m != nil {
+ return m.Url
+ }
+ return ""
+}
+
+func (m *PolicyOutput) GetKey() string {
+ if m != nil {
+ return m.Key
+ }
+ return ""
+}
+
+func (m *PolicyOutput) GetFields() map[string]string {
+ if m != nil {
+ return m.Fields
+ }
+ return nil
+}
+
+func (m *PolicyOutput) GetExpectedDecodedPolicy() string {
+ if m != nil {
+ return m.ExpectedDecodedPolicy
+ }
+ return ""
+}
+
+type PostPolicyV4Test struct {
+ Description string `protobuf:"bytes,1,opt,name=description,proto3" json:"description,omitempty"`
+ PolicyInput *PolicyInput `protobuf:"bytes,2,opt,name=policyInput,proto3" json:"policyInput,omitempty"`
+ PolicyOutput *PolicyOutput `protobuf:"bytes,3,opt,name=policyOutput,proto3" json:"policyOutput,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *PostPolicyV4Test) Reset() { *m = PostPolicyV4Test{} }
+func (m *PostPolicyV4Test) String() string { return proto.CompactTextString(m) }
+func (*PostPolicyV4Test) ProtoMessage() {}
+func (*PostPolicyV4Test) Descriptor() ([]byte, []int) {
+ return fileDescriptor_c161fcfdc0c3ff1e, []int{6}
+}
+
+func (m *PostPolicyV4Test) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_PostPolicyV4Test.Unmarshal(m, b)
+}
+func (m *PostPolicyV4Test) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_PostPolicyV4Test.Marshal(b, m, deterministic)
+}
+func (m *PostPolicyV4Test) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_PostPolicyV4Test.Merge(m, src)
+}
+func (m *PostPolicyV4Test) XXX_Size() int {
+ return xxx_messageInfo_PostPolicyV4Test.Size(m)
+}
+func (m *PostPolicyV4Test) XXX_DiscardUnknown() {
+ xxx_messageInfo_PostPolicyV4Test.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_PostPolicyV4Test proto.InternalMessageInfo
+
+func (m *PostPolicyV4Test) GetDescription() string {
+ if m != nil {
+ return m.Description
+ }
+ return ""
+}
+
+func (m *PostPolicyV4Test) GetPolicyInput() *PolicyInput {
+ if m != nil {
+ return m.PolicyInput
+ }
+ return nil
+}
+
+func (m *PostPolicyV4Test) GetPolicyOutput() *PolicyOutput {
+ if m != nil {
+ return m.PolicyOutput
+ }
+ return nil
+}
+
+func init() {
+ proto.RegisterEnum("google.cloud.conformance.storage.v1.SigningV4Test_UrlStyle", SigningV4Test_UrlStyle_name, SigningV4Test_UrlStyle_value)
+ proto.RegisterType((*TestFile)(nil), "google.cloud.conformance.storage.v1.TestFile")
+ proto.RegisterType((*SigningV4Test)(nil), "google.cloud.conformance.storage.v1.SigningV4Test")
+ proto.RegisterMapType((map[string]string)(nil), "google.cloud.conformance.storage.v1.SigningV4Test.HeadersEntry")
+ proto.RegisterMapType((map[string]string)(nil), "google.cloud.conformance.storage.v1.SigningV4Test.QueryParametersEntry")
+ proto.RegisterType((*ConditionalMatches)(nil), "google.cloud.conformance.storage.v1.ConditionalMatches")
+ proto.RegisterType((*PolicyConditions)(nil), "google.cloud.conformance.storage.v1.PolicyConditions")
+ proto.RegisterType((*PolicyInput)(nil), "google.cloud.conformance.storage.v1.PolicyInput")
+ proto.RegisterMapType((map[string]string)(nil), "google.cloud.conformance.storage.v1.PolicyInput.HeadersEntry")
+ proto.RegisterType((*PolicyOutput)(nil), "google.cloud.conformance.storage.v1.PolicyOutput")
+ proto.RegisterMapType((map[string]string)(nil), "google.cloud.conformance.storage.v1.PolicyOutput.FieldsEntry")
+ proto.RegisterType((*PostPolicyV4Test)(nil), "google.cloud.conformance.storage.v1.PostPolicyV4Test")
+}
+
+func init() { proto.RegisterFile("test.proto", fileDescriptor_c161fcfdc0c3ff1e) }
+
+var fileDescriptor_c161fcfdc0c3ff1e = []byte{
+ // 917 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x56, 0xef, 0x6e, 0xda, 0x56,
+ 0x14, 0x9f, 0xa1, 0x21, 0xe4, 0x38, 0x4d, 0xe9, 0x2d, 0xdb, 0x2c, 0x3e, 0x6c, 0x88, 0x7d, 0x18,
+ 0x93, 0x26, 0xb7, 0x61, 0xa9, 0xd6, 0x65, 0x9a, 0xa6, 0x10, 0xd2, 0x26, 0x5a, 0x93, 0x50, 0x03,
+ 0x89, 0x2a, 0x4d, 0x42, 0x8e, 0x7d, 0x00, 0xaf, 0xb6, 0xaf, 0xe3, 0x7b, 0x1d, 0x95, 0x67, 0xd9,
+ 0x1b, 0xec, 0x21, 0xf6, 0x06, 0x93, 0xf6, 0x06, 0x7b, 0x8f, 0x7d, 0x9a, 0xee, 0xbd, 0x36, 0x98,
+ 0x86, 0x4c, 0x90, 0x7e, 0xe3, 0x9e, 0x73, 0xcf, 0xef, 0xe7, 0xf3, 0xef, 0x77, 0x01, 0xe0, 0xc8,
+ 0xb8, 0x19, 0xc5, 0x94, 0x53, 0xf2, 0xd5, 0x98, 0xd2, 0xb1, 0x8f, 0xa6, 0xe3, 0xd3, 0xc4, 0x35,
+ 0x1d, 0x1a, 0x8e, 0x68, 0x1c, 0xd8, 0xa1, 0x83, 0x26, 0xe3, 0x34, 0xb6, 0xc7, 0x68, 0xde, 0xec,
+ 0xd6, 0xbe, 0x54, 0x97, 0x9e, 0xca, 0x90, 0xab, 0x64, 0xf4, 0x94, 0x7b, 0x01, 0x32, 0x6e, 0x07,
+ 0x91, 0x42, 0x69, 0xfc, 0xad, 0x41, 0xb9, 0x8f, 0x8c, 0xbf, 0xf4, 0x7c, 0x24, 0xbf, 0x42, 0x85,
+ 0x79, 0xe3, 0xd0, 0x0b, 0xc7, 0xc3, 0x9b, 0xbd, 0xa1, 0xe0, 0x62, 0x86, 0x56, 0x2f, 0x36, 0xf5,
+ 0x56, 0xcb, 0x5c, 0x81, 0xcd, 0xec, 0xa9, 0xe0, 0x8b, 0x3d, 0x81, 0x68, 0xed, 0xb0, 0xfc, 0x91,
+ 0x91, 0x11, 0x54, 0x23, 0xca, 0xf8, 0x30, 0xa2, 0xbe, 0xe7, 0x4c, 0xe7, 0x0c, 0x05, 0xc9, 0xf0,
+ 0x7c, 0x25, 0x86, 0x2e, 0x65, 0xbc, 0x2b, 0xe3, 0x53, 0x92, 0xc7, 0xd1, 0x07, 0x16, 0xd6, 0xf8,
+ 0x73, 0x13, 0x1e, 0x2e, 0x7c, 0x09, 0xa9, 0x41, 0x79, 0xe4, 0xf9, 0x78, 0x66, 0x07, 0x68, 0x68,
+ 0x75, 0xad, 0xb9, 0x65, 0xcd, 0xce, 0xa4, 0x0e, 0xba, 0x8b, 0xcc, 0x89, 0xbd, 0x88, 0x7b, 0x34,
+ 0x34, 0x0a, 0xd2, 0x9d, 0x37, 0x91, 0xcf, 0xa0, 0x74, 0x95, 0x38, 0xef, 0x90, 0x1b, 0x45, 0xe9,
+ 0x4c, 0x4f, 0xc2, 0x4e, 0xaf, 0x7e, 0x43, 0x87, 0x1b, 0x0f, 0x94, 0x5d, 0x9d, 0x84, 0x3d, 0x40,
+ 0x3e, 0xa1, 0xae, 0xb1, 0xa1, 0xec, 0xea, 0x44, 0xbe, 0x00, 0xc0, 0xf7, 0x91, 0x17, 0xdb, 0x92,
+ 0xa8, 0x54, 0xd7, 0x9a, 0x45, 0x2b, 0x67, 0x21, 0x2f, 0x60, 0x6b, 0xd6, 0x1d, 0x63, 0xb3, 0xae,
+ 0x35, 0xf5, 0x56, 0x2d, 0x2b, 0x4a, 0xd6, 0x3f, 0xb3, 0x9f, 0xdd, 0xb0, 0xe6, 0x97, 0x45, 0x0e,
+ 0xf8, 0x3e, 0x42, 0x87, 0xa3, 0x3b, 0x88, 0x7d, 0xa3, 0xac, 0x72, 0xc8, 0x99, 0xc8, 0x5b, 0xd8,
+ 0x9c, 0xa0, 0xed, 0x62, 0xcc, 0x8c, 0x2d, 0x59, 0xee, 0x9f, 0xd7, 0x6f, 0xa8, 0x79, 0xac, 0x10,
+ 0x8e, 0x42, 0x1e, 0x4f, 0xad, 0x0c, 0x8f, 0xc4, 0x50, 0xb9, 0x4e, 0x30, 0x9e, 0x0e, 0x23, 0x3b,
+ 0xb6, 0x03, 0xe4, 0x82, 0x03, 0x24, 0xc7, 0xab, 0x7b, 0x70, 0xbc, 0x11, 0x50, 0xdd, 0x19, 0x92,
+ 0xe2, 0x7a, 0x74, 0xbd, 0x68, 0x15, 0x25, 0x66, 0xce, 0x04, 0x03, 0x34, 0x74, 0x55, 0x62, 0x75,
+ 0x22, 0x97, 0x50, 0x4e, 0x62, 0xbf, 0xc7, 0xa7, 0x3e, 0x1a, 0xdb, 0x75, 0xad, 0xb9, 0xd3, 0xfa,
+ 0xf1, 0x1e, 0xdf, 0x30, 0x48, 0x21, 0xac, 0x19, 0x18, 0xf9, 0x16, 0x1e, 0xab, 0xae, 0xb7, 0x69,
+ 0x12, 0xba, 0x1d, 0x1a, 0xd8, 0x5e, 0x68, 0x3c, 0x94, 0xdc, 0xb7, 0x1d, 0x64, 0x1f, 0x8c, 0xac,
+ 0xf8, 0x87, 0x76, 0x48, 0x43, 0xcf, 0xb1, 0x7d, 0x0b, 0xaf, 0x13, 0x64, 0xdc, 0xd8, 0x91, 0x41,
+ 0x77, 0xfa, 0x49, 0x0b, 0xaa, 0x99, 0xaf, 0xc7, 0x63, 0x2f, 0x1c, 0xf7, 0xa9, 0xf8, 0x3a, 0xe3,
+ 0x91, 0x8c, 0x5b, 0xea, 0xab, 0xed, 0xc3, 0x76, 0xbe, 0x37, 0xa4, 0x02, 0xc5, 0x77, 0x38, 0x4d,
+ 0x47, 0x5d, 0xfc, 0x24, 0x55, 0xd8, 0xb8, 0xb1, 0xfd, 0x04, 0xd3, 0xf9, 0x56, 0x87, 0xfd, 0xc2,
+ 0x0b, 0xad, 0xd6, 0x86, 0xea, 0xb2, 0x9a, 0xaf, 0x83, 0xd1, 0x38, 0x85, 0x72, 0x56, 0x33, 0xb2,
+ 0x03, 0xd0, 0x3d, 0xe8, 0x1f, 0x0f, 0x7b, 0xfd, 0xb7, 0xaf, 0x8f, 0x2a, 0x9f, 0x10, 0x03, 0xaa,
+ 0x17, 0x27, 0x56, 0x7f, 0x70, 0xf0, 0x7a, 0x78, 0x7c, 0xde, 0xeb, 0x1f, 0x75, 0x52, 0x8f, 0x46,
+ 0x3e, 0x87, 0x27, 0xed, 0xc1, 0xe1, 0x2f, 0x47, 0xfd, 0x61, 0xfb, 0x7c, 0x70, 0xd6, 0x19, 0x76,
+ 0xce, 0x4f, 0x0f, 0x4e, 0xce, 0x2a, 0x85, 0xc6, 0x1e, 0x90, 0x43, 0x1a, 0xba, 0x9e, 0xd8, 0x0a,
+ 0xdb, 0x3f, 0xb5, 0xb9, 0x33, 0x41, 0x96, 0xae, 0x4f, 0x8c, 0x8c, 0x89, 0xf5, 0x11, 0xb2, 0xb4,
+ 0x65, 0xe5, 0x2c, 0x8d, 0xbf, 0x34, 0xa8, 0x28, 0x21, 0x98, 0x05, 0x33, 0xf2, 0x0c, 0x9e, 0xb0,
+ 0xc4, 0x71, 0x90, 0xb1, 0x03, 0x47, 0x58, 0x7a, 0xdc, 0xe6, 0x09, 0x4b, 0xb3, 0x5a, 0xe6, 0x22,
+ 0x7b, 0xf0, 0xe9, 0x82, 0xd9, 0x42, 0xd7, 0x8b, 0xc5, 0x92, 0xab, 0xac, 0x97, 0x3b, 0xc9, 0x1b,
+ 0xd8, 0x0c, 0xd4, 0x77, 0x1a, 0x45, 0x39, 0xfb, 0xdf, 0xaf, 0x34, 0x77, 0xb7, 0xd3, 0xb4, 0x32,
+ 0x9c, 0xc6, 0xef, 0x45, 0xd0, 0x55, 0x3e, 0x27, 0x61, 0x94, 0xf0, 0xdc, 0xcc, 0x6b, 0x0b, 0x33,
+ 0x3f, 0x97, 0xa7, 0xc2, 0x1d, 0xf2, 0x54, 0x5c, 0x90, 0xa7, 0x45, 0x19, 0x7a, 0xf0, 0xff, 0x32,
+ 0xb4, 0xb1, 0x8e, 0x0c, 0x5d, 0xce, 0x45, 0xa6, 0x24, 0x8b, 0xf0, 0xd3, 0x8a, 0x9a, 0x3e, 0x4b,
+ 0xf2, 0x0e, 0x89, 0x19, 0x00, 0x38, 0xb3, 0x9e, 0xa6, 0xd2, 0xf8, 0x7c, 0x0d, 0xec, 0xf9, 0x40,
+ 0x58, 0x39, 0xa0, 0x8f, 0x59, 0x9b, 0xc6, 0xbf, 0x1a, 0x6c, 0x2b, 0xf0, 0xf3, 0x84, 0x8b, 0xf6,
+ 0x54, 0xa0, 0x98, 0xc4, 0x7e, 0x16, 0x9c, 0xc4, 0x7e, 0x06, 0x57, 0x98, 0xc3, 0x0d, 0xa0, 0x34,
+ 0xf2, 0xd0, 0x77, 0xb3, 0x21, 0x59, 0xa7, 0x3e, 0x8a, 0xc6, 0x7c, 0x29, 0xe3, 0x55, 0x7d, 0x52,
+ 0x30, 0x31, 0xb2, 0x99, 0x2c, 0x74, 0xd0, 0xa1, 0x2e, 0xba, 0x2a, 0x24, 0x7d, 0x97, 0x96, 0x3b,
+ 0x6b, 0x3f, 0x80, 0x9e, 0x03, 0x5b, 0x2b, 0xf9, 0x7f, 0xe4, 0xaa, 0x2d, 0xbe, 0xbb, 0x1f, 0x3e,
+ 0xa4, 0xda, 0xed, 0x87, 0xd4, 0x02, 0x3d, 0x9a, 0xf7, 0x5a, 0xc2, 0xea, 0xad, 0x67, 0xeb, 0xce,
+ 0x88, 0x95, 0x07, 0x21, 0x03, 0xd8, 0x8e, 0x72, 0xf5, 0x91, 0xb3, 0xae, 0xb7, 0x76, 0xd7, 0x2e,
+ 0xac, 0xb5, 0x00, 0xd3, 0xbe, 0x84, 0xaf, 0x1d, 0x1a, 0xac, 0x82, 0xd2, 0xd5, 0xfe, 0x28, 0x7c,
+ 0xf3, 0x4a, 0xdd, 0x3b, 0x94, 0xf7, 0x7a, 0xa9, 0xef, 0x62, 0xd7, 0x94, 0x7f, 0x48, 0xc4, 0x96,
+ 0x67, 0x81, 0x57, 0x25, 0xb9, 0x42, 0xdf, 0xfd, 0x17, 0x00, 0x00, 0xff, 0xff, 0x0e, 0xac, 0xde,
+ 0xde, 0xca, 0x09, 0x00, 0x00,
}
diff --git a/storage/internal/test/conformance/test.proto b/storage/internal/test/conformance/test.proto
index 517cc81..b08611a 100644
--- a/storage/internal/test/conformance/test.proto
+++ b/storage/internal/test/conformance/test.proto
@@ -14,22 +14,71 @@
syntax = "proto3";
-package storage.v1.tests;
+package google.cloud.conformance.storage.v1;
import "google/protobuf/timestamp.proto";
+option csharp_namespace = "Google.Cloud.Storage.V1.Tests.Conformance";
+option java_package = "com.google.cloud.conformance.storage.v1";
+option java_multiple_files = true;
+
message TestFile {
- repeated SigningV4Test signing_v4_tests = 1;
+ repeated SigningV4Test signing_v4_tests = 1;
+ repeated PostPolicyV4Test post_policy_v4_tests = 2;
}
message SigningV4Test {
- string fileName = 1;
- string description = 2;
- string bucket = 3;
- string object = 4;
- string method = 5;
- int64 expiration = 6;
- google.protobuf.Timestamp timestamp = 7;
- string expectedUrl = 8;
- map<string, string> headers = 9;
+ string fileName = 1;
+ string description = 2;
+ string bucket = 3;
+ string object = 4;
+ string method = 5;
+ int64 expiration = 6;
+ google.protobuf.Timestamp timestamp = 7;
+ string expectedUrl = 8;
+ map<string, string> headers = 9;
+ map<string, string> query_parameters = 10;
+ string scheme = 11;
+ enum UrlStyle {
+ PATH_STYLE = 0;
+ VIRTUAL_HOSTED_STYLE = 1;
+ BUCKET_BOUND_DOMAIN = 2;
+ }
+ UrlStyle urlStyle = 12;
+ string bucketBoundDomain = 13;
+ string expectedCanonicalRequest = 14;
+ string expectedStringToSign = 15;
+}
+
+message ConditionalMatches {
+ repeated string expression = 1;
+}
+
+message PolicyConditions {
+ string successActionStatus = 1;
+ string successActionRedirect = 2;
+ repeated ConditionalMatches matches = 3;
+}
+
+message PolicyInput {
+ string scheme = 1;
+ string bucket = 2;
+ string object = 3;
+ int64 expiration = 4;
+ google.protobuf.Timestamp timestamp = 5;
+ map<string, string> headers = 6;
+ PolicyConditions conditions = 7;
+}
+
+message PolicyOutput {
+ string url = 1;
+ string key = 2;
+ map<string, string> fields = 3;
+ string expectedDecodedPolicy = 4;
+}
+
+message PostPolicyV4Test {
+ string description = 1;
+ PolicyInput policyInput = 2;
+ PolicyOutput policyOutput = 3;
}
diff --git a/storage/internal/test/conformance/v4_signatures.json b/storage/internal/test/conformance/v4_signatures.json
index 276510b..62d32a0 100644
--- a/storage/internal/test/conformance/v4_signatures.json
+++ b/storage/internal/test/conformance/v4_signatures.json
@@ -7,7 +7,10 @@
"method": "GET",
"expiration": "10",
"timestamp": "2019-02-01T09:00:00Z",
- "expectedUrl": "https://storage.googleapis.com/test-bucket/test-object?X-Goog-Algorithm=GOOG4-RSA-SHA256\u0026X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request\u0026X-Goog-Date=20190201T090000Z\u0026X-Goog-Expires=10\u0026X-Goog-SignedHeaders=host\u0026X-Goog-Signature=95e6a13d43a1d1962e667f17397f2b80ac9bdd1669210d5e08e0135df9dff4e56113485dbe429ca2266487b9d1796ebdee2d7cf682a6ef3bb9fbb4c351686fba90d7b621cf1c4eb1fdf126460dd25fa0837dfdde0a9fd98662ce60844c458448fb2b352c203d9969cb74efa4bdb742287744a4f2308afa4af0e0773f55e32e92973619249214b97283b2daa14195244444e33f938138d1e5f561088ce8011f4986dda33a556412594db7c12fc40e1ff3f1bedeb7a42f5bcda0b9567f17f65855f65071fabb88ea12371877f3f77f10e1466fff6ff6973b74a933322ff0949ce357e20abe96c3dd5cfab42c9c83e740a4d32b9e11e146f0eb3404d2e975896f74"
+ "expectedUrl": "https://storage.googleapis.com/test-bucket/test-object?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host&X-Goog-Signature=95e6a13d43a1d1962e667f17397f2b80ac9bdd1669210d5e08e0135df9dff4e56113485dbe429ca2266487b9d1796ebdee2d7cf682a6ef3bb9fbb4c351686fba90d7b621cf1c4eb1fdf126460dd25fa0837dfdde0a9fd98662ce60844c458448fb2b352c203d9969cb74efa4bdb742287744a4f2308afa4af0e0773f55e32e92973619249214b97283b2daa14195244444e33f938138d1e5f561088ce8011f4986dda33a556412594db7c12fc40e1ff3f1bedeb7a42f5bcda0b9567f17f65855f65071fabb88ea12371877f3f77f10e1466fff6ff6973b74a933322ff0949ce357e20abe96c3dd5cfab42c9c83e740a4d32b9e11e146f0eb3404d2e975896f74",
+ "scheme": "https",
+ "expectedCanonicalRequest": "GOOG4-RSA-SHA256\n20190201T090000Z\n20190201/auto/storage/goog4_request\n00e2fb794ea93d7adb703edaebdd509821fcc7d4f1a79ac5c8d2b394df109320",
+ "expectedStringToSign": "GET\n/test-bucket/test-object\nX-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host\nhost:storage.googleapis.com\n\nhost\nUNSIGNED-PAYLOAD"
},
{
"description": "Simple PUT",
@@ -16,7 +19,10 @@
"method": "PUT",
"expiration": "10",
"timestamp": "2019-02-01T09:00:00Z",
- "expectedUrl": "https://storage.googleapis.com/test-bucket/test-object?X-Goog-Algorithm=GOOG4-RSA-SHA256\u0026X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request\u0026X-Goog-Date=20190201T090000Z\u0026X-Goog-Expires=10\u0026X-Goog-SignedHeaders=host\u0026X-Goog-Signature=8adff1d4285739e31aa68e73767a46bc5511fde377497dbe08481bf5ceb34e29cc9a59921748d8ec3dd4085b7e9b7772a952afedfcdaecb3ae8352275b8b7c867f204e3db85076220a3127a8a9589302fc1181eae13b9b7fe41109ec8cdc93c1e8bac2d7a0cc32a109ca02d06957211326563ab3d3e678a0ba296e298b5fc5e14593c99d444c94724cc4be97015dbff1dca377b508fa0cb7169195de98d0e4ac96c42b918d28c8d92d33e1bd125ce0fb3cd7ad2c45dae65c22628378f6584971b8bf3945b26f2611eb651e9b6a8648970c1ecf386bb71327b082e7296c4e1ee2fc0bdd8983da80af375c817fb1ad491d0bc22c0f51dba0d66e2cffbc90803e47"
+ "expectedUrl": "https://storage.googleapis.com/test-bucket/test-object?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host&X-Goog-Signature=8adff1d4285739e31aa68e73767a46bc5511fde377497dbe08481bf5ceb34e29cc9a59921748d8ec3dd4085b7e9b7772a952afedfcdaecb3ae8352275b8b7c867f204e3db85076220a3127a8a9589302fc1181eae13b9b7fe41109ec8cdc93c1e8bac2d7a0cc32a109ca02d06957211326563ab3d3e678a0ba296e298b5fc5e14593c99d444c94724cc4be97015dbff1dca377b508fa0cb7169195de98d0e4ac96c42b918d28c8d92d33e1bd125ce0fb3cd7ad2c45dae65c22628378f6584971b8bf3945b26f2611eb651e9b6a8648970c1ecf386bb71327b082e7296c4e1ee2fc0bdd8983da80af375c817fb1ad491d0bc22c0f51dba0d66e2cffbc90803e47",
+ "scheme": "https",
+ "expectedCanonicalRequest": "PUT\n/test-bucket/test-object\nX-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host\nhost:storage.googleapis.com\n\nhost\nUNSIGNED-PAYLOAD",
+ "expectedStringToSign": "GOOG4-RSA-SHA256\n20190201T090000Z\n20190201/auto/storage/goog4_request\n78742860705da91404222d5d66ff89850292471199c3c2808d116ad12e6177b4"
},
{
"description": "POST for resumable uploads",
@@ -25,10 +31,13 @@
"method": "POST",
"expiration": "10",
"timestamp": "2019-02-01T09:00:00Z",
- "expectedUrl": "https://storage.googleapis.com/test-bucket/test-object?X-Goog-Algorithm=GOOG4-RSA-SHA256\u0026X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request\u0026X-Goog-Date=20190201T090000Z\u0026X-Goog-Expires=10\u0026X-Goog-SignedHeaders=host%3Bx-goog-resumable\u0026X-Goog-Signature=4a6d39b23343cedf4c30782aed4b384001828c79ffa3a080a481ea01a640dea0a0ceb58d67a12cef3b243c3f036bb3799c6ee88e8db3eaf7d0bdd4b70a228d0736e07eaa1ee076aff5c6ce09dff1f1f03a0d8ead0d2893408dd3604fdabff553aa6d7af2da67cdba6790006a70240f96717b98f1a6ccb24f00940749599be7ef72aaa5358db63ddd54b2de9e2d6d6a586eac4fe25f36d86fc6ab150418e9c6fa01b732cded226c6d62fc95b72473a4cc55a8257482583fe66d9ab6ede909eb41516a8690946c3e87b0f2052eb0e97e012a14b2f721c42e6e19b8a1cd5658ea36264f10b9b1ada66b8ed5bf7ed7d1708377ac6e5fe608ae361fb594d2e5b24c54",
+ "expectedUrl": "https://storage.googleapis.com/test-bucket/test-object?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host%3Bx-goog-resumable&X-Goog-Signature=4a6d39b23343cedf4c30782aed4b384001828c79ffa3a080a481ea01a640dea0a0ceb58d67a12cef3b243c3f036bb3799c6ee88e8db3eaf7d0bdd4b70a228d0736e07eaa1ee076aff5c6ce09dff1f1f03a0d8ead0d2893408dd3604fdabff553aa6d7af2da67cdba6790006a70240f96717b98f1a6ccb24f00940749599be7ef72aaa5358db63ddd54b2de9e2d6d6a586eac4fe25f36d86fc6ab150418e9c6fa01b732cded226c6d62fc95b72473a4cc55a8257482583fe66d9ab6ede909eb41516a8690946c3e87b0f2052eb0e97e012a14b2f721c42e6e19b8a1cd5658ea36264f10b9b1ada66b8ed5bf7ed7d1708377ac6e5fe608ae361fb594d2e5b24c54",
"headers": {
- "X-goog-resumable": "start"
- }
+ "X-Goog-Resumable": "start"
+ },
+ "scheme": "https",
+ "expectedCanonicalRequest": "POST\n/test-bucket/test-object\nX-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host%3Bx-goog-resumable\nhost:storage.googleapis.com\nx-goog-resumable:start\n\nhost;x-goog-resumable\nUNSIGNED-PAYLOAD",
+ "expectedStringToSign": "GOOG4-RSA-SHA256\n20190201T090000Z\n20190201/auto/storage/goog4_request\n877f8b40179d2753296f2fd6de815ab40503c7a3c446a7b44aa4e74422ff4daf"
},
{
"description": "Vary expiration and timestamp",
@@ -37,7 +46,10 @@
"method": "GET",
"expiration": "20",
"timestamp": "2019-03-01T09:00:00Z",
- "expectedUrl": "https://storage.googleapis.com/test-bucket/test-object?X-Goog-Algorithm=GOOG4-RSA-SHA256\u0026X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190301%2Fauto%2Fstorage%2Fgoog4_request\u0026X-Goog-Date=20190301T090000Z\u0026X-Goog-Expires=20\u0026X-Goog-SignedHeaders=host\u0026X-Goog-Signature=9669ed5b10664dc594c758296580662912cf4bcc5a4ba0b6bf055bcbf6f34eed7bdad664f534962174a924741a0c273a4f67bc1847cef20192a6beab44223bd9d4fbbd749c407b79997598c30f82ddc269ff47ec09fa3afe74e00616d438df0d96a7d8ad0adacfad1dc3286f864d924fe919fb0dce45d3d975c5afe8e13af2db9cc37ba77835f92f7669b61e94c6d562196c1274529e76cfff1564cc2cad7d5387dc8e12f7a5dfd925685fe92c30b43709eee29fa2f66067472cee5423d1a3a4182fe8cea75c9329d181dc6acad7c393cd04f8bf5bc0515127d8ebd65d80c08e19ad03316053ea60033fd1b1fd85a69c576415da3bf0a3718d9ea6d03e0d66f0"
+ "expectedUrl": "https://storage.googleapis.com/test-bucket/test-object?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190301%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190301T090000Z&X-Goog-Expires=20&X-Goog-SignedHeaders=host&X-Goog-Signature=9669ed5b10664dc594c758296580662912cf4bcc5a4ba0b6bf055bcbf6f34eed7bdad664f534962174a924741a0c273a4f67bc1847cef20192a6beab44223bd9d4fbbd749c407b79997598c30f82ddc269ff47ec09fa3afe74e00616d438df0d96a7d8ad0adacfad1dc3286f864d924fe919fb0dce45d3d975c5afe8e13af2db9cc37ba77835f92f7669b61e94c6d562196c1274529e76cfff1564cc2cad7d5387dc8e12f7a5dfd925685fe92c30b43709eee29fa2f66067472cee5423d1a3a4182fe8cea75c9329d181dc6acad7c393cd04f8bf5bc0515127d8ebd65d80c08e19ad03316053ea60033fd1b1fd85a69c576415da3bf0a3718d9ea6d03e0d66f0",
+ "scheme": "https",
+ "expectedCanonicalRequest": "GET\n/test-bucket/test-object\nX-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190301%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190301T090000Z&X-Goog-Expires=20&X-Goog-SignedHeaders=host\nhost:storage.googleapis.com\nhost\nUNSIGNED-PAYLOAD",
+ "expectedStringToSign": "GOOG4-RSA-SHA256\n20190301T090000Z\n20190301/auto/storage/goog4_request\n779f19fdb6fd381390e2d5af04947cf21750277ee3c20e0c97b7e46a1dff8907"
},
{
"description": "Vary bucket and object",
@@ -46,7 +58,37 @@
"method": "GET",
"expiration": "10",
"timestamp": "2019-02-01T09:00:00Z",
- "expectedUrl": "https://storage.googleapis.com/test-bucket2/test-object2?X-Goog-Algorithm=GOOG4-RSA-SHA256\u0026X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request\u0026X-Goog-Date=20190201T090000Z\u0026X-Goog-Expires=10\u0026X-Goog-SignedHeaders=host\u0026X-Goog-Signature=36e3d58dfd3ec1d2dd2f24b5ee372a71e811ffaa2162a2b871d26728d0354270bc116face87127532969c4a3967ed05b7309af741e19c7202f3167aa8c2ac420b61417d6451442bb91d7c822cd17be8783f01e05372769c88913561d27e6660dd8259f0081a71f831be6c50283626cbf04494ac10c394b29bb3bce74ab91548f58a37118a452693cf0483d77561fc9cac8f1765d2c724994cca46a83517a10157ee0347a233a2aaeae6e6ab5e204ff8fc5f54f90a3efdb8301d9fff5475d58cd05b181affd657f48203f4fb133c3a3d355b8eefbd10d5a0a5fd70d06e9515460ad74e22334b2cba4b29cae4f6f285cdb92d8f3126d7a1479ca3bdb69c207d860"
+ "expectedUrl": "https://storage.googleapis.com/test-bucket2/test-object2?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host&X-Goog-Signature=36e3d58dfd3ec1d2dd2f24b5ee372a71e811ffaa2162a2b871d26728d0354270bc116face87127532969c4a3967ed05b7309af741e19c7202f3167aa8c2ac420b61417d6451442bb91d7c822cd17be8783f01e05372769c88913561d27e6660dd8259f0081a71f831be6c50283626cbf04494ac10c394b29bb3bce74ab91548f58a37118a452693cf0483d77561fc9cac8f1765d2c724994cca46a83517a10157ee0347a233a2aaeae6e6ab5e204ff8fc5f54f90a3efdb8301d9fff5475d58cd05b181affd657f48203f4fb133c3a3d355b8eefbd10d5a0a5fd70d06e9515460ad74e22334b2cba4b29cae4f6f285cdb92d8f3126d7a1479ca3bdb69c207d860",
+ "scheme": "https",
+ "expectedCanonicalRequest": "GET\n/test-bucket2/test-object2\nX-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host\nhost:storage.googleapis.com\n\nhost\nUNSIGNED-PAYLOAD",
+ "expectedStringToSign": "GOOG4-RSA-SHA256\n20190201T090000Z\n20190201/auto/storage/goog4_request\na139afbf35ac30e9864f63197f79609731ab1b0ca166e2a456dba156fcd3f9ce"
+ },
+ {
+ "description": "Slashes in object name should not be URL encoded",
+ "bucket": "test-bucket",
+ "object": "path/with/slashes/under_score/amper&sand/file.ext",
+ "headers": {
+ "header/name/with/slash": "should-be-encoded"
+ },
+ "method": "GET",
+ "expiration": "10",
+ "timestamp": "2019-02-01T09:00:00Z",
+ "expectedUrl": "https://storage.googleapis.com/test-bucket/path/with/slashes/under_score/amper%26sand/file.ext?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=header%2Fname%2Fwith%2Fslash%3Bhost&X-Goog-Signature=2a9a82e84e39f5d2c0d980514db17f8c3dece473c9a5743d54e8453f9811927b1b99ce548c534cababd8fa339183e75b410e12e32a4c72f5ff176e95651fabed0072e59e7e236eb7e26f52c0ce599db1c47ae07af1a98d20872b6fde23432c0a5fcf4fb2dda735169198c80cd5cc51be9904f7e5eef2cc489ff44ac5697c529e4b34ac08709a7d2e425619377212c64561ed8b4d2fcb70a26e4f9236f995ab4658d240ac85c7a353bae6b2d39d5fc0716afa435a1f6e100db5504612b5e610db370623ab4b8eba3c03c98f23dcb4b9ffd518f2212abb2f93649d25385d71603d470cff0b7631adb9d0849d38609dedb3097761c8f47ec0d57777bb063611c05b",
+ "scheme": "https",
+ "expectedCanonicalRequest": "GET\n/test-bucket/path/with/slashes/under_score/amper%26sand/file.ext\nX-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=header%2Fname%2Fwith%2Fslash%3Bhost\nheader/name/with/slash:should-be-encoded\nhost:storage.googleapis.com\n\nheader/name/with/slash;host\nUNSIGNED-PAYLOAD",
+ "expectedStringToSign": "GOOG4-RSA-SHA256\n20190201T090000Z\n20190201/auto/storage/goog4_request\nf1d206dd8cbe1b892d4081ccddae0927d9f5fee5653fb2a2f43e7c20ed455cad"
+ },
+ {
+ "description": "Forward Slashes should not be stripped",
+ "bucket": "test-bucket",
+ "object": "/path/with/slashes/under_score/amper&sand/file.ext",
+ "method": "GET",
+ "expiration": "10",
+ "timestamp": "2019-02-01T09:00:00Z",
+ "expectedUrl": "https://storage.googleapis.com/test-bucket//path/with/slashes/under_score/amper%26sand/file.ext?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host&X-Goog-Signature=2db8b70e3f85b39be7824f6d02be31af2e6a2eb63f6bb41254851f7ef51bdad8963a9d2b254f8379c1780c8e6898be002d4100a0abd3d45f1437687fed65d15dd237c3a6f3c399c64ffd4e4cea7ef1c2f0391d35ecbeeaf3e3148d23c6f24c839cfcd92c1496332f5bfbbf1ed1e957eb45fad57df24828c96cf243eec23fba014d277c22a572708beb355888c5a8c0047cb3015d7f62cc90285676e7e34626fd0ce9ba5e0da39fc3de0035cc3ad120c46cb73db87246ae123f7a342c235e9480bd7d7e00c13b1e1bb7be5e2bce74d59a53505172463b48aefeedb48281d90874aa4177c881d3596ed1067f02eaac13d810a7aed234c41978b1394d0ce3662f76",
+ "scheme": "https",
+ "expectedCanonicalRequest": "GET\n/test-bucket//path/with/slashes/under_score/amper%26sand/file.ext\nX-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host\nhost:storage.googleapis.com\n\nhost\nUNSIGNED-PAYLOAD",
+ "expectedStringToSign": "GOOG4-RSA-SHA256\n20190201T090000Z\n20190201/auto/storage/goog4_request\n63c601ecd6ccfec84f1113fc906609cbdf7651395f4300cecd96ddd2c35164f8"
},
{
"description": "Simple headers",
@@ -55,11 +97,14 @@
"method": "GET",
"expiration": "10",
"timestamp": "2019-02-01T09:00:00Z",
- "expectedUrl": "https://storage.googleapis.com/test-bucket/test-object?X-Goog-Algorithm=GOOG4-RSA-SHA256\u0026X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request\u0026X-Goog-Date=20190201T090000Z\u0026X-Goog-Expires=10\u0026X-Goog-SignedHeaders=bar%3Bfoo%3Bhost\u0026X-Goog-Signature=68ecd3b008328ed30d91e2fe37444ed7b9b03f28ed4424555b5161980531ef87db1c3a5bc0265aad5640af30f96014c94fb2dba7479c41bfe1c020eb90c0c6d387d4dd09d4a5df8b60ea50eb6b01cdd786a1e37020f5f95eb8f9b6cd3f65a1f8a8a65c9fcb61ea662959efd9cd73b683f8d8804ef4d6d9b2852419b013368842731359d7f9e6d1139032ceca75d5e67cee5fd0192ea2125e5f2955d38d3d50cf116f3a52e6a62de77f6207f5b95aaa1d7d0f8a46de89ea72e7ea30f21286318d7eba0142232b0deb3a1dc9e1e812a981c66b5ffda3c6b01a8a9d113155792309fd53a3acfd054ca7776e8eec28c26480cd1e3c812f67f91d14217f39a606669d",
+ "expectedUrl": "https://storage.googleapis.com/test-bucket/test-object?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=bar%3Bfoo%3Bhost&X-Goog-Signature=68ecd3b008328ed30d91e2fe37444ed7b9b03f28ed4424555b5161980531ef87db1c3a5bc0265aad5640af30f96014c94fb2dba7479c41bfe1c020eb90c0c6d387d4dd09d4a5df8b60ea50eb6b01cdd786a1e37020f5f95eb8f9b6cd3f65a1f8a8a65c9fcb61ea662959efd9cd73b683f8d8804ef4d6d9b2852419b013368842731359d7f9e6d1139032ceca75d5e67cee5fd0192ea2125e5f2955d38d3d50cf116f3a52e6a62de77f6207f5b95aaa1d7d0f8a46de89ea72e7ea30f21286318d7eba0142232b0deb3a1dc9e1e812a981c66b5ffda3c6b01a8a9d113155792309fd53a3acfd054ca7776e8eec28c26480cd1e3c812f67f91d14217f39a606669d",
"headers": {
"BAR": "BAR-value",
"foo": "foo-value"
- }
+ },
+ "scheme": "https",
+ "expectedCanonicalRequest": "GET\n/test-bucket/test-object\nX-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=bar%3Bfoo%3Bhost\nbar:BAR-value\nfoo:foo-value\nhost:storage.googleapis.com\n\nbar;foo;host\nUNSIGNED-PAYLOAD",
+ "expectedStringToSign": "GOOG4-RSA-SHA256\n20190201T090000Z\n20190201/auto/storage/goog4_request\n59c1ac1a6ee7d773d5c4487ecc861d60b71c4871dd18fc7d8485fac09df1d296"
},
{
"description": "Headers should be trimmed",
@@ -68,13 +113,16 @@
"method": "GET",
"expiration": "10",
"timestamp": "2019-02-01T09:00:00Z",
- "expectedUrl": "https://storage.googleapis.com/test-bucket/test-object?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=collapsed%3Bhost%3Bleading%3Btabs%3Btrailing&X-Goog-Signature=52fa1d70bcd527ee9c1241f87c56bc481526e8a63d440948595ff776faacb0caa6e8a3060b113546cb27ed29d80c88d402947d83948758d4e5c49e47d9482751d46b2a99c2dae5bc8f7baffab03dec05b28b5d605610686c48e867d6a4239a2a61a785df7d6099d155bba57d0d331d66d667b5df8e165e8277e2675678fc28499abd34053a2bc4e4fa21d032c4278fd29897e8307f142506a3d8d07149cded15f7defa77028fb88ff45132cee5f6232feb8e7f899fe361f1f8ceed0795aff860084f35e27475447dc6e64e4baa09e96a725eee6fa3c408d6bb51c2bd5f649afb8339f46997d9ef22496a79cf0846e52ac941c08dc4b9d63639d0ff2ce8637412",
+ "expectedUrl": "https://storage.googleapis.com/test-bucket/test-object?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=collapsed%3Bhost%3Bleading%3Btabs%3Btrailing&X-Goog-Signature=75d77a3ed2d9b74ff7e1e23b2fd7cc714ad4cc32518c65f3a8197827cd87d302623bab990cf2ff3a633bfaae69b6c2d897add78c105aa68411229610421c4239579add4aff6bdbd5067a0fd61c3aa0029d7de0f8ae88fa3458fa70f875e841d6df9598597d9012b9f848c6857e08f2704ca2f332c71738490ffdda2ed928f9340549d7295745725062d28dc1696eab7cb3b88ac4fd445e951423f645d680a60dd8033d65b65f4c10286f59f4258dbb2bcf36a76ffdd40574104cbbf0b76901c24df5854f24c42e9192fcedc386d85704fec6a6bad3a5201e1fb6c491a4c43371b0913420743580daf3504e99204c6ec894b4d70cd27bc60c3fe2850e8bf3ed22",
"headers": {
"collapsed": "abc def",
"leading": " xyz",
"trailing": "abc ",
"tabs": "\tabc\t\t\t\tdef\t"
- }
+ },
+ "scheme": "https",
+ "expectedCanonicalRequest": "GET\n/test-bucket/test-object\nX-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=collapsed%3Bhost%3Bleading%3Btabs%3Btrailing\ncollapsed:abc def\nhost:storage.googleapis.com\nleading:xyz\ntabs:abc def\ntrailing:abc\n\ncollapsed;host;leading;tabs;trailing\nUNSIGNED-PAYLOAD",
+ "expectedStringToSign": "GOOG4-RSA-SHA256\n20190201T090000Z\n20190201/auto/storage/goog4_request\n19153e83555808dbfeb8969043cc8ce8d5db0cce91dc11fb9df58b8130f09d42"
},
{
"description": "Header value with multiple inline values",
@@ -85,8 +133,11 @@
"timestamp": "2019-02-01T09:00:00Z",
"expectedUrl": "https://storage.googleapis.com/test-bucket/test-object?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host%3Bmultiple&X-Goog-Signature=5cc113735625341f59c7203f0c2c9febc95ba6af6b9c38814f8e523214712087dc0996e4960d273ae1889f248ac1e58d4d19cb3a69ad7670e9a8ca1b434e878f59339dc7006cf32dfd715337e9f593e0504371839174962a08294586e0c78160a7aa303397888c8350637c6af3b32ac310886cc4590bfda9ca561ee58fb5b8ec56bc606d2ada6e7df31f4276e9dcb96bcaea39dc2cd096f3fad774f9c4b30e317ad43736c05f76831437f44e8726c1e90d3f6c9827dc273f211f32fc85658dfc5d357eb606743a6b00a29e519eef1bebaf9db3e8f4b1f5f9afb648ad06e60bc42fa8b57025056697c874c9ea76f5a73201c9717ea43e54713ff3502ff3fc626b",
"headers": {
- "multiple": " xyz , abc, def , xyz "
- }
+ "multiple": " xyz , abc, def , xyz "
+ },
+ "scheme": "https",
+ "expectedCanonicalRequest": "GET\n/test-bucket/test-object\nX-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host%3Bmultiple\nhost:storage.googleapis.com\nmultiple:xyz , abc, def , xyz\n\nhost;multiple\nUNSIGNED-PAYLOAD",
+ "expectedStringToSign": "GOOG4-RSA-SHA256\n20190201T090000Z\n20190201/auto/storage/goog4_request\n4df8e486146c31f1c8cd4e4c730554cde4326791ba48ec11fa969a3de064cd7f"
},
{
"description": "Customer-supplied encryption key",
@@ -100,7 +151,10 @@
"X-Goog-Encryption-Algorithm": "AES256",
"X-Goog-Encryption-Key": "key",
"X-Goog-Encryption-Key-Sha256": "key-hash"
- }
+ },
+ "scheme": "https",
+ "expectedCanonicalRequest": "GET\n/test-bucket/test-object\nX-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host%3Bx-goog-encryption-algorithm%3Bx-goog-encryption-key%3Bx-goog-encryption-key-sha256\nhost:storage.googleapis.com\nx-goog-encryption-algorithm:AES256\nx-goog-encryption-key:key\nx-goog-encryption-key-sha256:key-hash\n\nhost;x-goog-encryption-algorithm;x-goog-encryption-key;x-goog-encryption-key-sha256\nUNSIGNED-PAYLOAD",
+ "expectedStringToSign": "GOOG4-RSA-SHA256\n20190201T090000Z\n20190201/auto/storage/goog4_request\n66a45104eba8bdd9748723b45cbd54c3f0f6dba337a5deb9fb6a66334223dc06"
},
{
"description": "List Objects",
@@ -108,7 +162,72 @@
"method": "GET",
"expiration": "10",
"timestamp": "2019-02-01T09:00:00Z",
- "expectedUrl": "https://storage.googleapis.com/test-bucket?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host&X-Goog-Signature=6dbe94f8e52b2b8a9a476b1c857efa474e09944e2b52b925800316e094a7169d8dbe0df9c0ac08dabb22ac7e827470ceccd65f5a3eadba2a4fb9beebfe37f0d9bb1e552b851fa31a25045bdf019e507f5feb44f061551ef1aeb18dcec0e38ba2e2f77d560a46eaace9c56ed9aa642281301a9d848b0eb30749e34bc7f73a3d596240533466ff9b5f289cd0d4c845c7d96b82a35a5abd0c3aff83e4440ee6873e796087f43545544dc8c01afe1d79c726696b6f555371e491980e7ec145cca0803cf562c38f3fa1d724242f5dea25aac91d74ec9ddd739ff65523627763eaef25cd1f95ad985aaf0079b7c74eb5bcb2870a9b137a7b2c8e41fbe838c95872f75b"
+ "expectedUrl": "https://storage.googleapis.com/test-bucket?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host&X-Goog-Signature=6dbe94f8e52b2b8a9a476b1c857efa474e09944e2b52b925800316e094a7169d8dbe0df9c0ac08dabb22ac7e827470ceccd65f5a3eadba2a4fb9beebfe37f0d9bb1e552b851fa31a25045bdf019e507f5feb44f061551ef1aeb18dcec0e38ba2e2f77d560a46eaace9c56ed9aa642281301a9d848b0eb30749e34bc7f73a3d596240533466ff9b5f289cd0d4c845c7d96b82a35a5abd0c3aff83e4440ee6873e796087f43545544dc8c01afe1d79c726696b6f555371e491980e7ec145cca0803cf562c38f3fa1d724242f5dea25aac91d74ec9ddd739ff65523627763eaef25cd1f95ad985aaf0079b7c74eb5bcb2870a9b137a7b2c8e41fbe838c95872f75b",
+ "scheme": "https",
+ "expectedCanonicalRequest": "GET\n/test-bucket\nX-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host\nhost:storage.googleapis.com\n\nhost\nUNSIGNED-PAYLOAD",
+ "expectedStringToSign": "GOOG4-RSA-SHA256\n20190201T090000Z\n20190201/auto/storage/goog4_request\n51a7426c2a6c6ab80f336855fc629461ff182fb1d2cb552ac68e5ce8e25db487"
+ },
+ {
+ "description": "Query Parameter Encoding",
+ "bucket": "test-bucket",
+ "object": "test-object",
+ "method": "GET",
+ "expiration": "10",
+ "timestamp": "2019-02-01T09:00:00Z",
+ "expectedUrl": "https://storage.googleapis.com/test-bucket/test-object?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host&aA0%C3%A9%2F%3D%25-_.~=~._-%25%3D%2F%C3%A90Aa&X-Goog-Signature=221f1905382ce560042b0441e678b6589f4a661fd319f079bde1f7d7ab07e8515334dbabd901c95d3f16a03f389f661ef7de897fdc7cea8914d93ac8638ad56a9dca62ec8983478a9513a702e12dd57182b5b5ee58d7e94dd685f6c2bbaec1ad168294eaf8300cafec7565e1ad99f55b324caa48720d541e1b2be39b10baa7ff39d2cb77efdad91d63fa0c80625234430027077f68f8ad8c258ef8aba93e2a15fb3f74111e9ffab46f481899d1e83db7d84d9b2645975086ba67ce2d9284d50bb2725871d05621a791ee1c9db7db8a52d579191c5f59da6063128effbe0bbc1ae9a573e298e63aa29bbe9bb8dba76a6c98154a9f03f5ce0cb8f176e0ad14acae",
+ "queryParameters": {
+ "aA0é/=%-_.~": "~._-%=/é0Aa"
+ },
+ "scheme": "https",
+ "expectedCanonicalRequest": "GET\n/test-bucket/test-object\nX-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host&aA0%C3%A9%2F%3D%25-_.~=~._-%25%3D%2F%C3%A90Aa\nhost:storage.googleapis.com\n\nhost\nUNSIGNED-PAYLOAD",
+ "expectedStringToSign": "GOOG4-RSA-SHA256\n20190201T090000Z\n20190201/auto/storage/goog4_request\na4815f31e2df44febcde5f15614a38dfaab1d2dbc2488c92e0dfaef06351448a"
+ },
+ {
+ "description": "Query Parameter Ordering",
+ "bucket": "test-bucket",
+ "object": "test-object",
+ "method": "GET",
+ "expiration": "10",
+ "timestamp": "2019-02-01T09:00:00Z",
+ "expectedUrl": "https://storage.googleapis.com/test-bucket/test-object?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-Meta-Foo=bar&X-Goog-SignedHeaders=host&prefix=%2Ffoo&X-Goog-Signature=a07745e1b3d59b85cbe11aa766df72c22468959e7217615dccb7f030234f66b60b37e480f30725ed51f29816362ca8286c619ebb66448ff1d370be2a4a48aacf20d3d2d6200ed17341a5791baf2ee5cd9c2823adacc6264f66c8a54fa887e1bce3c55cf78fb2f6a52618cf09d6f945f63d148052a7b66a75e075ff5065828a806b84bdc49a42399be7483225c720d5e18a6160f79d815f433e7921694fe1d041099851793c2581db0e5ca503cfb566e414f900ceede5f9b22030edd32ab20b6f7f9fb2afba89098b9364e03397c03a94eac3a140c99979b8786844fb4f6c62c1985378939dd1bbaea8e41b9100dda85a27733171cc78d96ee362ea2c3432f4d8",
+ "queryParameters": {
+ "prefix": "/foo",
+ "X-Goog-Meta-Foo": "bar"
+ },
+ "scheme": "https",
+ "expectedCanonicalRequest": "GET\n/test-bucket/test-object\nX-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-Meta-Foo=bar&X-Goog-SignedHeaders=host&prefix=%2Ffoo\nhost:storage.googleapis.com\n\nhost\nUNSIGNED-PAYLOAD",
+ "expectedStringToSign": "GOOG4-RSA-SHA256\n20190201T090000Z\n20190201/auto/storage/goog4_request\n4dafe74ad142f32b7c25fc4e6b38fd3b8a6339d7f112247573fb0066f637db6c"
+ },
+ {
+ "description": "Header Ordering",
+ "bucket": "test-bucket",
+ "object": "test-object",
+ "method": "GET",
+ "expiration": "10",
+ "timestamp": "2019-02-01T09:00:00Z",
+ "expectedUrl": "https://storage.googleapis.com/test-bucket/test-object?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host%3Bx-goog-date&X-Goog-Signature=55a28435997457f1498291e878fd39c5f321973057d2541886020fdfd212b1467d9eeffdc70951ea952d634cb4193e657ed5b7860c46d37f7d904774680a16e518aa9dff273e8441d6893de615eb592e3113d682ad64a87eb0e0c48df17c30f899e7f940ba230530b30f725ab9ec38789682413752de6a026ae69dd858843100645f3ec986aed618d229f8844d378e0e66e907ede6dff7aac56723f51eb830e8877a56100c86a876173424602abefe6c22b6540a2b36634860b2e89137f297cca8f080bdf3433a9d614c5ab2ec84f65412b45516b30500886a2300f23c3423ae0e91546e3471ee08d06894bddc76203a418d46f35bf0b4574f7b24a693fb046c",
+ "headers": {
+ "X-Goog-Date": "20190201T090000Z"
+ },
+ "scheme": "https",
+ "expectedCanonicalRequest": "GET\n/test-bucket/test-object\nX-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host%3Bx-goog-date\nhost:storage.googleapis.com\nx-goog-date:20190201T090000Z\n\nhost;x-goog-date\nUNSIGNED-PAYLOAD",
+ "expectedStringToSign": "GOOG4-RSA-SHA256\n20190201T090000Z\n20190201/auto/storage/goog4_request\n4052143280d90d5f4a8c878ff7418be6fee5d34e50b1da28d8081a094b88fa61"
+ },
+ {
+ "description": "Signed Payload Instead of UNSIGNED-PAYLOAD",
+ "bucket": "test-bucket",
+ "object": "test-object",
+ "method": "PUT",
+ "expiration": "10",
+ "timestamp": "2019-02-01T09:00:00Z",
+ "expectedUrl": "https://storage.googleapis.com/test-bucket/test-object?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host%3Bx-goog-content-sha256%3Bx-testcasemetadata-payload-value&X-Goog-Signature=3e5a9669e9aa162888dff1553d24c159bad4f16d444987f6a1b26d8ad0cb7927f15bfaf79c205324d2138fd1f62edb255430c77a03c0d6e9601399e2519014f9e1a7051d9be735cde530022c84602b1c4c25c86cb1e1584489e49d511c9a618a1a8443af31626ca5b2ad105eda1e4499f52b4043f3c1a3bd40c06c0cae36bb19a50ed8671e5d2cdbb148a196ce5a8c14d6970c08225da293e1ef400c92e7a3d5ba0a29ad0893827c96b203a04b04ebd51929bf99b323beba93097dfee700ee2c1bd97013779e5c8f156e56175d4d07e453b2eb0d616086f9f4753dde63507efe88b0dec29c872d25d9465f07778b16b532814148c578ee7e64ed8437006fa551",
+ "headers": {
+ "X-Goog-Content-SHA256": "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b982",
+ "X-TestCaseMetadata-Payload-Value": "hello"
+ },
+ "scheme": "https",
+ "expectedCanonicalRequest": "PUT\n/test-bucket/test-object\nX-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=test-iam-credentials%40dummy-project-id.iam.gserviceaccount.com%2F20190201%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20190201T090000Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host%3Bx-goog-content-sha256%3Bx-testcasemetadata-payload-value\nhost:storage.googleapis.com\nx-goog-content-sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b982\nx-testcasemetadata-payload-value:hello\n\nhost;x-goog-content-sha256;x-testcasemetadata-payload-value\n2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b982",
+ "expectedStringToSign": "GOOG4-RSA-SHA256\n20190201T090000Z\n20190201/auto/storage/goog4_request\nbe21a0841a897930ff5cf72e6e74ec5274efd76c3fe4cde6678f24a0a3d6dbec"
}
]
}
diff --git a/storage/storage.go b/storage/storage.go
index 9d9e92f..382b175 100644
--- a/storage/storage.go
+++ b/storage/storage.go
@@ -226,10 +226,18 @@
ContentType string
// Headers is a list of extension headers the client must provide
- // in order to use the generated signed URL.
+ // in order to use the generated signed URL. Each must be a string of the
+ // form "key:values", with multiple values separated by a semicolon.
// Optional.
Headers []string
+ // QueryParameters is a map of additional query parameters. When
+ // SigningScheme is V4, this is used in computing the signature, and the
+ // client must use the same query parameters when using the generated signed
+ // URL.
+ // Optional.
+ QueryParameters url.Values
+
// MD5 is the base64 encoded MD5 checksum of the file.
// If provided, the client should provide the exact value on the request
// header in order to use the signed URL.
@@ -431,6 +439,21 @@
return res
}
+// pathEncodeV4 creates an encoded string that matches the v4 signature spec.
+// Following the spec precisely is necessary in order to ensure that the URL
+// and signing string are correctly formed, and Go's url.PathEncode and
+// url.QueryEncode don't generate an exact match without some additional logic.
+func pathEncodeV4(path string) string {
+ segments := strings.Split(path, "/")
+ var encodedSegments []string
+ for _, s := range segments {
+ encodedSegments = append(encodedSegments, url.QueryEscape(s))
+ }
+ encodedStr := strings.Join(encodedSegments, "/")
+ encodedStr = strings.Replace(encodedStr, "+", "%20", -1)
+ return encodedStr
+}
+
// signedURLV4 creates a signed URL using the sigV4 algorithm.
func signedURLV4(bucket, name string, opts *SignedURLOptions, now time.Time) (string, error) {
buf := &bytes.Buffer{}
@@ -439,11 +462,12 @@
if name != "" {
u.Path += "/" + name
}
+ u.RawPath = pathEncodeV4(u.Path)
// Note: we have to add a / here because GCS does so auto-magically, despite
- // Go's EscapedPath not doing so (and we have to exactly match their
+ // our encoding not doing so (and we have to exactly match their
// canonical query).
- fmt.Fprintf(buf, "/%s\n", u.EscapedPath())
+ fmt.Fprintf(buf, "/%s\n", u.RawPath)
headerNames := append(extractHeaderNames(opts.Headers), "host")
if opts.ContentType != "" {
@@ -463,6 +487,12 @@
"X-Goog-Expires": {fmt.Sprintf("%d", int(opts.Expires.Sub(now).Seconds()))},
"X-Goog-SignedHeaders": {signedHeaders},
}
+ // Add user-supplied query parameters to the canonical query string. For V4,
+ // it's necessary to include these.
+ for k, v := range opts.QueryParameters {
+ canonicalQueryString[k] = append(canonicalQueryString[k], v...)
+ }
+
fmt.Fprintf(buf, "%s\n", canonicalQueryString.Encode())
u.Host = "storage.googleapis.com"
@@ -471,15 +501,33 @@
headersWithValue = append(headersWithValue, "host:"+u.Host)
headersWithValue = append(headersWithValue, opts.Headers...)
if opts.ContentType != "" {
- headersWithValue = append(headersWithValue, "content-type:"+strings.TrimSpace(opts.ContentType))
+ headersWithValue = append(headersWithValue, "content-type:"+opts.ContentType)
}
if opts.MD5 != "" {
- headersWithValue = append(headersWithValue, "content-md5:"+strings.TrimSpace(opts.MD5))
+ headersWithValue = append(headersWithValue, "content-md5:"+opts.MD5)
}
- canonicalHeaders := strings.Join(sortHeadersByKey(headersWithValue), "\n")
+ // Trim extra whitespace from headers and replace with a single space.
+ var trimmedHeaders []string
+ for _, h := range headersWithValue {
+ trimmedHeaders = append(trimmedHeaders, strings.Join(strings.Fields(h), " "))
+ }
+ canonicalHeaders := strings.Join(sortHeadersByKey(trimmedHeaders), "\n")
fmt.Fprintf(buf, "%s\n\n", canonicalHeaders)
fmt.Fprintf(buf, "%s\n", signedHeaders)
- fmt.Fprint(buf, "UNSIGNED-PAYLOAD")
+
+ // If the user provides a value for X-Goog-Content-SHA256, we must use
+ // that value in the request string. If not, we use UNSIGNED-PAYLOAD.
+ sha256Header := false
+ for _, h := range trimmedHeaders {
+ if strings.HasPrefix(strings.ToLower(h), "x-goog-content-sha256") && strings.Contains(h, ":") {
+ sha256Header = true
+ fmt.Fprintf(buf, "%s", strings.SplitN(h, ":", 2)[1])
+ break
+ }
+ }
+ if !sha256Header {
+ fmt.Fprint(buf, "UNSIGNED-PAYLOAD")
+ }
sum := sha256.Sum256(buf.Bytes())
hexDigest := hex.EncodeToString(sum[:])
diff --git a/storage/storage_test.go b/storage/storage_test.go
index 2b38965..2bf13c6 100644
--- a/storage/storage_test.go
+++ b/storage/storage_test.go
@@ -487,6 +487,31 @@
}
}
+func TestPathEncodeV4(t *testing.T) {
+ tests := []struct {
+ input string
+ want string
+ }{
+ {
+ "path/with/slashes",
+ "path/with/slashes",
+ },
+ {
+ "path/with/speci@lchar$&",
+ "path/with/speci%40lchar%24%26",
+ },
+ {
+ "path/with/un_ersc_re/~tilde/sp ace/",
+ "path/with/un_ersc_re/~tilde/sp%20%20ace/",
+ },
+ }
+ for _, test := range tests {
+ if got := pathEncodeV4(test.input); got != test.want {
+ t.Errorf("pathEncodeV4(%q) = %q, want %q", test.input, got, test.want)
+ }
+ }
+}
+
func dummyKey(kind string) []byte {
slurp, err := ioutil.ReadFile(fmt.Sprintf("./internal/test/dummy_%s", kind))
if err != nil {