Source File
storage.go
Belonging Package
cloud.google.com/go/storage
package storage
import (
raw
htransport
)
ScopeReadWrite = raw.DevstorageReadWriteScope
)
var xGoogHeader = fmt.Sprintf("gl-go/%s gccl/%s", version.Go(), version.Repo)
func ( http.Header) {
.Set("x-goog-api-client", xGoogHeader)
}
= append([]option.ClientOption{option.WithScopes(ScopeFullControl), option.WithUserAgent(userAgent)}, ...)
} else {
= "http"
=
= append([]option.ClientOption{option.WithoutAuthentication()}, ...)
}
, , := htransport.NewClient(, ...)
if != nil {
return nil, fmt.Errorf("dialing: %v", )
}
, := raw.NewService(, option.WithHTTPClient())
if != nil {
return nil, fmt.Errorf("storage client: %v", )
}
.BasePath = "https://storage.googleapis.com/storage/v1/"
type SigningScheme int
path(bucket, object string) string
}
type pathStyle struct{}
type virtualHostedStyle struct{}
type bucketBoundHostname struct {
hostname string
}
func ( pathStyle) ( string) string {
return "storage.googleapis.com"
}
func ( virtualHostedStyle) ( string) string {
return + ".storage.googleapis.com"
}
func ( bucketBoundHostname) ( string) string {
return .hostname
}
func ( pathStyle) (, string) string {
:=
if != "" {
+= "/" +
}
return
}
func ( virtualHostedStyle) (, string) string {
return
}
func ( bucketBoundHostname) (, string) string {
return
}
func () URLStyle {
return virtualHostedStyle{}
}
func ( string) URLStyle {
return bucketBoundHostname{hostname: }
}
Scheme SigningScheme
}
var (
spaceRegex = regexp.MustCompile(` +`)
canonicalHeaderRegexp = regexp.MustCompile(`(?i)^(x-goog-[^:]+):(.*)?$`)
excludedCanonicalHeaders = map[string]bool{
"x-goog-encryption-key": true,
"x-goog-encryption-key-sha256": true,
}
)
:= canonicalHeaderRegexp.FindStringSubmatch()
if len() == 0 {
continue
}
= [1]
= [2]
= strings.ToLower(strings.TrimSpace())
= strings.TrimSpace()
continue
}
func (, string, *SignedURLOptions) (string, error) {
:= utcNow()
if := validateOptions(, ); != nil {
return "",
}
switch .Scheme {
case SigningSchemeV2:
.Headers = v2SanitizeHeaders(.Headers)
return signedURLV2(, , )
case SigningSchemeV4:
.Headers = v4SanitizeHeaders(.Headers)
return signedURLV4(, , , )
default: // SigningSchemeDefault
.Headers = v2SanitizeHeaders(.Headers)
return signedURLV2(, , )
}
}
func ( *SignedURLOptions, time.Time) error {
if == nil {
return errors.New("storage: missing required SignedURLOptions")
}
if .GoogleAccessID == "" {
return errors.New("storage: missing required GoogleAccessID")
}
if (.PrivateKey == nil) == (.SignBytes == nil) {
return errors.New("storage: exactly one of PrivateKey or SignedBytes must be set")
}
.Method = strings.ToUpper(.Method)
if , := signedURLMethods[.Method]; ! {
return errMethodNotValid
}
if .Expires.IsZero() {
return errors.New("storage: missing required expires option")
}
if .MD5 != "" {
, := base64.StdEncoding.DecodeString(.MD5)
if != nil || len() != 16 {
return errors.New("storage: invalid MD5 checksum")
}
}
if .Style == nil {
.Style = PathStyle()
}
if , := .Style.(pathStyle); ! && .Scheme == SigningSchemeV2 {
return errors.New("storage: only path-style URLs are permitted with SigningSchemeV2")
}
if .Scheme == SigningSchemeV4 {
:= .Add(604801 * time.Second) // 7 days + 1 second
if !.Expires.Before() {
return errors.New("storage: expires must be within seven days from now")
}
}
return nil
}
const (
iso8601 = "20060102T150405Z"
yearMonthDay = "20060102"
)
fmt.Fprintf(, "/%s\n", .RawPath)
:= append(extractHeaderNames(.Headers), "host")
if .ContentType != "" {
= append(, "content-type")
}
if .MD5 != "" {
= append(, "content-md5")
}
sort.Strings()
:= strings.Join(, ";")
:= .Format(iso8601)
:= fmt.Sprintf("%s/auto/storage/goog4_request", .Format(yearMonthDay))
:= url.Values{
"X-Goog-Algorithm": {"GOOG4-RSA-SHA256"},
"X-Goog-Credential": {fmt.Sprintf("%s/%s", .GoogleAccessID, )},
"X-Goog-Date": {},
"X-Goog-Expires": {fmt.Sprintf("%d", int(.Expires.Sub().Seconds()))},
"X-Goog-SignedHeaders": {},
for , := range .QueryParameters {
[] = append([], ...)
}
fmt.Fprintf(, "%s\n", .Encode())
:= false
for , := range {
if strings.HasPrefix(strings.ToLower(), "x-goog-content-sha256") && strings.Contains(, ":") {
= true
fmt.Fprintf(, "%s", strings.SplitN(, ":", 2)[1])
break
}
}
if ! {
fmt.Fprint(, "UNSIGNED-PAYLOAD")
}
:= sha256.Sum256(.Bytes())
:= hex.EncodeToString([:])
:= &bytes.Buffer{}
fmt.Fprint(, "GOOG4-RSA-SHA256\n")
fmt.Fprintf(, "%s\n", )
fmt.Fprintf(, "%s\n", )
fmt.Fprintf(, "%s", )
:= .SignBytes
if .PrivateKey != nil {
, := parseKey(.PrivateKey)
if != nil {
return "",
}
= func( []byte) ([]byte, error) {
:= sha256.Sum256()
return rsa.SignPKCS1v15(
rand.Reader,
,
crypto.SHA256,
[:],
)
}
}
, := (.Bytes())
if != nil {
return "",
}
:= hex.EncodeToString()
.Set("X-Goog-Signature", string())
.RawQuery = .Encode()
return .String(), nil
}
func ( []string) []string {
:= map[string]string{}
var []string
for , := range {
:= strings.Split(, ":")
:= [0]
:= [1]
[] =
= append(, )
}
sort.Strings()
var []string
for , := range {
:= []
= append(, fmt.Sprintf("%s:%s", , ))
}
return
}
func (, string, *SignedURLOptions) (string, error) {
:= .SignBytes
if .PrivateKey != nil {
, := parseKey(.PrivateKey)
if != nil {
return "",
}
= func( []byte) ([]byte, error) {
:= sha256.Sum256()
return rsa.SignPKCS1v15(
rand.Reader,
,
crypto.SHA256,
[:],
)
}
}
:= &url.URL{
Path: fmt.Sprintf("/%s/%s", , ),
}
:= &bytes.Buffer{}
fmt.Fprintf(, "%s\n", .Method)
fmt.Fprintf(, "%s\n", .MD5)
fmt.Fprintf(, "%s\n", .ContentType)
fmt.Fprintf(, "%d\n", .Expires.Unix())
if len(.Headers) > 0 {
fmt.Fprintf(, "%s\n", strings.Join(.Headers, "\n"))
}
fmt.Fprintf(, "%s", .String())
, := (.Bytes())
if != nil {
return "",
}
:= base64.StdEncoding.EncodeToString()
.Scheme = "https"
.Host = "storage.googleapis.com"
:= .Query()
.Set("GoogleAccessId", .GoogleAccessID)
.Set("Expires", fmt.Sprintf("%d", .Expires.Unix()))
.Set("Signature", string())
.RawQuery = .Encode()
return .String(), nil
}
type ObjectHandle struct {
c *Client
bucket string
object string
acl ACLHandle
gen int64 // a negative value indicates latest
conds *Conditions
encryptionKey []byte // AES-256 key
userProject string // for requester-pays buckets
readCompressed bool // Accept-Encoding: gzip
}
func ( *ObjectHandle) () *ACLHandle {
return &.acl
}
func ( *ObjectHandle) ( int64) *ObjectHandle {
:= *
.gen =
return &
}
func ( *ObjectHandle) ( Conditions) *ObjectHandle {
:= *
.conds = &
return &
}
func ( *ObjectHandle) ( []byte) *ObjectHandle {
:= *
.encryptionKey =
return &
}
func ( *ObjectHandle) ( context.Context) ( *ObjectAttrs, error) {
= trace.StartSpan(, "cloud.google.com/go/storage.Object.Attrs")
defer func() { trace.EndSpan(, ) }()
if := .validate(); != nil {
return nil,
}
:= .c.raw.Objects.Get(.bucket, .object).Projection("full").Context()
if := applyConds("Attrs", .gen, .conds, ); != nil {
return nil,
}
if .userProject != "" {
.UserProject(.userProject)
}
if := setEncryptionHeaders(.Header(), .encryptionKey, false); != nil {
return nil,
}
var *raw.Object
setClientHeader(.Header())
= runWithRetry(, func() error { , = .Do(); return })
if , := .(*googleapi.Error); && .Code == http.StatusNotFound {
return nil, ErrObjectNotExist
}
if != nil {
return nil,
}
return newObject(), nil
}
func ( *ObjectHandle) ( context.Context, ObjectAttrsToUpdate) ( *ObjectAttrs, error) {
= trace.StartSpan(, "cloud.google.com/go/storage.Object.Update")
defer func() { trace.EndSpan(, ) }()
if := .validate(); != nil {
return nil,
}
var , []string
if .ContentType != nil {
if .ContentType == "" {
= append(, "ContentType")
} else {
= append(, "ContentType")
}
}
if .ContentLanguage != nil {
if .ContentLanguage == "" {
= append(, "ContentLanguage")
} else {
= append(, "ContentLanguage")
}
}
if .ContentEncoding != nil {
.ContentEncoding = optional.ToString(.ContentEncoding)
= append(, "ContentEncoding")
}
if .ContentDisposition != nil {
.ContentDisposition = optional.ToString(.ContentDisposition)
= append(, "ContentDisposition")
}
if .CacheControl != nil {
.CacheControl = optional.ToString(.CacheControl)
= append(, "CacheControl")
}
if .EventBasedHold != nil {
.EventBasedHold = optional.ToBool(.EventBasedHold)
= append(, "EventBasedHold")
}
if .TemporaryHold != nil {
.TemporaryHold = optional.ToBool(.TemporaryHold)
= append(, "TemporaryHold")
}
if .Metadata != nil {
.Metadata = .Metadata
= append(, "Acl")
}
:= .toRawObject(.bucket)
.ForceSendFields =
.NullFields =
:= .c.raw.Objects.Patch(.bucket, .object, ).Projection("full").Context()
if := applyConds("Update", .gen, .conds, ); != nil {
return nil,
}
if .userProject != "" {
.UserProject(.userProject)
}
if .PredefinedACL != "" {
.PredefinedAcl(.PredefinedACL)
}
if := setEncryptionHeaders(.Header(), .encryptionKey, false); != nil {
return nil,
}
var *raw.Object
setClientHeader(.Header())
= runWithRetry(, func() error { , = .Do(); return })
if , := .(*googleapi.Error); && .Code == http.StatusNotFound {
return nil, ErrObjectNotExist
}
if != nil {
return nil,
}
return newObject(), nil
}
func ( *ObjectHandle) () string {
return .bucket
}
func ( *ObjectHandle) () string {
return .object
}
type ObjectAttrsToUpdate struct {
EventBasedHold optional.Bool
TemporaryHold optional.Bool
ContentType optional.String
ContentLanguage optional.String
ContentEncoding optional.String
ContentDisposition optional.String
CacheControl optional.String
Metadata map[string]string // set to map[string]string{} to delete
ACL []ACLRule
func ( *ObjectHandle) ( context.Context) error {
if := .validate(); != nil {
return
}
:= .c.raw.Objects.Delete(.bucket, .object).Context()
if := applyConds("Delete", .gen, .conds, ); != nil {
return
}
if .userProject != "" {
.UserProject(.userProject)
setClientHeader(.Header())
:= runWithRetry(, func() error { return .Do() })
switch e := .(type) {
case nil:
return nil
case *googleapi.Error:
if .Code == http.StatusNotFound {
return ErrObjectNotExist
}
}
return
}
func ( *ObjectHandle) ( bool) *ObjectHandle {
:= *
.readCompressed =
return &
}
func ( *ObjectHandle) ( context.Context) *Writer {
return &Writer{
ctx: ,
o: ,
donec: make(chan struct{}),
ObjectAttrs: ObjectAttrs{Name: .object},
ChunkSize: googleapi.DefaultUploadChunkSize,
}
}
func ( *ObjectHandle) () error {
if .bucket == "" {
return errors.New("storage: bucket name is empty")
}
if .object == "" {
return errors.New("storage: object name is empty")
}
if !utf8.ValidString(.object) {
return fmt.Errorf("storage: object name %q is not valid UTF-8", .object)
}
return nil
}
func ( *ObjectAttrs) ( string) *raw.Object {
var string
if !.RetentionExpirationTime.IsZero() {
= .RetentionExpirationTime.Format(time.RFC3339)
}
return &raw.Object{
Bucket: ,
Name: .Name,
EventBasedHold: .EventBasedHold,
TemporaryHold: .TemporaryHold,
RetentionExpirationTime: ,
ContentType: .ContentType,
ContentEncoding: .ContentEncoding,
ContentLanguage: .ContentLanguage,
CacheControl: .CacheControl,
ContentDisposition: .ContentDisposition,
StorageClass: .StorageClass,
Acl: toRawObjectACL(.ACL),
Metadata: .Metadata,
}
}
func ( string) time.Time {
var time.Time
if != "" {
, _ = time.Parse(time.RFC3339, )
}
return
}
func ( *raw.Object) *ObjectAttrs {
if == nil {
return nil
}
:= ""
if .Owner != nil {
= .Owner.Entity
}
, := base64.StdEncoding.DecodeString(.Md5Hash)
, := decodeUint32(.Crc32c)
var string
if .CustomerEncryption != nil {
= .CustomerEncryption.KeySha256
}
return &ObjectAttrs{
Bucket: .Bucket,
Name: .Name,
ContentType: .ContentType,
ContentLanguage: .ContentLanguage,
CacheControl: .CacheControl,
EventBasedHold: .EventBasedHold,
TemporaryHold: .TemporaryHold,
RetentionExpirationTime: convertTime(.RetentionExpirationTime),
ACL: toObjectACLRules(.Acl),
Owner: ,
ContentEncoding: .ContentEncoding,
ContentDisposition: .ContentDisposition,
Size: int64(.Size),
MD5: ,
CRC32C: ,
MediaLink: .MediaLink,
Metadata: .Metadata,
Generation: .Generation,
Metageneration: .Metageneration,
StorageClass: .StorageClass,
CustomerKeySHA256: ,
KMSKeyName: .KmsKeyName,
Created: convertTime(.TimeCreated),
Deleted: convertTime(.TimeDeleted),
Updated: convertTime(.Updated),
Etag: .Etag,
}
}
func ( uint32) string {
:= []byte{byte( >> 24), byte( >> 16), byte( >> 8), byte()}
return base64.StdEncoding.EncodeToString()
}
var attrToFieldMap = map[string]string{
"Bucket": "bucket",
"Name": "name",
"ContentType": "contentType",
"ContentLanguage": "contentLanguage",
"CacheControl": "cacheControl",
"EventBasedHold": "eventBasedHold",
"TemporaryHold": "temporaryHold",
"RetentionExpirationTime": "retentionExpirationTime",
"ACL": "acl",
"Owner": "owner",
"ContentEncoding": "contentEncoding",
"ContentDisposition": "contentDisposition",
"Size": "size",
"MD5": "md5Hash",
"CRC32C": "crc32c",
"MediaLink": "mediaLink",
"Metadata": "metadata",
"Generation": "generation",
"Metageneration": "metageneration",
"StorageClass": "storageClass",
"CustomerKeySHA256": "customerEncryption",
"KMSKeyName": "kmsKeyName",
"Created": "timeCreated",
"Deleted": "timeDeleted",
"Updated": "updated",
"Etag": "etag",
}
func ( *Query) ( []string) error {
:= make(map[string]bool)
for , := range {
, := attrToFieldMap[]
if ! {
return fmt.Errorf("storage: attr %v is not valid", )
}
[] = true
}
if len() > 0 {
var bytes.Buffer
.WriteString("items(")
:= true
for := range {
if ! {
.WriteString(",")
}
= false
.WriteString()
}
.WriteString(")")
.fieldSelection = .String()
}
return nil
}
MetagenerationNotMatch int64
}
func ( *Conditions) ( string) error {
if * == (Conditions{}) {
return fmt.Errorf("storage: %s: empty conditions", )
}
if !.isGenerationValid() {
return fmt.Errorf("storage: %s: multiple conditions specified for generation", )
}
if !.isMetagenerationValid() {
return fmt.Errorf("storage: %s: multiple conditions specified for metageneration", )
}
return nil
}
func ( *Conditions) () bool {
:= 0
if .GenerationMatch != 0 {
++
}
if .GenerationNotMatch != 0 {
++
}
if .DoesNotExist {
++
}
return <= 1
}
func ( *Conditions) () bool {
return .MetagenerationMatch == 0 || .MetagenerationNotMatch == 0
}
func ( string, int64, *Conditions, interface{}) error {
:= reflect.ValueOf()
if >= 0 {
if !setConditionField(, "Generation", ) {
return fmt.Errorf("storage: %s: generation not supported", )
}
}
if == nil {
return nil
}
if := .validate(); != nil {
return
}
switch {
case .GenerationMatch != 0:
if !setConditionField(, "IfGenerationMatch", .GenerationMatch) {
return fmt.Errorf("storage: %s: ifGenerationMatch not supported", )
}
case .GenerationNotMatch != 0:
if !setConditionField(, "IfGenerationNotMatch", .GenerationNotMatch) {
return fmt.Errorf("storage: %s: ifGenerationNotMatch not supported", )
}
case .DoesNotExist:
if !setConditionField(, "IfGenerationMatch", int64(0)) {
return fmt.Errorf("storage: %s: DoesNotExist not supported", )
}
}
switch {
case .MetagenerationMatch != 0:
if !setConditionField(, "IfMetagenerationMatch", .MetagenerationMatch) {
return fmt.Errorf("storage: %s: ifMetagenerationMatch not supported", )
}
case .MetagenerationNotMatch != 0:
if !setConditionField(, "IfMetagenerationNotMatch", .MetagenerationNotMatch) {
return fmt.Errorf("storage: %s: ifMetagenerationNotMatch not supported", )
}
}
return nil
}
func ( int64, *Conditions, *raw.ObjectsRewriteCall) error {
if >= 0 {
.SourceGeneration()
}
if == nil {
return nil
}
if := .validate("CopyTo source"); != nil {
return
}
switch {
case .GenerationMatch != 0:
.IfSourceGenerationMatch(.GenerationMatch)
case .GenerationNotMatch != 0:
.IfSourceGenerationNotMatch(.GenerationNotMatch)
case .DoesNotExist:
.IfSourceGenerationMatch(0)
}
switch {
case .MetagenerationMatch != 0:
.IfSourceMetagenerationMatch(.MetagenerationMatch)
case .MetagenerationNotMatch != 0:
.IfSourceMetagenerationNotMatch(.MetagenerationNotMatch)
}
return nil
}
var []byte
:= func( string, int64) {
if len() > 0 {
= append(, '&')
}
= append(, ...)
= strconv.AppendInt(, , 10)
}
if >= 0 {
("generation=", )
}
if == nil {
return string()
}
switch {
case .GenerationMatch != 0:
("ifGenerationMatch=", .GenerationMatch)
case .GenerationNotMatch != 0:
("ifGenerationNotMatch=", .GenerationNotMatch)
case .DoesNotExist:
("ifGenerationMatch=", 0)
}
switch {
case .MetagenerationMatch != 0:
("ifMetagenerationMatch=", .MetagenerationMatch)
case .MetagenerationNotMatch != 0:
("ifMetagenerationNotMatch=", .MetagenerationNotMatch)
}
return string()
}
type composeSourceObj struct {
src *raw.ComposeRequestSourceObjects
}
func ( composeSourceObj) ( int64) {
.src.Generation =
}
.src.ObjectPreconditions = &raw.ComposeRequestSourceObjectsObjectPreconditions{
IfGenerationMatch: ,
}
}
func ( http.Header, []byte, bool) error {
if == nil {
return nil
if len() != 32 {
return errors.New("storage: not a 32-byte AES-256 key")
}
var string
if {
= "copy-source-"
}
.Set("x-goog-"++"encryption-algorithm", "AES256")
.Set("x-goog-"++"encryption-key", base64.StdEncoding.EncodeToString())
:= sha256.Sum256()
.Set("x-goog-"++"encryption-key-sha256", base64.StdEncoding.EncodeToString([:]))
return nil
}
![]() |
The pages are generated with Golds v0.3.2-preview. (GOOS=darwin GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds. |