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