1// Copyright 2014 Google LLC 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package storage 16 17import ( 18 "bytes" 19 "context" 20 "crypto" 21 "crypto/rand" 22 "crypto/rsa" 23 "crypto/sha256" 24 "crypto/x509" 25 "encoding/base64" 26 "encoding/hex" 27 "encoding/pem" 28 "errors" 29 "fmt" 30 "net/http" 31 "net/url" 32 "os" 33 "reflect" 34 "regexp" 35 "sort" 36 "strconv" 37 "strings" 38 "time" 39 "unicode/utf8" 40 41 "cloud.google.com/go/internal/optional" 42 "cloud.google.com/go/internal/trace" 43 "cloud.google.com/go/internal/version" 44 "google.golang.org/api/googleapi" 45 "google.golang.org/api/option" 46 raw "google.golang.org/api/storage/v1" 47 htransport "google.golang.org/api/transport/http" 48) 49 50var ( 51 // ErrBucketNotExist indicates that the bucket does not exist. 52 ErrBucketNotExist = errors.New("storage: bucket doesn't exist") 53 // ErrObjectNotExist indicates that the object does not exist. 54 ErrObjectNotExist = errors.New("storage: object doesn't exist") 55) 56 57const userAgent = "gcloud-golang-storage/20151204" 58 59const ( 60 // ScopeFullControl grants permissions to manage your 61 // data and permissions in Google Cloud Storage. 62 ScopeFullControl = raw.DevstorageFullControlScope 63 64 // ScopeReadOnly grants permissions to 65 // view your data in Google Cloud Storage. 66 ScopeReadOnly = raw.DevstorageReadOnlyScope 67 68 // ScopeReadWrite grants permissions to manage your 69 // data in Google Cloud Storage. 70 ScopeReadWrite = raw.DevstorageReadWriteScope 71) 72 73var xGoogHeader = fmt.Sprintf("gl-go/%s gccl/%s", version.Go(), version.Repo) 74 75func setClientHeader(headers http.Header) { 76 headers.Set("x-goog-api-client", xGoogHeader) 77} 78 79// Client is a client for interacting with Google Cloud Storage. 80// 81// Clients should be reused instead of created as needed. 82// The methods of Client are safe for concurrent use by multiple goroutines. 83type Client struct { 84 hc *http.Client 85 raw *raw.Service 86 // Scheme describes the scheme under the current host. 87 scheme string 88 // EnvHost is the host set on the STORAGE_EMULATOR_HOST variable. 89 envHost string 90 // ReadHost is the default host used on the reader. 91 readHost string 92} 93 94// NewClient creates a new Google Cloud Storage client. 95// The default scope is ScopeFullControl. To use a different scope, like ScopeReadOnly, use option.WithScopes. 96func NewClient(ctx context.Context, opts ...option.ClientOption) (*Client, error) { 97 o := []option.ClientOption{ 98 option.WithScopes(ScopeFullControl), 99 option.WithUserAgent(userAgent), 100 } 101 opts = append(o, opts...) 102 hc, ep, err := htransport.NewClient(ctx, opts...) 103 if err != nil { 104 return nil, fmt.Errorf("dialing: %v", err) 105 } 106 rawService, err := raw.New(hc) 107 if err != nil { 108 return nil, fmt.Errorf("storage client: %v", err) 109 } 110 if ep != "" { 111 rawService.BasePath = ep 112 } 113 scheme := "https" 114 var host, readHost string 115 if host = os.Getenv("STORAGE_EMULATOR_HOST"); host != "" { 116 scheme = "http" 117 readHost = host 118 } else { 119 readHost = "storage.googleapis.com" 120 } 121 return &Client{ 122 hc: hc, 123 raw: rawService, 124 scheme: scheme, 125 envHost: host, 126 readHost: readHost, 127 }, nil 128} 129 130// Close closes the Client. 131// 132// Close need not be called at program exit. 133func (c *Client) Close() error { 134 // Set fields to nil so that subsequent uses will panic. 135 c.hc = nil 136 c.raw = nil 137 return nil 138} 139 140// SigningScheme determines the API version to use when signing URLs. 141type SigningScheme int 142 143const ( 144 // SigningSchemeDefault is presently V2 and will change to V4 in the future. 145 SigningSchemeDefault SigningScheme = iota 146 147 // SigningSchemeV2 uses the V2 scheme to sign URLs. 148 SigningSchemeV2 149 150 // SigningSchemeV4 uses the V4 scheme to sign URLs. 151 SigningSchemeV4 152) 153 154// SignedURLOptions allows you to restrict the access to the signed URL. 155type SignedURLOptions struct { 156 // GoogleAccessID represents the authorizer of the signed URL generation. 157 // It is typically the Google service account client email address from 158 // the Google Developers Console in the form of "xxx@developer.gserviceaccount.com". 159 // Required. 160 GoogleAccessID string 161 162 // PrivateKey is the Google service account private key. It is obtainable 163 // from the Google Developers Console. 164 // At https://console.developers.google.com/project/<your-project-id>/apiui/credential, 165 // create a service account client ID or reuse one of your existing service account 166 // credentials. Click on the "Generate new P12 key" to generate and download 167 // a new private key. Once you download the P12 file, use the following command 168 // to convert it into a PEM file. 169 // 170 // $ openssl pkcs12 -in key.p12 -passin pass:notasecret -out key.pem -nodes 171 // 172 // Provide the contents of the PEM file as a byte slice. 173 // Exactly one of PrivateKey or SignBytes must be non-nil. 174 PrivateKey []byte 175 176 // SignBytes is a function for implementing custom signing. For example, if 177 // your application is running on Google App Engine, you can use 178 // appengine's internal signing function: 179 // ctx := appengine.NewContext(request) 180 // acc, _ := appengine.ServiceAccount(ctx) 181 // url, err := SignedURL("bucket", "object", &SignedURLOptions{ 182 // GoogleAccessID: acc, 183 // SignBytes: func(b []byte) ([]byte, error) { 184 // _, signedBytes, err := appengine.SignBytes(ctx, b) 185 // return signedBytes, err 186 // }, 187 // // etc. 188 // }) 189 // 190 // Exactly one of PrivateKey or SignBytes must be non-nil. 191 SignBytes func([]byte) ([]byte, error) 192 193 // Method is the HTTP method to be used with the signed URL. 194 // Signed URLs can be used with GET, HEAD, PUT, and DELETE requests. 195 // Required. 196 Method string 197 198 // Expires is the expiration time on the signed URL. It must be 199 // a datetime in the future. For SigningSchemeV4, the expiration may be no 200 // more than seven days in the future. 201 // Required. 202 Expires time.Time 203 204 // ContentType is the content type header the client must provide 205 // to use the generated signed URL. 206 // Optional. 207 ContentType string 208 209 // Headers is a list of extension headers the client must provide 210 // in order to use the generated signed URL. 211 // Optional. 212 Headers []string 213 214 // MD5 is the base64 encoded MD5 checksum of the file. 215 // If provided, the client should provide the exact value on the request 216 // header in order to use the signed URL. 217 // Optional. 218 MD5 string 219 220 // Scheme determines the version of URL signing to use. Default is 221 // SigningSchemeV2. 222 Scheme SigningScheme 223} 224 225var ( 226 tabRegex = regexp.MustCompile(`[\t]+`) 227 // I was tempted to call this spacex. :) 228 spaceRegex = regexp.MustCompile(` +`) 229 230 canonicalHeaderRegexp = regexp.MustCompile(`(?i)^(x-goog-[^:]+):(.*)?$`) 231 excludedCanonicalHeaders = map[string]bool{ 232 "x-goog-encryption-key": true, 233 "x-goog-encryption-key-sha256": true, 234 } 235) 236 237// v2SanitizeHeaders applies the specifications for canonical extension headers at 238// https://cloud.google.com/storage/docs/access-control/signed-urls#about-canonical-extension-headers. 239func v2SanitizeHeaders(hdrs []string) []string { 240 headerMap := map[string][]string{} 241 for _, hdr := range hdrs { 242 // No leading or trailing whitespaces. 243 sanitizedHeader := strings.TrimSpace(hdr) 244 245 var header, value string 246 // Only keep canonical headers, discard any others. 247 headerMatches := canonicalHeaderRegexp.FindStringSubmatch(sanitizedHeader) 248 if len(headerMatches) == 0 { 249 continue 250 } 251 header = headerMatches[1] 252 value = headerMatches[2] 253 254 header = strings.ToLower(strings.TrimSpace(header)) 255 value = strings.TrimSpace(value) 256 257 if excludedCanonicalHeaders[header] { 258 // Do not keep any deliberately excluded canonical headers when signing. 259 continue 260 } 261 262 if len(value) > 0 { 263 // Remove duplicate headers by appending the values of duplicates 264 // in their order of appearance. 265 headerMap[header] = append(headerMap[header], value) 266 } 267 } 268 269 var sanitizedHeaders []string 270 for header, values := range headerMap { 271 // There should be no spaces around the colon separating the header name 272 // from the header value or around the values themselves. The values 273 // should be separated by commas. 274 // 275 // NOTE: The semantics for headers without a value are not clear. 276 // However from specifications these should be edge-cases anyway and we 277 // should assume that there will be no canonical headers using empty 278 // values. Any such headers are discarded at the regexp stage above. 279 sanitizedHeaders = append(sanitizedHeaders, fmt.Sprintf("%s:%s", header, strings.Join(values, ","))) 280 } 281 sort.Strings(sanitizedHeaders) 282 return sanitizedHeaders 283} 284 285// v4SanitizeHeaders applies the specifications for canonical extension headers 286// at https://cloud.google.com/storage/docs/access-control/signed-urls#about-canonical-extension-headers. 287// 288// V4 does a couple things differently from V2: 289// - Headers get sorted by key, instead of by key:value. We do this in 290// signedURLV4. 291// - There's no canonical regexp: we simply split headers on :. 292// - We don't exclude canonical headers. 293// - We replace leading and trailing spaces in header values, like v2, but also 294// all intermediate space duplicates get stripped. That is, there's only ever 295// a single consecutive space. 296func v4SanitizeHeaders(hdrs []string) []string { 297 headerMap := map[string][]string{} 298 for _, hdr := range hdrs { 299 // No leading or trailing whitespaces. 300 sanitizedHeader := strings.TrimSpace(hdr) 301 302 var key, value string 303 headerMatches := strings.Split(sanitizedHeader, ":") 304 if len(headerMatches) < 2 { 305 continue 306 } 307 308 key = headerMatches[0] 309 value = headerMatches[1] 310 311 key = strings.ToLower(strings.TrimSpace(key)) 312 value = strings.TrimSpace(value) 313 value = string(spaceRegex.ReplaceAll([]byte(value), []byte(" "))) 314 value = string(tabRegex.ReplaceAll([]byte(value), []byte("\t"))) 315 316 if len(value) > 0 { 317 // Remove duplicate headers by appending the values of duplicates 318 // in their order of appearance. 319 headerMap[key] = append(headerMap[key], value) 320 } 321 } 322 323 var sanitizedHeaders []string 324 for header, values := range headerMap { 325 // There should be no spaces around the colon separating the header name 326 // from the header value or around the values themselves. The values 327 // should be separated by commas. 328 // 329 // NOTE: The semantics for headers without a value are not clear. 330 // However from specifications these should be edge-cases anyway and we 331 // should assume that there will be no canonical headers using empty 332 // values. Any such headers are discarded at the regexp stage above. 333 sanitizedHeaders = append(sanitizedHeaders, fmt.Sprintf("%s:%s", header, strings.Join(values, ","))) 334 } 335 return sanitizedHeaders 336} 337 338// SignedURL returns a URL for the specified object. Signed URLs allow 339// the users access to a restricted resource for a limited time without having a 340// Google account or signing in. For more information about the signed 341// URLs, see https://cloud.google.com/storage/docs/accesscontrol#Signed-URLs. 342func SignedURL(bucket, name string, opts *SignedURLOptions) (string, error) { 343 now := utcNow() 344 if err := validateOptions(opts, now); err != nil { 345 return "", err 346 } 347 348 switch opts.Scheme { 349 case SigningSchemeV2: 350 opts.Headers = v2SanitizeHeaders(opts.Headers) 351 return signedURLV2(bucket, name, opts) 352 case SigningSchemeV4: 353 opts.Headers = v4SanitizeHeaders(opts.Headers) 354 return signedURLV4(bucket, name, opts, now) 355 default: // SigningSchemeDefault 356 opts.Headers = v2SanitizeHeaders(opts.Headers) 357 return signedURLV2(bucket, name, opts) 358 } 359} 360 361func validateOptions(opts *SignedURLOptions, now time.Time) error { 362 if opts == nil { 363 return errors.New("storage: missing required SignedURLOptions") 364 } 365 if opts.GoogleAccessID == "" { 366 return errors.New("storage: missing required GoogleAccessID") 367 } 368 if (opts.PrivateKey == nil) == (opts.SignBytes == nil) { 369 return errors.New("storage: exactly one of PrivateKey or SignedBytes must be set") 370 } 371 if opts.Method == "" { 372 return errors.New("storage: missing required method option") 373 } 374 if opts.Expires.IsZero() { 375 return errors.New("storage: missing required expires option") 376 } 377 if opts.MD5 != "" { 378 md5, err := base64.StdEncoding.DecodeString(opts.MD5) 379 if err != nil || len(md5) != 16 { 380 return errors.New("storage: invalid MD5 checksum") 381 } 382 } 383 if opts.Scheme == SigningSchemeV4 { 384 cutoff := now.Add(604801 * time.Second) // 7 days + 1 second 385 if !opts.Expires.Before(cutoff) { 386 return errors.New("storage: expires must be within seven days from now") 387 } 388 } 389 return nil 390} 391 392const ( 393 iso8601 = "20060102T150405Z" 394 yearMonthDay = "20060102" 395) 396 397// utcNow returns the current time in UTC and is a variable to allow for 398// reassignment in tests to provide deterministic signed URL values. 399var utcNow = func() time.Time { 400 return time.Now().UTC() 401} 402 403// extractHeaderNames takes in a series of key:value headers and returns the 404// header names only. 405func extractHeaderNames(kvs []string) []string { 406 var res []string 407 for _, header := range kvs { 408 nameValue := strings.Split(header, ":") 409 res = append(res, nameValue[0]) 410 } 411 return res 412} 413 414// signedURLV4 creates a signed URL using the sigV4 algorithm. 415func signedURLV4(bucket, name string, opts *SignedURLOptions, now time.Time) (string, error) { 416 buf := &bytes.Buffer{} 417 fmt.Fprintf(buf, "%s\n", opts.Method) 418 u := &url.URL{Path: bucket} 419 if name != "" { 420 u.Path += "/" + name 421 } 422 423 // Note: we have to add a / here because GCS does so auto-magically, despite 424 // Go's EscapedPath not doing so (and we have to exactly match their 425 // canonical query). 426 fmt.Fprintf(buf, "/%s\n", u.EscapedPath()) 427 428 headerNames := append(extractHeaderNames(opts.Headers), "host") 429 if opts.ContentType != "" { 430 headerNames = append(headerNames, "content-type") 431 } 432 if opts.MD5 != "" { 433 headerNames = append(headerNames, "content-md5") 434 } 435 sort.Strings(headerNames) 436 signedHeaders := strings.Join(headerNames, ";") 437 timestamp := now.Format(iso8601) 438 credentialScope := fmt.Sprintf("%s/auto/storage/goog4_request", now.Format(yearMonthDay)) 439 canonicalQueryString := url.Values{ 440 "X-Goog-Algorithm": {"GOOG4-RSA-SHA256"}, 441 "X-Goog-Credential": {fmt.Sprintf("%s/%s", opts.GoogleAccessID, credentialScope)}, 442 "X-Goog-Date": {timestamp}, 443 "X-Goog-Expires": {fmt.Sprintf("%d", int(opts.Expires.Sub(now).Seconds()))}, 444 "X-Goog-SignedHeaders": {signedHeaders}, 445 } 446 fmt.Fprintf(buf, "%s\n", canonicalQueryString.Encode()) 447 448 u.Host = "storage.googleapis.com" 449 450 var headersWithValue []string 451 headersWithValue = append(headersWithValue, "host:"+u.Host) 452 headersWithValue = append(headersWithValue, opts.Headers...) 453 if opts.ContentType != "" { 454 headersWithValue = append(headersWithValue, "content-type:"+strings.TrimSpace(opts.ContentType)) 455 } 456 if opts.MD5 != "" { 457 headersWithValue = append(headersWithValue, "content-md5:"+strings.TrimSpace(opts.MD5)) 458 } 459 canonicalHeaders := strings.Join(sortHeadersByKey(headersWithValue), "\n") 460 fmt.Fprintf(buf, "%s\n\n", canonicalHeaders) 461 fmt.Fprintf(buf, "%s\n", signedHeaders) 462 fmt.Fprint(buf, "UNSIGNED-PAYLOAD") 463 464 sum := sha256.Sum256(buf.Bytes()) 465 hexDigest := hex.EncodeToString(sum[:]) 466 signBuf := &bytes.Buffer{} 467 fmt.Fprint(signBuf, "GOOG4-RSA-SHA256\n") 468 fmt.Fprintf(signBuf, "%s\n", timestamp) 469 fmt.Fprintf(signBuf, "%s\n", credentialScope) 470 fmt.Fprintf(signBuf, "%s", hexDigest) 471 472 signBytes := opts.SignBytes 473 if opts.PrivateKey != nil { 474 key, err := parseKey(opts.PrivateKey) 475 if err != nil { 476 return "", err 477 } 478 signBytes = func(b []byte) ([]byte, error) { 479 sum := sha256.Sum256(b) 480 return rsa.SignPKCS1v15( 481 rand.Reader, 482 key, 483 crypto.SHA256, 484 sum[:], 485 ) 486 } 487 } 488 b, err := signBytes(signBuf.Bytes()) 489 if err != nil { 490 return "", err 491 } 492 signature := hex.EncodeToString(b) 493 canonicalQueryString.Set("X-Goog-Signature", string(signature)) 494 u.Scheme = "https" 495 u.RawQuery = canonicalQueryString.Encode() 496 return u.String(), nil 497} 498 499// takes a list of headerKey:headervalue1,headervalue2,etc and sorts by header 500// key. 501func sortHeadersByKey(hdrs []string) []string { 502 headersMap := map[string]string{} 503 var headersKeys []string 504 for _, h := range hdrs { 505 parts := strings.Split(h, ":") 506 k := parts[0] 507 v := parts[1] 508 headersMap[k] = v 509 headersKeys = append(headersKeys, k) 510 } 511 sort.Strings(headersKeys) 512 var sorted []string 513 for _, k := range headersKeys { 514 v := headersMap[k] 515 sorted = append(sorted, fmt.Sprintf("%s:%s", k, v)) 516 } 517 return sorted 518} 519 520func signedURLV2(bucket, name string, opts *SignedURLOptions) (string, error) { 521 signBytes := opts.SignBytes 522 if opts.PrivateKey != nil { 523 key, err := parseKey(opts.PrivateKey) 524 if err != nil { 525 return "", err 526 } 527 signBytes = func(b []byte) ([]byte, error) { 528 sum := sha256.Sum256(b) 529 return rsa.SignPKCS1v15( 530 rand.Reader, 531 key, 532 crypto.SHA256, 533 sum[:], 534 ) 535 } 536 } 537 538 u := &url.URL{ 539 Path: fmt.Sprintf("/%s/%s", bucket, name), 540 } 541 542 buf := &bytes.Buffer{} 543 fmt.Fprintf(buf, "%s\n", opts.Method) 544 fmt.Fprintf(buf, "%s\n", opts.MD5) 545 fmt.Fprintf(buf, "%s\n", opts.ContentType) 546 fmt.Fprintf(buf, "%d\n", opts.Expires.Unix()) 547 if len(opts.Headers) > 0 { 548 fmt.Fprintf(buf, "%s\n", strings.Join(opts.Headers, "\n")) 549 } 550 fmt.Fprintf(buf, "%s", u.String()) 551 552 b, err := signBytes(buf.Bytes()) 553 if err != nil { 554 return "", err 555 } 556 encoded := base64.StdEncoding.EncodeToString(b) 557 u.Scheme = "https" 558 u.Host = "storage.googleapis.com" 559 q := u.Query() 560 q.Set("GoogleAccessId", opts.GoogleAccessID) 561 q.Set("Expires", fmt.Sprintf("%d", opts.Expires.Unix())) 562 q.Set("Signature", string(encoded)) 563 u.RawQuery = q.Encode() 564 return u.String(), nil 565} 566 567// ObjectHandle provides operations on an object in a Google Cloud Storage bucket. 568// Use BucketHandle.Object to get a handle. 569type ObjectHandle struct { 570 c *Client 571 bucket string 572 object string 573 acl ACLHandle 574 gen int64 // a negative value indicates latest 575 conds *Conditions 576 encryptionKey []byte // AES-256 key 577 userProject string // for requester-pays buckets 578 readCompressed bool // Accept-Encoding: gzip 579} 580 581// ACL provides access to the object's access control list. 582// This controls who can read and write this object. 583// This call does not perform any network operations. 584func (o *ObjectHandle) ACL() *ACLHandle { 585 return &o.acl 586} 587 588// Generation returns a new ObjectHandle that operates on a specific generation 589// of the object. 590// By default, the handle operates on the latest generation. Not 591// all operations work when given a specific generation; check the API 592// endpoints at https://cloud.google.com/storage/docs/json_api/ for details. 593func (o *ObjectHandle) Generation(gen int64) *ObjectHandle { 594 o2 := *o 595 o2.gen = gen 596 return &o2 597} 598 599// If returns a new ObjectHandle that applies a set of preconditions. 600// Preconditions already set on the ObjectHandle are ignored. 601// Operations on the new handle will return an error if the preconditions are not 602// satisfied. See https://cloud.google.com/storage/docs/generations-preconditions 603// for more details. 604func (o *ObjectHandle) If(conds Conditions) *ObjectHandle { 605 o2 := *o 606 o2.conds = &conds 607 return &o2 608} 609 610// Key returns a new ObjectHandle that uses the supplied encryption 611// key to encrypt and decrypt the object's contents. 612// 613// Encryption key must be a 32-byte AES-256 key. 614// See https://cloud.google.com/storage/docs/encryption for details. 615func (o *ObjectHandle) Key(encryptionKey []byte) *ObjectHandle { 616 o2 := *o 617 o2.encryptionKey = encryptionKey 618 return &o2 619} 620 621// Attrs returns meta information about the object. 622// ErrObjectNotExist will be returned if the object is not found. 623func (o *ObjectHandle) Attrs(ctx context.Context) (attrs *ObjectAttrs, err error) { 624 ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Object.Attrs") 625 defer func() { trace.EndSpan(ctx, err) }() 626 627 if err := o.validate(); err != nil { 628 return nil, err 629 } 630 call := o.c.raw.Objects.Get(o.bucket, o.object).Projection("full").Context(ctx) 631 if err := applyConds("Attrs", o.gen, o.conds, call); err != nil { 632 return nil, err 633 } 634 if o.userProject != "" { 635 call.UserProject(o.userProject) 636 } 637 if err := setEncryptionHeaders(call.Header(), o.encryptionKey, false); err != nil { 638 return nil, err 639 } 640 var obj *raw.Object 641 setClientHeader(call.Header()) 642 err = runWithRetry(ctx, func() error { obj, err = call.Do(); return err }) 643 if e, ok := err.(*googleapi.Error); ok && e.Code == http.StatusNotFound { 644 return nil, ErrObjectNotExist 645 } 646 if err != nil { 647 return nil, err 648 } 649 return newObject(obj), nil 650} 651 652// Update updates an object with the provided attributes. 653// All zero-value attributes are ignored. 654// ErrObjectNotExist will be returned if the object is not found. 655func (o *ObjectHandle) Update(ctx context.Context, uattrs ObjectAttrsToUpdate) (oa *ObjectAttrs, err error) { 656 ctx = trace.StartSpan(ctx, "cloud.google.com/go/storage.Object.Update") 657 defer func() { trace.EndSpan(ctx, err) }() 658 659 if err := o.validate(); err != nil { 660 return nil, err 661 } 662 var attrs ObjectAttrs 663 // Lists of fields to send, and set to null, in the JSON. 664 var forceSendFields, nullFields []string 665 if uattrs.ContentType != nil { 666 attrs.ContentType = optional.ToString(uattrs.ContentType) 667 // For ContentType, sending the empty string is a no-op. 668 // Instead we send a null. 669 if attrs.ContentType == "" { 670 nullFields = append(nullFields, "ContentType") 671 } else { 672 forceSendFields = append(forceSendFields, "ContentType") 673 } 674 } 675 if uattrs.ContentLanguage != nil { 676 attrs.ContentLanguage = optional.ToString(uattrs.ContentLanguage) 677 // For ContentLanguage it's an error to send the empty string. 678 // Instead we send a null. 679 if attrs.ContentLanguage == "" { 680 nullFields = append(nullFields, "ContentLanguage") 681 } else { 682 forceSendFields = append(forceSendFields, "ContentLanguage") 683 } 684 } 685 if uattrs.ContentEncoding != nil { 686 attrs.ContentEncoding = optional.ToString(uattrs.ContentEncoding) 687 forceSendFields = append(forceSendFields, "ContentEncoding") 688 } 689 if uattrs.ContentDisposition != nil { 690 attrs.ContentDisposition = optional.ToString(uattrs.ContentDisposition) 691 forceSendFields = append(forceSendFields, "ContentDisposition") 692 } 693 if uattrs.CacheControl != nil { 694 attrs.CacheControl = optional.ToString(uattrs.CacheControl) 695 forceSendFields = append(forceSendFields, "CacheControl") 696 } 697 if uattrs.EventBasedHold != nil { 698 attrs.EventBasedHold = optional.ToBool(uattrs.EventBasedHold) 699 forceSendFields = append(forceSendFields, "EventBasedHold") 700 } 701 if uattrs.TemporaryHold != nil { 702 attrs.TemporaryHold = optional.ToBool(uattrs.TemporaryHold) 703 forceSendFields = append(forceSendFields, "TemporaryHold") 704 } 705 if uattrs.Metadata != nil { 706 attrs.Metadata = uattrs.Metadata 707 if len(attrs.Metadata) == 0 { 708 // Sending the empty map is a no-op. We send null instead. 709 nullFields = append(nullFields, "Metadata") 710 } else { 711 forceSendFields = append(forceSendFields, "Metadata") 712 } 713 } 714 if uattrs.ACL != nil { 715 attrs.ACL = uattrs.ACL 716 // It's an error to attempt to delete the ACL, so 717 // we don't append to nullFields here. 718 forceSendFields = append(forceSendFields, "Acl") 719 } 720 rawObj := attrs.toRawObject(o.bucket) 721 rawObj.ForceSendFields = forceSendFields 722 rawObj.NullFields = nullFields 723 call := o.c.raw.Objects.Patch(o.bucket, o.object, rawObj).Projection("full").Context(ctx) 724 if err := applyConds("Update", o.gen, o.conds, call); err != nil { 725 return nil, err 726 } 727 if o.userProject != "" { 728 call.UserProject(o.userProject) 729 } 730 if uattrs.PredefinedACL != "" { 731 call.PredefinedAcl(uattrs.PredefinedACL) 732 } 733 if err := setEncryptionHeaders(call.Header(), o.encryptionKey, false); err != nil { 734 return nil, err 735 } 736 var obj *raw.Object 737 setClientHeader(call.Header()) 738 err = runWithRetry(ctx, func() error { obj, err = call.Do(); return err }) 739 if e, ok := err.(*googleapi.Error); ok && e.Code == http.StatusNotFound { 740 return nil, ErrObjectNotExist 741 } 742 if err != nil { 743 return nil, err 744 } 745 return newObject(obj), nil 746} 747 748// BucketName returns the name of the bucket. 749func (o *ObjectHandle) BucketName() string { 750 return o.bucket 751} 752 753// ObjectName returns the name of the object. 754func (o *ObjectHandle) ObjectName() string { 755 return o.object 756} 757 758// ObjectAttrsToUpdate is used to update the attributes of an object. 759// Only fields set to non-nil values will be updated. 760// Set a field to its zero value to delete it. 761// 762// For example, to change ContentType and delete ContentEncoding and 763// Metadata, use 764// ObjectAttrsToUpdate{ 765// ContentType: "text/html", 766// ContentEncoding: "", 767// Metadata: map[string]string{}, 768// } 769type ObjectAttrsToUpdate struct { 770 EventBasedHold optional.Bool 771 TemporaryHold optional.Bool 772 ContentType optional.String 773 ContentLanguage optional.String 774 ContentEncoding optional.String 775 ContentDisposition optional.String 776 CacheControl optional.String 777 Metadata map[string]string // set to map[string]string{} to delete 778 ACL []ACLRule 779 780 // If not empty, applies a predefined set of access controls. ACL must be nil. 781 // See https://cloud.google.com/storage/docs/json_api/v1/objects/patch. 782 PredefinedACL string 783} 784 785// Delete deletes the single specified object. 786func (o *ObjectHandle) Delete(ctx context.Context) error { 787 if err := o.validate(); err != nil { 788 return err 789 } 790 call := o.c.raw.Objects.Delete(o.bucket, o.object).Context(ctx) 791 if err := applyConds("Delete", o.gen, o.conds, call); err != nil { 792 return err 793 } 794 if o.userProject != "" { 795 call.UserProject(o.userProject) 796 } 797 // Encryption doesn't apply to Delete. 798 setClientHeader(call.Header()) 799 err := runWithRetry(ctx, func() error { return call.Do() }) 800 switch e := err.(type) { 801 case nil: 802 return nil 803 case *googleapi.Error: 804 if e.Code == http.StatusNotFound { 805 return ErrObjectNotExist 806 } 807 } 808 return err 809} 810 811// ReadCompressed when true causes the read to happen without decompressing. 812func (o *ObjectHandle) ReadCompressed(compressed bool) *ObjectHandle { 813 o2 := *o 814 o2.readCompressed = compressed 815 return &o2 816} 817 818// NewWriter returns a storage Writer that writes to the GCS object 819// associated with this ObjectHandle. 820// 821// A new object will be created unless an object with this name already exists. 822// Otherwise any previous object with the same name will be replaced. 823// The object will not be available (and any previous object will remain) 824// until Close has been called. 825// 826// Attributes can be set on the object by modifying the returned Writer's 827// ObjectAttrs field before the first call to Write. If no ContentType 828// attribute is specified, the content type will be automatically sniffed 829// using net/http.DetectContentType. 830// 831// It is the caller's responsibility to call Close when writing is done. To 832// stop writing without saving the data, cancel the context. 833func (o *ObjectHandle) NewWriter(ctx context.Context) *Writer { 834 return &Writer{ 835 ctx: ctx, 836 o: o, 837 donec: make(chan struct{}), 838 ObjectAttrs: ObjectAttrs{Name: o.object}, 839 ChunkSize: googleapi.DefaultUploadChunkSize, 840 } 841} 842 843func (o *ObjectHandle) validate() error { 844 if o.bucket == "" { 845 return errors.New("storage: bucket name is empty") 846 } 847 if o.object == "" { 848 return errors.New("storage: object name is empty") 849 } 850 if !utf8.ValidString(o.object) { 851 return fmt.Errorf("storage: object name %q is not valid UTF-8", o.object) 852 } 853 return nil 854} 855 856// parseKey converts the binary contents of a private key file to an 857// *rsa.PrivateKey. It detects whether the private key is in a PEM container or 858// not. If so, it extracts the private key from PEM container before 859// conversion. It only supports PEM containers with no passphrase. 860func parseKey(key []byte) (*rsa.PrivateKey, error) { 861 if block, _ := pem.Decode(key); block != nil { 862 key = block.Bytes 863 } 864 parsedKey, err := x509.ParsePKCS8PrivateKey(key) 865 if err != nil { 866 parsedKey, err = x509.ParsePKCS1PrivateKey(key) 867 if err != nil { 868 return nil, err 869 } 870 } 871 parsed, ok := parsedKey.(*rsa.PrivateKey) 872 if !ok { 873 return nil, errors.New("oauth2: private key is invalid") 874 } 875 return parsed, nil 876} 877 878// toRawObject copies the editable attributes from o to the raw library's Object type. 879func (o *ObjectAttrs) toRawObject(bucket string) *raw.Object { 880 var ret string 881 if !o.RetentionExpirationTime.IsZero() { 882 ret = o.RetentionExpirationTime.Format(time.RFC3339) 883 } 884 return &raw.Object{ 885 Bucket: bucket, 886 Name: o.Name, 887 EventBasedHold: o.EventBasedHold, 888 TemporaryHold: o.TemporaryHold, 889 RetentionExpirationTime: ret, 890 ContentType: o.ContentType, 891 ContentEncoding: o.ContentEncoding, 892 ContentLanguage: o.ContentLanguage, 893 CacheControl: o.CacheControl, 894 ContentDisposition: o.ContentDisposition, 895 StorageClass: o.StorageClass, 896 Acl: toRawObjectACL(o.ACL), 897 Metadata: o.Metadata, 898 } 899} 900 901// ObjectAttrs represents the metadata for a Google Cloud Storage (GCS) object. 902type ObjectAttrs struct { 903 // Bucket is the name of the bucket containing this GCS object. 904 // This field is read-only. 905 Bucket string 906 907 // Name is the name of the object within the bucket. 908 // This field is read-only. 909 Name string 910 911 // ContentType is the MIME type of the object's content. 912 ContentType string 913 914 // ContentLanguage is the content language of the object's content. 915 ContentLanguage string 916 917 // CacheControl is the Cache-Control header to be sent in the response 918 // headers when serving the object data. 919 CacheControl string 920 921 // EventBasedHold specifies whether an object is under event-based hold. New 922 // objects created in a bucket whose DefaultEventBasedHold is set will 923 // default to that value. 924 EventBasedHold bool 925 926 // TemporaryHold specifies whether an object is under temporary hold. While 927 // this flag is set to true, the object is protected against deletion and 928 // overwrites. 929 TemporaryHold bool 930 931 // RetentionExpirationTime is a server-determined value that specifies the 932 // earliest time that the object's retention period expires. 933 // This is a read-only field. 934 RetentionExpirationTime time.Time 935 936 // ACL is the list of access control rules for the object. 937 ACL []ACLRule 938 939 // If not empty, applies a predefined set of access controls. It should be set 940 // only when writing, copying or composing an object. When copying or composing, 941 // it acts as the destinationPredefinedAcl parameter. 942 // PredefinedACL is always empty for ObjectAttrs returned from the service. 943 // See https://cloud.google.com/storage/docs/json_api/v1/objects/insert 944 // for valid values. 945 PredefinedACL string 946 947 // Owner is the owner of the object. This field is read-only. 948 // 949 // If non-zero, it is in the form of "user-<userId>". 950 Owner string 951 952 // Size is the length of the object's content. This field is read-only. 953 Size int64 954 955 // ContentEncoding is the encoding of the object's content. 956 ContentEncoding string 957 958 // ContentDisposition is the optional Content-Disposition header of the object 959 // sent in the response headers. 960 ContentDisposition string 961 962 // MD5 is the MD5 hash of the object's content. This field is read-only, 963 // except when used from a Writer. If set on a Writer, the uploaded 964 // data is rejected if its MD5 hash does not match this field. 965 MD5 []byte 966 967 // CRC32C is the CRC32 checksum of the object's content using 968 // the Castagnoli93 polynomial. This field is read-only, except when 969 // used from a Writer. If set on a Writer and Writer.SendCRC32C 970 // is true, the uploaded data is rejected if its CRC32c hash does not 971 // match this field. 972 CRC32C uint32 973 974 // MediaLink is an URL to the object's content. This field is read-only. 975 MediaLink string 976 977 // Metadata represents user-provided metadata, in key/value pairs. 978 // It can be nil if no metadata is provided. 979 Metadata map[string]string 980 981 // Generation is the generation number of the object's content. 982 // This field is read-only. 983 Generation int64 984 985 // Metageneration is the version of the metadata for this 986 // object at this generation. This field is used for preconditions 987 // and for detecting changes in metadata. A metageneration number 988 // is only meaningful in the context of a particular generation 989 // of a particular object. This field is read-only. 990 Metageneration int64 991 992 // StorageClass is the storage class of the object. 993 // This value defines how objects in the bucket are stored and 994 // determines the SLA and the cost of storage. Typical values are 995 // "MULTI_REGIONAL", "REGIONAL", "NEARLINE", "COLDLINE", "STANDARD" 996 // and "DURABLE_REDUCED_AVAILABILITY". 997 // It defaults to "STANDARD", which is equivalent to "MULTI_REGIONAL" 998 // or "REGIONAL" depending on the bucket's location settings. 999 StorageClass string 1000 1001 // Created is the time the object was created. This field is read-only. 1002 Created time.Time 1003 1004 // Deleted is the time the object was deleted. 1005 // If not deleted, it is the zero value. This field is read-only. 1006 Deleted time.Time 1007 1008 // Updated is the creation or modification time of the object. 1009 // For buckets with versioning enabled, changing an object's 1010 // metadata does not change this property. This field is read-only. 1011 Updated time.Time 1012 1013 // CustomerKeySHA256 is the base64-encoded SHA-256 hash of the 1014 // customer-supplied encryption key for the object. It is empty if there is 1015 // no customer-supplied encryption key. 1016 // See // https://cloud.google.com/storage/docs/encryption for more about 1017 // encryption in Google Cloud Storage. 1018 CustomerKeySHA256 string 1019 1020 // Cloud KMS key name, in the form 1021 // projects/P/locations/L/keyRings/R/cryptoKeys/K, used to encrypt this object, 1022 // if the object is encrypted by such a key. 1023 // 1024 // Providing both a KMSKeyName and a customer-supplied encryption key (via 1025 // ObjectHandle.Key) will result in an error when writing an object. 1026 KMSKeyName string 1027 1028 // Prefix is set only for ObjectAttrs which represent synthetic "directory 1029 // entries" when iterating over buckets using Query.Delimiter. See 1030 // ObjectIterator.Next. When set, no other fields in ObjectAttrs will be 1031 // populated. 1032 Prefix string 1033 1034 // Etag is the HTTP/1.1 Entity tag for the object. 1035 // This field is read-only. 1036 Etag string 1037} 1038 1039// convertTime converts a time in RFC3339 format to time.Time. 1040// If any error occurs in parsing, the zero-value time.Time is silently returned. 1041func convertTime(t string) time.Time { 1042 var r time.Time 1043 if t != "" { 1044 r, _ = time.Parse(time.RFC3339, t) 1045 } 1046 return r 1047} 1048 1049func newObject(o *raw.Object) *ObjectAttrs { 1050 if o == nil { 1051 return nil 1052 } 1053 owner := "" 1054 if o.Owner != nil { 1055 owner = o.Owner.Entity 1056 } 1057 md5, _ := base64.StdEncoding.DecodeString(o.Md5Hash) 1058 crc32c, _ := decodeUint32(o.Crc32c) 1059 var sha256 string 1060 if o.CustomerEncryption != nil { 1061 sha256 = o.CustomerEncryption.KeySha256 1062 } 1063 return &ObjectAttrs{ 1064 Bucket: o.Bucket, 1065 Name: o.Name, 1066 ContentType: o.ContentType, 1067 ContentLanguage: o.ContentLanguage, 1068 CacheControl: o.CacheControl, 1069 EventBasedHold: o.EventBasedHold, 1070 TemporaryHold: o.TemporaryHold, 1071 RetentionExpirationTime: convertTime(o.RetentionExpirationTime), 1072 ACL: toObjectACLRules(o.Acl), 1073 Owner: owner, 1074 ContentEncoding: o.ContentEncoding, 1075 ContentDisposition: o.ContentDisposition, 1076 Size: int64(o.Size), 1077 MD5: md5, 1078 CRC32C: crc32c, 1079 MediaLink: o.MediaLink, 1080 Metadata: o.Metadata, 1081 Generation: o.Generation, 1082 Metageneration: o.Metageneration, 1083 StorageClass: o.StorageClass, 1084 CustomerKeySHA256: sha256, 1085 KMSKeyName: o.KmsKeyName, 1086 Created: convertTime(o.TimeCreated), 1087 Deleted: convertTime(o.TimeDeleted), 1088 Updated: convertTime(o.Updated), 1089 Etag: o.Etag, 1090 } 1091} 1092 1093// Decode a uint32 encoded in Base64 in big-endian byte order. 1094func decodeUint32(b64 string) (uint32, error) { 1095 d, err := base64.StdEncoding.DecodeString(b64) 1096 if err != nil { 1097 return 0, err 1098 } 1099 if len(d) != 4 { 1100 return 0, fmt.Errorf("storage: %q does not encode a 32-bit value", d) 1101 } 1102 return uint32(d[0])<<24 + uint32(d[1])<<16 + uint32(d[2])<<8 + uint32(d[3]), nil 1103} 1104 1105// Encode a uint32 as Base64 in big-endian byte order. 1106func encodeUint32(u uint32) string { 1107 b := []byte{byte(u >> 24), byte(u >> 16), byte(u >> 8), byte(u)} 1108 return base64.StdEncoding.EncodeToString(b) 1109} 1110 1111// Query represents a query to filter objects from a bucket. 1112type Query struct { 1113 // Delimiter returns results in a directory-like fashion. 1114 // Results will contain only objects whose names, aside from the 1115 // prefix, do not contain delimiter. Objects whose names, 1116 // aside from the prefix, contain delimiter will have their name, 1117 // truncated after the delimiter, returned in prefixes. 1118 // Duplicate prefixes are omitted. 1119 // Optional. 1120 Delimiter string 1121 1122 // Prefix is the prefix filter to query objects 1123 // whose names begin with this prefix. 1124 // Optional. 1125 Prefix string 1126 1127 // Versions indicates whether multiple versions of the same 1128 // object will be included in the results. 1129 Versions bool 1130} 1131 1132// Conditions constrain methods to act on specific generations of 1133// objects. 1134// 1135// The zero value is an empty set of constraints. Not all conditions or 1136// combinations of conditions are applicable to all methods. 1137// See https://cloud.google.com/storage/docs/generations-preconditions 1138// for details on how these operate. 1139type Conditions struct { 1140 // Generation constraints. 1141 // At most one of the following can be set to a non-zero value. 1142 1143 // GenerationMatch specifies that the object must have the given generation 1144 // for the operation to occur. 1145 // If GenerationMatch is zero, it has no effect. 1146 // Use DoesNotExist to specify that the object does not exist in the bucket. 1147 GenerationMatch int64 1148 1149 // GenerationNotMatch specifies that the object must not have the given 1150 // generation for the operation to occur. 1151 // If GenerationNotMatch is zero, it has no effect. 1152 GenerationNotMatch int64 1153 1154 // DoesNotExist specifies that the object must not exist in the bucket for 1155 // the operation to occur. 1156 // If DoesNotExist is false, it has no effect. 1157 DoesNotExist bool 1158 1159 // Metadata generation constraints. 1160 // At most one of the following can be set to a non-zero value. 1161 1162 // MetagenerationMatch specifies that the object must have the given 1163 // metageneration for the operation to occur. 1164 // If MetagenerationMatch is zero, it has no effect. 1165 MetagenerationMatch int64 1166 1167 // MetagenerationNotMatch specifies that the object must not have the given 1168 // metageneration for the operation to occur. 1169 // If MetagenerationNotMatch is zero, it has no effect. 1170 MetagenerationNotMatch int64 1171} 1172 1173func (c *Conditions) validate(method string) error { 1174 if *c == (Conditions{}) { 1175 return fmt.Errorf("storage: %s: empty conditions", method) 1176 } 1177 if !c.isGenerationValid() { 1178 return fmt.Errorf("storage: %s: multiple conditions specified for generation", method) 1179 } 1180 if !c.isMetagenerationValid() { 1181 return fmt.Errorf("storage: %s: multiple conditions specified for metageneration", method) 1182 } 1183 return nil 1184} 1185 1186func (c *Conditions) isGenerationValid() bool { 1187 n := 0 1188 if c.GenerationMatch != 0 { 1189 n++ 1190 } 1191 if c.GenerationNotMatch != 0 { 1192 n++ 1193 } 1194 if c.DoesNotExist { 1195 n++ 1196 } 1197 return n <= 1 1198} 1199 1200func (c *Conditions) isMetagenerationValid() bool { 1201 return c.MetagenerationMatch == 0 || c.MetagenerationNotMatch == 0 1202} 1203 1204// applyConds modifies the provided call using the conditions in conds. 1205// call is something that quacks like a *raw.WhateverCall. 1206func applyConds(method string, gen int64, conds *Conditions, call interface{}) error { 1207 cval := reflect.ValueOf(call) 1208 if gen >= 0 { 1209 if !setConditionField(cval, "Generation", gen) { 1210 return fmt.Errorf("storage: %s: generation not supported", method) 1211 } 1212 } 1213 if conds == nil { 1214 return nil 1215 } 1216 if err := conds.validate(method); err != nil { 1217 return err 1218 } 1219 switch { 1220 case conds.GenerationMatch != 0: 1221 if !setConditionField(cval, "IfGenerationMatch", conds.GenerationMatch) { 1222 return fmt.Errorf("storage: %s: ifGenerationMatch not supported", method) 1223 } 1224 case conds.GenerationNotMatch != 0: 1225 if !setConditionField(cval, "IfGenerationNotMatch", conds.GenerationNotMatch) { 1226 return fmt.Errorf("storage: %s: ifGenerationNotMatch not supported", method) 1227 } 1228 case conds.DoesNotExist: 1229 if !setConditionField(cval, "IfGenerationMatch", int64(0)) { 1230 return fmt.Errorf("storage: %s: DoesNotExist not supported", method) 1231 } 1232 } 1233 switch { 1234 case conds.MetagenerationMatch != 0: 1235 if !setConditionField(cval, "IfMetagenerationMatch", conds.MetagenerationMatch) { 1236 return fmt.Errorf("storage: %s: ifMetagenerationMatch not supported", method) 1237 } 1238 case conds.MetagenerationNotMatch != 0: 1239 if !setConditionField(cval, "IfMetagenerationNotMatch", conds.MetagenerationNotMatch) { 1240 return fmt.Errorf("storage: %s: ifMetagenerationNotMatch not supported", method) 1241 } 1242 } 1243 return nil 1244} 1245 1246func applySourceConds(gen int64, conds *Conditions, call *raw.ObjectsRewriteCall) error { 1247 if gen >= 0 { 1248 call.SourceGeneration(gen) 1249 } 1250 if conds == nil { 1251 return nil 1252 } 1253 if err := conds.validate("CopyTo source"); err != nil { 1254 return err 1255 } 1256 switch { 1257 case conds.GenerationMatch != 0: 1258 call.IfSourceGenerationMatch(conds.GenerationMatch) 1259 case conds.GenerationNotMatch != 0: 1260 call.IfSourceGenerationNotMatch(conds.GenerationNotMatch) 1261 case conds.DoesNotExist: 1262 call.IfSourceGenerationMatch(0) 1263 } 1264 switch { 1265 case conds.MetagenerationMatch != 0: 1266 call.IfSourceMetagenerationMatch(conds.MetagenerationMatch) 1267 case conds.MetagenerationNotMatch != 0: 1268 call.IfSourceMetagenerationNotMatch(conds.MetagenerationNotMatch) 1269 } 1270 return nil 1271} 1272 1273// setConditionField sets a field on a *raw.WhateverCall. 1274// We can't use anonymous interfaces because the return type is 1275// different, since the field setters are builders. 1276func setConditionField(call reflect.Value, name string, value interface{}) bool { 1277 m := call.MethodByName(name) 1278 if !m.IsValid() { 1279 return false 1280 } 1281 m.Call([]reflect.Value{reflect.ValueOf(value)}) 1282 return true 1283} 1284 1285// conditionsQuery returns the generation and conditions as a URL query 1286// string suitable for URL.RawQuery. It assumes that the conditions 1287// have been validated. 1288func conditionsQuery(gen int64, conds *Conditions) string { 1289 // URL escapes are elided because integer strings are URL-safe. 1290 var buf []byte 1291 1292 appendParam := func(s string, n int64) { 1293 if len(buf) > 0 { 1294 buf = append(buf, '&') 1295 } 1296 buf = append(buf, s...) 1297 buf = strconv.AppendInt(buf, n, 10) 1298 } 1299 1300 if gen >= 0 { 1301 appendParam("generation=", gen) 1302 } 1303 if conds == nil { 1304 return string(buf) 1305 } 1306 switch { 1307 case conds.GenerationMatch != 0: 1308 appendParam("ifGenerationMatch=", conds.GenerationMatch) 1309 case conds.GenerationNotMatch != 0: 1310 appendParam("ifGenerationNotMatch=", conds.GenerationNotMatch) 1311 case conds.DoesNotExist: 1312 appendParam("ifGenerationMatch=", 0) 1313 } 1314 switch { 1315 case conds.MetagenerationMatch != 0: 1316 appendParam("ifMetagenerationMatch=", conds.MetagenerationMatch) 1317 case conds.MetagenerationNotMatch != 0: 1318 appendParam("ifMetagenerationNotMatch=", conds.MetagenerationNotMatch) 1319 } 1320 return string(buf) 1321} 1322 1323// composeSourceObj wraps a *raw.ComposeRequestSourceObjects, but adds the methods 1324// that modifyCall searches for by name. 1325type composeSourceObj struct { 1326 src *raw.ComposeRequestSourceObjects 1327} 1328 1329func (c composeSourceObj) Generation(gen int64) { 1330 c.src.Generation = gen 1331} 1332 1333func (c composeSourceObj) IfGenerationMatch(gen int64) { 1334 // It's safe to overwrite ObjectPreconditions, since its only field is 1335 // IfGenerationMatch. 1336 c.src.ObjectPreconditions = &raw.ComposeRequestSourceObjectsObjectPreconditions{ 1337 IfGenerationMatch: gen, 1338 } 1339} 1340 1341func setEncryptionHeaders(headers http.Header, key []byte, copySource bool) error { 1342 if key == nil { 1343 return nil 1344 } 1345 // TODO(jbd): Ask the API team to return a more user-friendly error 1346 // and avoid doing this check at the client level. 1347 if len(key) != 32 { 1348 return errors.New("storage: not a 32-byte AES-256 key") 1349 } 1350 var cs string 1351 if copySource { 1352 cs = "copy-source-" 1353 } 1354 headers.Set("x-goog-"+cs+"encryption-algorithm", "AES256") 1355 headers.Set("x-goog-"+cs+"encryption-key", base64.StdEncoding.EncodeToString(key)) 1356 keyHash := sha256.Sum256(key) 1357 headers.Set("x-goog-"+cs+"encryption-key-sha256", base64.StdEncoding.EncodeToString(keyHash[:])) 1358 return nil 1359} 1360 1361// ServiceAccount fetches the email address of the given project's Google Cloud Storage service account. 1362func (c *Client) ServiceAccount(ctx context.Context, projectID string) (string, error) { 1363 r := c.raw.Projects.ServiceAccount.Get(projectID) 1364 res, err := r.Context(ctx).Do() 1365 if err != nil { 1366 return "", err 1367 } 1368 return res.EmailAddress, nil 1369} 1370