Update dependencies

Among others, this updates minio-go, so that the new "eu-west-3" zone
for AWS is supported.
This commit is contained in:
Alexander Neumann
2018-01-23 19:40:42 +01:00
parent b63de7c798
commit 2b39f9f4b2
3435 changed files with 1317989 additions and 315639 deletions
+5 -22
View File
@@ -22,7 +22,6 @@ import (
"github.com/google/go-cmp/cmp"
"cloud.google.com/go/internal/pretty"
"cloud.google.com/go/internal/testutil"
"google.golang.org/api/googleapi"
raw "google.golang.org/api/storage/v1"
@@ -56,11 +55,7 @@ func TestBucketAttrsToRawBucket(t *testing.T) {
Versioning: nil, // ignore VersioningEnabled if false
Labels: map[string]string{"label": "value"},
}
msg, ok, err := pretty.Diff(want, got)
if err != nil {
t.Fatal(err)
}
if !ok {
if msg := testutil.Diff(got, want); msg != "" {
t.Error(msg)
}
@@ -69,11 +64,7 @@ func TestBucketAttrsToRawBucket(t *testing.T) {
got = attrs.toRawBucket()
want.Versioning = &raw.BucketVersioning{Enabled: true}
want.Billing = &raw.BucketBilling{RequesterPays: true}
msg, ok, err = pretty.Diff(want, got)
if err != nil {
t.Fatal(err)
}
if !ok {
if msg := testutil.Diff(got, want); msg != "" {
t.Error(msg)
}
}
@@ -103,11 +94,7 @@ func TestBucketAttrsToUpdateToRawBucket(t *testing.T) {
},
NullFields: []string{"Labels.b"},
}
msg, ok, err := pretty.Diff(want, got)
if err != nil {
t.Fatal(err)
}
if !ok {
if msg := testutil.Diff(got, want); msg != "" {
t.Error(msg)
}
@@ -119,14 +106,10 @@ func TestBucketAttrsToUpdateToRawBucket(t *testing.T) {
ForceSendFields: []string{"Labels"},
NullFields: []string{"Labels.b"},
}
msg, ok, err = pretty.Diff(want, got)
if err != nil {
t.Fatal(err)
}
if !ok {
if msg := testutil.Diff(got, want); msg != "" {
t.Error(msg)
}
}
func TestCallBuilders(t *testing.T) {
-2
View File
@@ -23,8 +23,6 @@ All of the methods of this package use exponential backoff to retry calls
that fail with certain errors, as described in
https://cloud.google.com/storage/docs/exponential-backoff.
Note: This package is in beta. Some backwards-incompatible changes may occur.
Creating a Client
+290 -40
View File
@@ -310,11 +310,7 @@ func TestIntegration_ConditionalDelete(t *testing.T) {
}
func TestIntegration_Objects(t *testing.T) {
// TODO(djd): there are a lot of closely-related tests here which share
// a common setup. Once we can depend on Go 1.7 features, we should refactor
// this test to use the sub-test feature. This will increase the readability
// of this test, and should also reduce the time it takes to execute.
// https://golang.org/pkg/testing/#hdr-Subtests_and_Sub_benchmarks
// TODO(jba): Use subtests (Go 1.7).
ctx := context.Background()
client, bucket := testConfig(ctx, t)
defer client.Close()
@@ -386,33 +382,6 @@ func TestIntegration_Objects(t *testing.T) {
if err := rc.Close(); err != nil {
t.Errorf("%v Close: %v", obj, err)
}
// Test SignedURL
opts := &SignedURLOptions{
GoogleAccessID: "xxx@clientid",
PrivateKey: dummyKey("rsa"),
Method: "GET",
MD5: "ICy5YqxZB1uWSwcVLSNLcA==",
Expires: time.Date(2020, time.October, 2, 10, 0, 0, 0, time.UTC),
ContentType: "application/json",
Headers: []string{"x-header1", "x-header2"},
}
u, err := SignedURL(bucket, obj, opts)
if err != nil {
t.Fatalf("SignedURL(%q, %q) errored with %v", bucket, obj, err)
}
res, err := client.hc.Get(u)
if err != nil {
t.Fatalf("Can't get URL %q: %v", u, err)
}
slurp, err = ioutil.ReadAll(res.Body)
if err != nil {
t.Fatalf("Can't ReadAll signed object %v, errored with %v", obj, err)
}
if got, want := slurp, contents[obj]; !bytes.Equal(got, want) {
t.Errorf("Contents (%v) = %q; want %q", obj, got, want)
}
res.Body.Close()
}
obj := objects[0]
@@ -763,6 +732,115 @@ func testObjectIterator(t *testing.T, bkt *BucketHandle, objects []string) {
// TODO(jba): test query.Delimiter != ""
}
func TestIntegration_SignedURL(t *testing.T) {
// To test SignedURL, we need a real user email and private key. Extract them
// from the JSON key file.
jwtConf, err := testutil.JWTConfig()
if err != nil {
t.Fatal(err)
}
if jwtConf == nil {
t.Skip("JSON key file is not present")
}
ctx := context.Background()
client, bucket := testConfig(ctx, t)
defer client.Close()
bkt := client.Bucket(bucket)
obj := "signedURL"
contents := []byte("This is a test of SignedURL.\n")
md5 := "Jyxvgwm9n2MsrGTMPbMeYA==" // base64-encoded MD5 of contents
if err := writeObject(ctx, bkt.Object(obj), "text/plain", contents); err != nil {
t.Fatalf("writing: %v", err)
}
for _, test := range []struct {
desc string
opts SignedURLOptions
headers map[string][]string
fail bool
}{
{
desc: "basic",
},
{
desc: "MD5 sent and matches",
opts: SignedURLOptions{MD5: md5},
headers: map[string][]string{"Content-MD5": {md5}},
},
{
desc: "MD5 not sent",
opts: SignedURLOptions{MD5: md5},
fail: true,
},
{
desc: "Content-Type sent and matches",
opts: SignedURLOptions{ContentType: "text/plain"},
headers: map[string][]string{"Content-Type": {"text/plain"}},
},
{
desc: "Content-Type sent but does not match",
opts: SignedURLOptions{ContentType: "text/plain"},
headers: map[string][]string{"Content-Type": {"application/json"}},
fail: true,
},
{
desc: "Canonical headers sent and match",
opts: SignedURLOptions{Headers: []string{
" X-Goog-Foo: Bar baz ",
"X-Goog-Novalue", // ignored: no value
"X-Google-Foo", // ignored: wrong prefix
}},
headers: map[string][]string{"X-Goog-foo": {"Bar baz "}},
},
{
desc: "Canonical headers sent but don't match",
opts: SignedURLOptions{Headers: []string{" X-Goog-Foo: Bar baz"}},
headers: map[string][]string{"X-Goog-Foo": {"bar baz"}},
fail: true,
},
} {
opts := test.opts
opts.GoogleAccessID = jwtConf.Email
opts.PrivateKey = jwtConf.PrivateKey
opts.Method = "GET"
opts.Expires = time.Now().Add(time.Hour)
u, err := SignedURL(bucket, obj, &opts)
if err != nil {
t.Errorf("%s: SignedURL: %v", test.desc, err)
continue
}
got, err := getURL(u, test.headers)
if err != nil && !test.fail {
t.Errorf("%s: getURL %q: %v", test.desc, u, err)
} else if err == nil && !bytes.Equal(got, contents) {
t.Errorf("%s: got %q, want %q", test.desc, got, contents)
}
}
}
// Make a GET request to a URL using an unauthenticated client, and return its contents.
func getURL(url string, headers map[string][]string) ([]byte, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req.Header = headers
res, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
bytes, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
if res.StatusCode != 200 {
return nil, fmt.Errorf("code=%d, body=%s", res.StatusCode, string(bytes))
}
return bytes, nil
}
func TestIntegration_ACL(t *testing.T) {
ctx := context.Background()
client, bucket := testConfig(ctx, t)
@@ -1012,7 +1090,7 @@ func TestIntegration_Encryption(t *testing.T) {
}
gotContents := string(got)
if gotContents != wantContents {
t.Errorf("%s: got %q, want %q", gotContents, wantContents)
t.Errorf("%s: got %q, want %q", msg, gotContents, wantContents)
}
}
@@ -1441,12 +1519,11 @@ func TestIntegration_RequesterPays(t *testing.T) {
// user: not an Owner on the containing project
// userProject: not the containing one, but user has Editor role on it
// result: success, by the standard requester-pays rule
// TODO(jba): enable when the service is fixed.
// if err := f(ob.UserProject(otherProjID)); err != nil {
// t.Errorf("%s: got %v, want nil\n"+
// "confirm that %s is an Editor on %s",
// msg, err, otherUser, otherProjID)
// }
if err := f(ob.UserProject(otherProjID)); err != nil {
t.Errorf("%s: got %v, want nil\n"+
"confirm that %s is an Editor on %s and that that project has billing enabled",
msg, err, otherUser, otherProjID)
}
// user: not an Owner on the containing project
// userProject: the containing one, on which the user does NOT have Editor permission.
// result: failure
@@ -1469,7 +1546,7 @@ func TestIntegration_RequesterPays(t *testing.T) {
})
if attrs != nil {
if got, want := attrs.RequesterPays, true; got != want {
t.Fatalf("attr.RequesterPays = %b, want %b", got, want)
t.Fatalf("attr.RequesterPays = %t, want %t", got, want)
}
}
// Object operations.
@@ -1689,6 +1766,179 @@ func TestIntegration_Public(t *testing.T) {
}
}
func TestIntegration_ReadCRC(t *testing.T) {
// Test that the checksum is handled correctly when reading files.
// For gzipped files, see https://github.com/GoogleCloudPlatform/google-cloud-dotnet/issues/1641.
if testing.Short() {
t.Skip("Integration tests skipped in short mode")
}
const (
// This is an uncompressed file.
// See https://cloud.google.com/storage/docs/public-datasets/landsat
uncompressedBucket = "gcp-public-data-landsat"
uncompressedObject = "LC08/PRE/044/034/LC80440342016259LGN00/LC80440342016259LGN00_MTL.txt"
gzippedBucket = "storage-library-test-bucket"
gzippedObject = "gzipped-text.txt"
gzippedContents = "hello world" // uncompressed contents of the file
)
ctx := context.Background()
client, err := NewClient(ctx, option.WithoutAuthentication())
if err != nil {
t.Fatal(err)
}
defer client.Close()
for _, test := range []struct {
desc string
obj *ObjectHandle
offset, length int64
readCompressed bool // don't decompress a gzipped file
wantErr bool
wantCheck bool // Should Reader try to check the CRC?
wantChecked bool // Did Reader actually check the CRC?
}{
{
desc: "uncompressed, entire file",
obj: client.Bucket(uncompressedBucket).Object(uncompressedObject),
offset: 0,
length: -1,
readCompressed: false,
wantCheck: true,
wantChecked: true,
},
{
desc: "uncompressed, entire file, don't decompress",
obj: client.Bucket(uncompressedBucket).Object(uncompressedObject),
offset: 0,
length: -1,
readCompressed: true,
wantCheck: true,
wantChecked: true,
},
{
desc: "uncompressed, suffix",
obj: client.Bucket(uncompressedBucket).Object(uncompressedObject),
offset: 1,
length: -1,
readCompressed: false,
wantCheck: false,
wantChecked: false,
},
{
desc: "uncompressed, prefix",
obj: client.Bucket(uncompressedBucket).Object(uncompressedObject),
offset: 0,
length: 18,
readCompressed: false,
wantCheck: false,
wantChecked: false,
},
{
// When a gzipped file is unzipped by GCS, we can't verify the checksum
// because it was computed against the zipped contents. There is no
// header that indicates that a gzipped file is being served unzipped.
// But our CRC check only happens if there is a Content-Length header,
// and that header is absent for this read.
desc: "compressed, entire file, server unzips",
obj: client.Bucket(gzippedBucket).Object(gzippedObject),
offset: 0,
length: -1,
readCompressed: false,
wantCheck: true,
wantChecked: false,
},
{
// When we read a gzipped file uncompressed, it's like reading a regular file:
// the served content and the CRC match.
desc: "compressed, entire file, read compressed",
obj: client.Bucket(gzippedBucket).Object(gzippedObject),
offset: 0,
length: -1,
readCompressed: true,
wantCheck: true,
wantChecked: true,
},
{
desc: "compressed, partial, server unzips",
obj: client.Bucket(gzippedBucket).Object(gzippedObject),
offset: 1,
length: 8,
readCompressed: false,
wantErr: true, // GCS can't serve part of a gzipped object
wantCheck: false,
wantChecked: false,
},
{
desc: "compressed, partial, read compressed",
obj: client.Bucket(gzippedBucket).Object(gzippedObject),
offset: 1,
length: 8,
readCompressed: true,
wantCheck: false,
wantChecked: false,
},
} {
obj := test.obj.ReadCompressed(test.readCompressed)
r, err := obj.NewRangeReader(ctx, test.offset, test.length)
if err != nil {
if test.wantErr {
continue
}
t.Fatalf("%s: %v", test.desc, err)
}
if got, want := r.checkCRC, test.wantCheck; got != want {
t.Errorf("%s, checkCRC: got %t, want %t", test.desc, got, want)
}
_, err = ioutil.ReadAll(r)
_ = r.Close()
if err != nil {
t.Fatalf("%s: %v", test.desc, err)
}
if got, want := r.checkedCRC, test.wantChecked; got != want {
t.Errorf("%s, checkedCRC: got %t, want %t", test.desc, got, want)
}
}
}
func TestIntegration_CancelWrite(t *testing.T) {
// Verify that canceling the writer's context immediately stops uploading an object.
if testing.Short() {
t.Skip("Integration tests skipped in short mode")
}
ctx := context.Background()
client, bucket := testConfig(ctx, t)
defer client.Close()
bkt := client.Bucket(bucket)
cctx, cancel := context.WithCancel(ctx)
defer cancel()
obj := bkt.Object("cancel-write")
w := obj.NewWriter(cctx)
w.ChunkSize = googleapi.MinUploadChunkSize
buf := make([]byte, w.ChunkSize)
// Write the first chunk. This is read in its entirety before sending the request
// (see google.golang.org/api/gensupport.PrepareUpload), so we expect it to return
// without error.
_, err := w.Write(buf)
if err != nil {
t.Fatal(err)
}
// Now cancel the context.
cancel()
// The next Write should return context.Canceled.
_, err = w.Write(buf)
if err != context.Canceled {
t.Fatalf("got %v, wanted context.Canceled", err)
}
// The Close should too.
err = w.Close()
if err != context.Canceled {
t.Fatalf("got %v, wanted context.Canceled", err)
}
}
func writeObject(ctx context.Context, obj *ObjectHandle, contentType string, contents []byte) error {
w := obj.NewWriter(ctx)
w.ContentType = contentType
+24 -10
View File
@@ -24,14 +24,20 @@ var crc32cTable = crc32.MakeTable(crc32.Castagnoli)
// Reader reads a Cloud Storage object.
// It implements io.Reader.
//
// Typically, a Reader computes the CRC of the downloaded content and compares it to
// the stored CRC, returning an error from Read if there is a mismatch. This integrity check
// is skipped if transcoding occurs. See https://cloud.google.com/storage/docs/transcoding.
type Reader struct {
body io.ReadCloser
remain, size int64
contentType string
cacheControl string
checkCRC bool // should we check the CRC?
wantCRC uint32 // the CRC32c value the server sent in the header
gotCRC uint32 // running crc
body io.ReadCloser
remain, size int64
contentType string
contentEncoding string
cacheControl string
checkCRC bool // should we check the CRC?
wantCRC uint32 // the CRC32c value the server sent in the header
gotCRC uint32 // running crc
checkedCRC bool // did we check the CRC? (For tests.)
}
// Close closes the Reader. It must be called when done reading.
@@ -49,9 +55,12 @@ func (r *Reader) Read(p []byte) (int, error) {
// Check CRC here. It would be natural to check it in Close, but
// everybody defers Close on the assumption that it doesn't return
// anything worth looking at.
if r.remain == 0 && r.gotCRC != r.wantCRC {
return n, fmt.Errorf("storage: bad CRC on read: got %d, want %d",
r.gotCRC, r.wantCRC)
if r.remain == 0 { // Only check if we have Content-Length.
r.checkedCRC = true
if r.gotCRC != r.wantCRC {
return n, fmt.Errorf("storage: bad CRC on read: got %d, want %d",
r.gotCRC, r.wantCRC)
}
}
}
return n, err
@@ -74,6 +83,11 @@ func (r *Reader) ContentType() string {
return r.contentType
}
// ContentEncoding returns the content encoding of the object.
func (r *Reader) ContentEncoding() string {
return r.contentEncoding
}
// CacheControl returns the cache control of the object.
func (r *Reader) CacheControl() string {
return r.cacheControl
+84 -15
View File
@@ -30,6 +30,8 @@ import (
"net/http"
"net/url"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"time"
@@ -182,6 +184,60 @@ type SignedURLOptions struct {
MD5 string
}
var (
canonicalHeaderRegexp = regexp.MustCompile(`(?i)^(x-goog-[^:]+):(.*)?$`)
excludedCanonicalHeaders = map[string]bool{
"x-goog-encryption-key": true,
"x-goog-encryption-key-sha256": true,
}
)
// sanitizeHeaders applies the specifications for canonical extension headers at
// https://cloud.google.com/storage/docs/access-control/signed-urls#about-canonical-extension-headers.
func sanitizeHeaders(hdrs []string) []string {
headerMap := map[string][]string{}
for _, hdr := range hdrs {
// No leading or trailing whitespaces.
sanitizedHeader := strings.TrimSpace(hdr)
// Only keep canonical headers, discard any others.
headerMatches := canonicalHeaderRegexp.FindStringSubmatch(sanitizedHeader)
if len(headerMatches) == 0 {
continue
}
header := strings.ToLower(strings.TrimSpace(headerMatches[1]))
if excludedCanonicalHeaders[headerMatches[1]] {
// Do not keep any deliberately excluded canonical headers when signing.
continue
}
value := strings.TrimSpace(headerMatches[2])
if len(value) > 0 {
// Remove duplicate headers by appending the values of duplicates
// in their order of appearance.
headerMap[header] = append(headerMap[header], value)
}
}
var sanitizedHeaders []string
for header, values := range headerMap {
// There should be no spaces around the colon separating the
// header name from the header value or around the values
// themselves. The values should be separated by commas.
// NOTE: The semantics for headers without a value are not clear.
// However from specifications these should be edge-cases
// anyway and we should assume that there will be no
// canonical headers using empty values. Any such headers
// are discarded at the regexp stage above.
sanitizedHeaders = append(
sanitizedHeaders,
fmt.Sprintf("%s:%s", header, strings.Join(values, ",")),
)
}
sort.Strings(sanitizedHeaders)
return sanitizedHeaders
}
// SignedURL returns a URL for the specified object. Signed URLs allow
// the users access to a restricted resource for a limited time without having a
// Google account or signing in. For more information about the signed
@@ -208,6 +264,7 @@ func SignedURL(bucket, name string, opts *SignedURLOptions) (string, error) {
return "", errors.New("storage: invalid MD5 checksum")
}
}
opts.Headers = sanitizeHeaders(opts.Headers)
signBytes := opts.SignBytes
if opts.PrivateKey != nil {
@@ -258,14 +315,15 @@ func SignedURL(bucket, name string, opts *SignedURLOptions) (string, error) {
// ObjectHandle provides operations on an object in a Google Cloud Storage bucket.
// Use BucketHandle.Object to get a handle.
type ObjectHandle struct {
c *Client
bucket string
object string
acl ACLHandle
gen int64 // a negative value indicates latest
conds *Conditions
encryptionKey []byte // AES-256 key
userProject string // for requester-pays buckets
c *Client
bucket string
object string
acl ACLHandle
gen int64 // a negative value indicates latest
conds *Conditions
encryptionKey []byte // AES-256 key
userProject string // for requester-pays buckets
readCompressed bool // Accept-Encoding: gzip
}
// ACL provides access to the object's access control list.
@@ -467,6 +525,13 @@ func (o *ObjectHandle) Delete(ctx context.Context) error {
return err
}
// ReadCompressed when true causes the read to happen without decompressing.
func (o *ObjectHandle) ReadCompressed(compressed bool) *ObjectHandle {
o2 := *o
o2.readCompressed = compressed
return &o2
}
// NewReader creates a new Reader to read the contents of the
// object.
// ErrObjectNotExist will be returned if the object is not found.
@@ -514,6 +579,9 @@ func (o *ObjectHandle) NewRangeReader(ctx context.Context, offset, length int64)
if o.userProject != "" {
req.Header.Set("X-Goog-User-Project", o.userProject)
}
if o.readCompressed {
req.Header.Set("Accept-Encoding", "gzip")
}
if err := setEncryptionHeaders(req.Header, o.encryptionKey, false); err != nil {
return nil, err
}
@@ -576,13 +644,14 @@ func (o *ObjectHandle) NewRangeReader(ctx context.Context, offset, length int64)
crc, checkCRC = parseCRC32c(res)
}
return &Reader{
body: body,
size: size,
remain: remain,
contentType: res.Header.Get("Content-Type"),
cacheControl: res.Header.Get("Cache-Control"),
wantCRC: crc,
checkCRC: checkCRC,
body: body,
size: size,
remain: remain,
contentType: res.Header.Get("Content-Type"),
contentEncoding: res.Header.Get("Content-Encoding"),
cacheControl: res.Header.Get("Cache-Control"),
wantCRC: crc,
checkCRC: checkCRC,
}, nil
}
+66 -19
View File
@@ -40,6 +40,52 @@ import (
raw "google.golang.org/api/storage/v1"
)
func TestHeaderSanitization(t *testing.T) {
t.Parallel()
var tests = []struct {
desc string
in []string
want []string
}{
{
desc: "already sanitized headers should not be modified",
in: []string{"x-goog-header1:true", "x-goog-header2:0"},
want: []string{"x-goog-header1:true", "x-goog-header2:0"},
},
{
desc: "sanitized headers should be sorted",
in: []string{"x-goog-header2:0", "x-goog-header1:true"},
want: []string{"x-goog-header1:true", "x-goog-header2:0"},
},
{
desc: "non-canonical headers should be removed",
in: []string{"x-goog-header1:true", "x-goog-no-value", "non-canonical-header:not-of-use"},
want: []string{"x-goog-header1:true"},
},
{
desc: "excluded canonical headers should be removed",
in: []string{"x-goog-header1:true", "x-goog-encryption-key:my_key", "x-goog-encryption-key-sha256:my_sha256"},
want: []string{"x-goog-header1:true"},
},
{
desc: "dirty headers should be formatted correctly",
in: []string{" x-goog-header1 : \textra-spaces ", "X-Goog-Header2:CamelCaseValue"},
want: []string{"x-goog-header1:extra-spaces", "x-goog-header2:CamelCaseValue"},
},
{
desc: "duplicate headers should be merged",
in: []string{"x-goog-header1:value1", "X-Goog-Header1:value2"},
want: []string{"x-goog-header1:value1,value2"},
},
}
for _, test := range tests {
got := sanitizeHeaders(test.in)
if !testutil.Equal(got, test.want) {
t.Errorf("%s: got %v, want %v", test.desc, got, test.want)
}
}
}
func TestSignedURL(t *testing.T) {
t.Parallel()
expires, _ := time.Parse(time.RFC3339, "2002-10-02T10:00:00-05:00")
@@ -50,20 +96,20 @@ func TestSignedURL(t *testing.T) {
MD5: "ICy5YqxZB1uWSwcVLSNLcA==",
Expires: expires,
ContentType: "application/json",
Headers: []string{"x-header1", "x-header2"},
Headers: []string{"x-goog-header1:true", "x-goog-header2:false"},
})
if err != nil {
t.Error(err)
}
want := "https://storage.googleapis.com/bucket-name/object-name?" +
"Expires=1033570800&GoogleAccessId=xxx%40clientid&Signature=" +
"ZMw18bZVhySNYAMEX87RMyuZCUMtGLVi%2B2zU2ByiQ0Rxgij%2BhFZ5LsT" +
"5ZPIH5h3QXB%2BiSb1URJnZo3aF0exVP%2FYR1hpg2e65w9HHt7yYjIqcg" +
"%2FfAOIyxriFtgRYk3oAv%2FFLF62fI8iF%2BCp0fWSm%2FHggz22blVnQz" +
"EtSP%2BuRhFle4172L%2B710sfMDtyQLKTz6W4TmRjC9ymTi8mVj95dZgyF" +
"RXbibTdtw0JzndE0Ig4c6pU4xDPPiyaziUSVDMIpzZDJH1GYOGHxbFasba4" +
"1rRoWWkdBnsMtHm2ck%2FsFD2leL6u8q0OpVAc4ZdxseucL4OpCy%2BCLhQ" +
"JFQT5bqSljP0g%3D%3D"
"RfsHlPtbB2JUYjzCgNr2Mi%2BjggdEuL1V7E6N9o6aaqwVLBDuTv3I0%2B9" +
"x94E6rmmr%2FVgnmZigkIUxX%2Blfl7LgKf30uPGLt0mjKGH2p7r9ey1ONJ" +
"%2BhVec23FnTRcSgopglvHPuCMWU2oNJE%2F1y8EwWE27baHrG1RhRHbLVF" +
"bPpLZ9xTRFK20pluIkfHV00JGljB1imqQHXM%2B2XPWqBngLr%2FwqxLN7i" +
"FcUiqR8xQEOHF%2F2e7fbkTHPNq4TazaLZ8X0eZ3eFdJ55A5QmNi8atlN4W" +
"5q7Hvs0jcxElG3yqIbx439A995BkspLiAcA%2Fo4%2BxAwEMkGLICdbvakq" +
"3eEprNCojw%3D%3D"
if url != want {
t.Fatalf("Unexpected signed URL; found %v", url)
}
@@ -79,16 +125,17 @@ func TestSignedURL_PEMPrivateKey(t *testing.T) {
MD5: "ICy5YqxZB1uWSwcVLSNLcA==",
Expires: expires,
ContentType: "application/json",
Headers: []string{"x-header1", "x-header2"},
Headers: []string{"x-goog-header1:true", "x-goog-header2:false"},
})
if err != nil {
t.Error(err)
}
want := "https://storage.googleapis.com/bucket-name/object-name?" +
"Expires=1033570800&GoogleAccessId=xxx%40clientid&Signature=" +
"gHlh63sOxJnNj22X%2B%2F4kwOSNMeqwXWr4udEfrzJPQcq1xzxA8ovMM5SOrOc%" +
"2FuE%2Ftc9%2Bq7a42CDBwZff1PsvuJMBDaPbluU257h%2Bvxx8lHMnb%2Bg1wD1" +
"99FiCE014MRH9TlIg%2FdXRkErosVWTy4GqAgZemmKHo0HwDGT6IovB9mdg%3D"
"TiyKD%2FgGb6Kh0kkb2iF%2FfF%2BnTx7L0J4YiZua8AcTmnidutePEGIU5" +
"NULYlrGl6l52gz4zqFb3VFfIRTcPXMdXnnFdMCDhz2QuJBUpsU1Ai9zlyTQ" +
"dkb6ShG03xz9%2BEXWAUQO4GBybJw%2FULASuv37xA00SwLdkqj8YdyS5II" +
"1lro%3D"
if url != want {
t.Fatalf("Unexpected signed URL; found %v", url)
}
@@ -106,7 +153,7 @@ func TestSignedURL_SignBytes(t *testing.T) {
MD5: "ICy5YqxZB1uWSwcVLSNLcA==",
Expires: expires,
ContentType: "application/json",
Headers: []string{"x-header1", "x-header2"},
Headers: []string{"x-goog-header1:true", "x-goog-header2:false"},
})
if err != nil {
t.Error(err)
@@ -129,16 +176,16 @@ func TestSignedURL_URLUnsafeObjectName(t *testing.T) {
MD5: "ICy5YqxZB1uWSwcVLSNLcA==",
Expires: expires,
ContentType: "application/json",
Headers: []string{"x-header1", "x-header2"},
Headers: []string{"x-goog-header1:true", "x-goog-header2:false"},
})
if err != nil {
t.Error(err)
}
want := "https://storage.googleapis.com/bucket-name/object%20name%E7%95%8C?" +
"Expires=1033570800&GoogleAccessId=xxx%40clientid&Signature=" +
"LSxs1YwXNKOa7mQv1ZAI2ao0Fuv6yXLLU7%2BQ97z2B7hYZ57OiFwQ72EdGXSiIM" +
"JwLisEKkwoSlYCMm3uuTdgJtXXVi7SYXMfdeKaonyQwMv531KETCBTSewt8CW%2B" +
"FaUJ5SEYG44SeJCiqeIr3GF7t90UNWs6TdFXDaKShpQzBGg%3D"
"Expires=1033570800&GoogleAccessId=xxx%40clientid&Signature=bxVH1%2Bl%2" +
"BSxpnj3XuqKz6mOFk6M94Y%2B4w85J6FCmJan%2FNhGSpndP6fAw1uLHlOn%2F8xUaY%2F" +
"SfZ5GzcQ%2BbxOL1WA37yIwZ7xgLYlO%2ByAi3GuqMUmHZiNCai28emODXQ8RtWHvgv6dE" +
"SQ%2F0KpDMIWW7rYCaUa63UkUyeSQsKhrVqkIA%3D"
if url != want {
t.Fatalf("Unexpected signed URL; found %v", url)
}
@@ -325,7 +372,7 @@ func TestObjectNames(t *testing.T) {
MD5: "ICy5YqxZB1uWSwcVLSNLcA==",
Expires: time.Date(2002, time.October, 2, 10, 0, 0, 0, time.UTC),
ContentType: "application/json",
Headers: []string{"x-header1", "x-header2"},
Headers: []string{"x-goog-header1", "x-goog-header2"},
}
for _, test := range tests {
+2
View File
@@ -187,6 +187,8 @@ func (w *Writer) Close() error {
// CloseWithError aborts the write operation with the provided error.
// CloseWithError always returns nil.
//
// Deprecated: cancel the context passed to NewWriter instead.
func (w *Writer) CloseWithError(err error) error {
if !w.opened {
return nil