github.com/aliyun/aliyun-oss-go-sdk@v3.0.2+incompatible/oss/bucket.go (about) 1 package oss 2 3 import ( 4 "bytes" 5 "context" 6 "crypto/md5" 7 "encoding/base64" 8 "encoding/xml" 9 "fmt" 10 "hash" 11 "hash/crc64" 12 "io" 13 "io/ioutil" 14 "net/http" 15 "net/url" 16 "os" 17 "strconv" 18 "strings" 19 "time" 20 ) 21 22 // Bucket implements the operations of object. 23 type Bucket struct { 24 Client Client 25 BucketName string 26 } 27 28 // PutObject creates a new object and it will overwrite the original one if it exists already. 29 // 30 // objectKey the object key in UTF-8 encoding. The length must be between 1 and 1023, and cannot start with "/" or "\". 31 // reader io.Reader instance for reading the data for uploading 32 // options the options for uploading the object. The valid options here are CacheControl, ContentDisposition, ContentEncoding 33 // 34 // Expires, ServerSideEncryption, ObjectACL and Meta. Refer to the link below for more details. 35 // https://www.alibabacloud.com/help/en/object-storage-service/latest/putobject 36 // 37 // error it's nil if no error, otherwise it's an error object. 38 func (bucket Bucket) PutObject(objectKey string, reader io.Reader, options ...Option) error { 39 opts := AddContentType(options, objectKey) 40 41 request := &PutObjectRequest{ 42 ObjectKey: objectKey, 43 Reader: reader, 44 } 45 resp, err := bucket.DoPutObject(request, opts) 46 if err != nil { 47 return err 48 } 49 defer resp.Body.Close() 50 51 return err 52 } 53 54 // PutObjectFromFile creates a new object from the local file. 55 // 56 // objectKey object key. 57 // filePath the local file path to upload. 58 // options the options for uploading the object. Refer to the parameter options in PutObject for more details. 59 // 60 // error it's nil if no error, otherwise it's an error object. 61 func (bucket Bucket) PutObjectFromFile(objectKey, filePath string, options ...Option) error { 62 fd, err := os.Open(filePath) 63 if err != nil { 64 return err 65 } 66 defer fd.Close() 67 68 opts := AddContentType(options, filePath, objectKey) 69 70 request := &PutObjectRequest{ 71 ObjectKey: objectKey, 72 Reader: fd, 73 } 74 resp, err := bucket.DoPutObject(request, opts) 75 if err != nil { 76 return err 77 } 78 defer resp.Body.Close() 79 80 return err 81 } 82 83 // DoPutObject does the actual upload work. 84 // 85 // request the request instance for uploading an object. 86 // options the options for uploading an object. 87 // 88 // Response the response from OSS. 89 // error it's nil if no error, otherwise it's an error object. 90 func (bucket Bucket) DoPutObject(request *PutObjectRequest, options []Option) (*Response, error) { 91 isOptSet, _, _ := IsOptionSet(options, HTTPHeaderContentType) 92 if !isOptSet { 93 options = AddContentType(options, request.ObjectKey) 94 } 95 96 listener := GetProgressListener(options) 97 98 params := map[string]interface{}{} 99 resp, err := bucket.do("PUT", request.ObjectKey, params, options, request.Reader, listener) 100 if err != nil { 101 return nil, err 102 } 103 if bucket.GetConfig().IsEnableCRC { 104 err = CheckCRC(resp, "DoPutObject") 105 if err != nil { 106 return resp, err 107 } 108 } 109 err = CheckRespCode(resp.StatusCode, []int{http.StatusOK}) 110 body, _ := ioutil.ReadAll(resp.Body) 111 if len(body) > 0 { 112 if err != nil { 113 err = tryConvertServiceError(body, resp, err) 114 } else { 115 rb, _ := FindOption(options, responseBody, nil) 116 if rb != nil { 117 if rbody, ok := rb.(*[]byte); ok { 118 *rbody = body 119 } 120 } 121 } 122 } 123 return resp, err 124 } 125 126 // GetObject downloads the object. 127 // 128 // objectKey the object key. 129 // options the options for downloading the object. The valid values are: Range, IfModifiedSince, IfUnmodifiedSince, IfMatch, 130 // 131 // IfNoneMatch, AcceptEncoding. For more details, please check out: 132 // https://www.alibabacloud.com/help/en/object-storage-service/latest/getobject 133 // 134 // io.ReadCloser reader instance for reading data from response. It must be called close() after the usage and only valid when error is nil. 135 // error it's nil if no error, otherwise it's an error object. 136 func (bucket Bucket) GetObject(objectKey string, options ...Option) (io.ReadCloser, error) { 137 result, err := bucket.DoGetObject(&GetObjectRequest{objectKey}, options) 138 if err != nil { 139 return nil, err 140 } 141 142 return result.Response, nil 143 } 144 145 // GetObjectToFile downloads the data to a local file. 146 // 147 // objectKey the object key to download. 148 // filePath the local file to store the object data. 149 // options the options for downloading the object. Refer to the parameter options in method GetObject for more details. 150 // 151 // error it's nil if no error, otherwise it's an error object. 152 func (bucket Bucket) GetObjectToFile(objectKey, filePath string, options ...Option) error { 153 tempFilePath := filePath + TempFileSuffix 154 155 // Calls the API to actually download the object. Returns the result instance. 156 result, err := bucket.DoGetObject(&GetObjectRequest{objectKey}, options) 157 if err != nil { 158 return err 159 } 160 defer result.Response.Close() 161 162 // If the local file does not exist, create a new one. If it exists, overwrite it. 163 fd, err := os.OpenFile(tempFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, FilePermMode) 164 if err != nil { 165 return err 166 } 167 168 // Copy the data to the local file path. 169 _, err = io.Copy(fd, result.Response.Body) 170 fd.Close() 171 if err != nil { 172 return err 173 } 174 175 // Compares the CRC value 176 hasRange, _, _ := IsOptionSet(options, HTTPHeaderRange) 177 encodeOpt, _ := FindOption(options, HTTPHeaderAcceptEncoding, nil) 178 acceptEncoding := "" 179 if encodeOpt != nil { 180 acceptEncoding = encodeOpt.(string) 181 } 182 if bucket.GetConfig().IsEnableCRC && !hasRange && acceptEncoding != "gzip" { 183 result.Response.ClientCRC = result.ClientCRC.Sum64() 184 err = CheckCRC(result.Response, "GetObjectToFile") 185 if err != nil { 186 os.Remove(tempFilePath) 187 return err 188 } 189 } 190 191 return os.Rename(tempFilePath, filePath) 192 } 193 194 // DoGetObject is the actual API that gets the object. It's the internal function called by other public APIs. 195 // 196 // request the request to download the object. 197 // options the options for downloading the file. Checks out the parameter options in method GetObject. 198 // 199 // GetObjectResult the result instance of getting the object. 200 // error it's nil if no error, otherwise it's an error object. 201 func (bucket Bucket) DoGetObject(request *GetObjectRequest, options []Option) (*GetObjectResult, error) { 202 params, _ := GetRawParams(options) 203 resp, err := bucket.do("GET", request.ObjectKey, params, options, nil, nil) 204 if err != nil { 205 return nil, err 206 } 207 208 result := &GetObjectResult{ 209 Response: resp, 210 } 211 212 // CRC 213 var crcCalc hash.Hash64 214 hasRange, _, _ := IsOptionSet(options, HTTPHeaderRange) 215 if bucket.GetConfig().IsEnableCRC && !hasRange { 216 crcCalc = crc64.New(CrcTable()) 217 result.ServerCRC = resp.ServerCRC 218 result.ClientCRC = crcCalc 219 } 220 221 // Progress 222 listener := GetProgressListener(options) 223 224 contentLen, _ := strconv.ParseInt(resp.Headers.Get(HTTPHeaderContentLength), 10, 64) 225 resp.Body = TeeReader(resp.Body, crcCalc, contentLen, listener, nil) 226 227 return result, nil 228 } 229 230 // CopyObject copies the object inside the bucket. 231 // 232 // srcObjectKey the source object to copy. 233 // destObjectKey the target object to copy. 234 // options options for copying an object. You can specify the conditions of copy. The valid conditions are CopySourceIfMatch, 235 // 236 // CopySourceIfNoneMatch, CopySourceIfModifiedSince, CopySourceIfUnmodifiedSince, MetadataDirective. 237 // Also you can specify the target object's attributes, such as CacheControl, ContentDisposition, ContentEncoding, Expires, 238 // ServerSideEncryption, ObjectACL, Meta. Refer to the link below for more details : 239 // https://www.alibabacloud.com/help/en/object-storage-service/latest/copyobject 240 // 241 // error it's nil if no error, otherwise it's an error object. 242 func (bucket Bucket) CopyObject(srcObjectKey, destObjectKey string, options ...Option) (CopyObjectResult, error) { 243 var out CopyObjectResult 244 245 //first find version id 246 versionIdKey := "versionId" 247 versionId, _ := FindOption(options, versionIdKey, nil) 248 if versionId == nil { 249 options = append(options, CopySource(bucket.BucketName, url.QueryEscape(srcObjectKey))) 250 } else { 251 options = DeleteOption(options, versionIdKey) 252 options = append(options, CopySourceVersion(bucket.BucketName, url.QueryEscape(srcObjectKey), versionId.(string))) 253 } 254 255 params := map[string]interface{}{} 256 resp, err := bucket.do("PUT", destObjectKey, params, options, nil, nil) 257 if err != nil { 258 return out, err 259 } 260 defer resp.Body.Close() 261 262 err = xmlUnmarshal(resp.Body, &out) 263 return out, err 264 } 265 266 // CopyObjectTo copies the object to another bucket. 267 // 268 // srcObjectKey source object key. The source bucket is Bucket.BucketName . 269 // destBucketName target bucket name. 270 // destObjectKey target object name. 271 // options copy options, check out parameter options in function CopyObject for more details. 272 // 273 // error it's nil if no error, otherwise it's an error object. 274 func (bucket Bucket) CopyObjectTo(destBucketName, destObjectKey, srcObjectKey string, options ...Option) (CopyObjectResult, error) { 275 return bucket.copy(srcObjectKey, destBucketName, destObjectKey, options...) 276 } 277 278 // CopyObjectFrom copies the object to another bucket. 279 // 280 // srcBucketName source bucket name. 281 // srcObjectKey source object name. 282 // destObjectKey target object name. The target bucket name is Bucket.BucketName. 283 // options copy options. Check out parameter options in function CopyObject. 284 // 285 // error it's nil if no error, otherwise it's an error object. 286 func (bucket Bucket) CopyObjectFrom(srcBucketName, srcObjectKey, destObjectKey string, options ...Option) (CopyObjectResult, error) { 287 destBucketName := bucket.BucketName 288 var out CopyObjectResult 289 srcBucket, err := bucket.Client.Bucket(srcBucketName) 290 if err != nil { 291 return out, err 292 } 293 294 return srcBucket.copy(srcObjectKey, destBucketName, destObjectKey, options...) 295 } 296 297 func (bucket Bucket) copy(srcObjectKey, destBucketName, destObjectKey string, options ...Option) (CopyObjectResult, error) { 298 var out CopyObjectResult 299 300 //first find version id 301 versionIdKey := "versionId" 302 versionId, _ := FindOption(options, versionIdKey, nil) 303 if versionId == nil { 304 options = append(options, CopySource(bucket.BucketName, url.QueryEscape(srcObjectKey))) 305 } else { 306 options = DeleteOption(options, versionIdKey) 307 options = append(options, CopySourceVersion(bucket.BucketName, url.QueryEscape(srcObjectKey), versionId.(string))) 308 } 309 310 headers := make(map[string]string) 311 err := handleOptions(headers, options) 312 if err != nil { 313 return out, err 314 } 315 params := map[string]interface{}{} 316 317 ctxArg, _ := FindOption(options, contextArg, nil) 318 ctx, _ := ctxArg.(context.Context) 319 320 resp, err := bucket.Client.Conn.DoWithContext(ctx, "PUT", destBucketName, destObjectKey, params, headers, nil, 0, nil) 321 322 // get response header 323 respHeader, _ := FindOption(options, responseHeader, nil) 324 if respHeader != nil { 325 pRespHeader := respHeader.(*http.Header) 326 if resp != nil { 327 *pRespHeader = resp.Headers 328 } 329 } 330 331 if err != nil { 332 return out, err 333 } 334 defer resp.Body.Close() 335 336 err = xmlUnmarshal(resp.Body, &out) 337 return out, err 338 } 339 340 // AppendObject uploads the data in the way of appending an existing or new object. 341 // 342 // AppendObject the parameter appendPosition specifies which postion (in the target object) to append. For the first append (to a non-existing file), 343 // the appendPosition should be 0. The appendPosition in the subsequent calls will be the current object length. 344 // For example, the first appendObject's appendPosition is 0 and it uploaded 65536 bytes data, then the second call's position is 65536. 345 // The response header x-oss-next-append-position after each successful request also specifies the next call's append position (so the caller need not to maintain this information). 346 // 347 // objectKey the target object to append to. 348 // reader io.Reader. The read instance for reading the data to append. 349 // appendPosition the start position to append. 350 // destObjectProperties the options for the first appending, such as CacheControl, ContentDisposition, ContentEncoding, 351 // 352 // Expires, ServerSideEncryption, ObjectACL. 353 // 354 // int64 the next append position, it's valid when error is nil. 355 // error it's nil if no error, otherwise it's an error object. 356 func (bucket Bucket) AppendObject(objectKey string, reader io.Reader, appendPosition int64, options ...Option) (int64, error) { 357 request := &AppendObjectRequest{ 358 ObjectKey: objectKey, 359 Reader: reader, 360 Position: appendPosition, 361 } 362 363 result, err := bucket.DoAppendObject(request, options) 364 if err != nil { 365 return appendPosition, err 366 } 367 368 return result.NextPosition, err 369 } 370 371 // DoAppendObject is the actual API that does the object append. 372 // 373 // request the request object for appending object. 374 // options the options for appending object. 375 // 376 // AppendObjectResult the result object for appending object. 377 // error it's nil if no error, otherwise it's an error object. 378 func (bucket Bucket) DoAppendObject(request *AppendObjectRequest, options []Option) (*AppendObjectResult, error) { 379 params := map[string]interface{}{} 380 params["append"] = nil 381 params["position"] = strconv.FormatInt(request.Position, 10) 382 headers := make(map[string]string) 383 384 opts := AddContentType(options, request.ObjectKey) 385 handleOptions(headers, opts) 386 387 var initCRC uint64 388 isCRCSet, initCRCOpt, _ := IsOptionSet(options, initCRC64) 389 if isCRCSet { 390 initCRC = initCRCOpt.(uint64) 391 } 392 393 listener := GetProgressListener(options) 394 395 handleOptions(headers, opts) 396 397 ctxArg, _ := FindOption(options, contextArg, nil) 398 ctx, _ := ctxArg.(context.Context) 399 400 resp, err := bucket.Client.Conn.DoWithContext(ctx, "POST", bucket.BucketName, request.ObjectKey, params, headers, 401 request.Reader, initCRC, listener) 402 403 // get response header 404 respHeader, _ := FindOption(options, responseHeader, nil) 405 if respHeader != nil { 406 pRespHeader := respHeader.(*http.Header) 407 if resp != nil { 408 *pRespHeader = resp.Headers 409 } 410 } 411 412 if err != nil { 413 return nil, err 414 } 415 defer resp.Body.Close() 416 417 nextPosition, _ := strconv.ParseInt(resp.Headers.Get(HTTPHeaderOssNextAppendPosition), 10, 64) 418 result := &AppendObjectResult{ 419 NextPosition: nextPosition, 420 CRC: resp.ServerCRC, 421 } 422 423 if bucket.GetConfig().IsEnableCRC && isCRCSet { 424 err = CheckCRC(resp, "AppendObject") 425 if err != nil { 426 return result, err 427 } 428 } 429 430 return result, nil 431 } 432 433 // DeleteObject deletes the object. 434 // 435 // objectKey the object key to delete. 436 // 437 // error it's nil if no error, otherwise it's an error object. 438 func (bucket Bucket) DeleteObject(objectKey string, options ...Option) error { 439 params, _ := GetRawParams(options) 440 resp, err := bucket.do("DELETE", objectKey, params, options, nil, nil) 441 if err != nil { 442 return err 443 } 444 defer resp.Body.Close() 445 return CheckRespCode(resp.StatusCode, []int{http.StatusNoContent}) 446 } 447 448 // DeleteObjects deletes multiple objects. 449 // 450 // objectKeys the object keys to delete. 451 // options the options for deleting objects. 452 // 453 // Supported option is DeleteObjectsQuiet which means it will not return error even deletion failed (not recommended). By default it's not used. 454 // 455 // DeleteObjectsResult the result object. 456 // error it's nil if no error, otherwise it's an error object. 457 func (bucket Bucket) DeleteObjects(objectKeys []string, options ...Option) (DeleteObjectsResult, error) { 458 out := DeleteObjectsResult{} 459 dxml := deleteXML{} 460 for _, key := range objectKeys { 461 dxml.Objects = append(dxml.Objects, DeleteObject{Key: key}) 462 } 463 isQuiet, _ := FindOption(options, deleteObjectsQuiet, false) 464 dxml.Quiet = isQuiet.(bool) 465 xmlData := marshalDeleteObjectToXml(dxml) 466 body, err := bucket.DeleteMultipleObjectsXml(xmlData, options...) 467 if err != nil { 468 return out, err 469 } 470 deletedResult := DeleteObjectVersionsResult{} 471 if !dxml.Quiet { 472 if err = xmlUnmarshal(strings.NewReader(body), &deletedResult); err == nil { 473 err = decodeDeleteObjectsResult(&deletedResult) 474 } 475 } 476 // Keep compatibility:need convert to struct DeleteObjectsResult 477 out.XMLName = deletedResult.XMLName 478 for _, v := range deletedResult.DeletedObjectsDetail { 479 out.DeletedObjects = append(out.DeletedObjects, v.Key) 480 } 481 return out, err 482 } 483 484 // DeleteObjectVersions deletes multiple object versions. 485 // 486 // objectVersions the object keys and versions to delete. 487 // options the options for deleting objects. 488 // 489 // Supported option is DeleteObjectsQuiet which means it will not return error even deletion failed (not recommended). By default it's not used. 490 // 491 // DeleteObjectVersionsResult the result object. 492 // error it's nil if no error, otherwise it's an error object. 493 func (bucket Bucket) DeleteObjectVersions(objectVersions []DeleteObject, options ...Option) (DeleteObjectVersionsResult, error) { 494 out := DeleteObjectVersionsResult{} 495 dxml := deleteXML{} 496 dxml.Objects = objectVersions 497 isQuiet, _ := FindOption(options, deleteObjectsQuiet, false) 498 dxml.Quiet = isQuiet.(bool) 499 xmlData := marshalDeleteObjectToXml(dxml) 500 body, err := bucket.DeleteMultipleObjectsXml(xmlData, options...) 501 if err != nil { 502 return out, err 503 } 504 if !dxml.Quiet { 505 if err = xmlUnmarshal(strings.NewReader(body), &out); err == nil { 506 err = decodeDeleteObjectsResult(&out) 507 } 508 } 509 return out, err 510 } 511 512 // DeleteMultipleObjectsXml deletes multiple object or deletes multiple object versions. 513 // 514 // xmlData the object keys and versions to delete as the xml format. 515 // options the options for deleting objects. 516 // 517 // string the result response body. 518 // error it's nil if no error, otherwise it's an error. 519 func (bucket Bucket) DeleteMultipleObjectsXml(xmlData string, options ...Option) (string, error) { 520 buffer := new(bytes.Buffer) 521 bs := []byte(xmlData) 522 buffer.Write(bs) 523 options = append(options, ContentType("application/xml")) 524 sum := md5.Sum(bs) 525 b64 := base64.StdEncoding.EncodeToString(sum[:]) 526 options = append(options, ContentMD5(b64)) 527 params := map[string]interface{}{} 528 params["delete"] = nil 529 params["encoding-type"] = "url" 530 resp, err := bucket.doInner("POST", "", params, options, buffer, nil) 531 if err != nil { 532 return "", err 533 } 534 defer resp.Body.Close() 535 536 body, err := ioutil.ReadAll(resp.Body) 537 out := string(body) 538 return out, err 539 } 540 541 // IsObjectExist checks if the object exists. 542 // 543 // bool flag of object's existence (true:exists; false:non-exist) when error is nil. 544 // 545 // error it's nil if no error, otherwise it's an error object. 546 func (bucket Bucket) IsObjectExist(objectKey string, options ...Option) (bool, error) { 547 _, err := bucket.GetObjectMeta(objectKey, options...) 548 if err == nil { 549 return true, nil 550 } 551 552 switch err.(type) { 553 case ServiceError: 554 if err.(ServiceError).StatusCode == 404 { 555 return false, nil 556 } 557 } 558 559 return false, err 560 } 561 562 // ListObjects lists the objects under the current bucket. 563 // 564 // options it contains all the filters for listing objects. 565 // 566 // It could specify a prefix filter on object keys, the max keys count to return and the object key marker and the delimiter for grouping object names. 567 // The key marker means the returned objects' key must be greater than it in lexicographic order. 568 // 569 // For example, if the bucket has 8 objects, my-object-1, my-object-11, my-object-2, my-object-21, 570 // my-object-22, my-object-3, my-object-31, my-object-32. If the prefix is my-object-2 (no other filters), then it returns 571 // my-object-2, my-object-21, my-object-22 three objects. If the marker is my-object-22 (no other filters), then it returns 572 // my-object-3, my-object-31, my-object-32 three objects. If the max keys is 5, then it returns 5 objects. 573 // The three filters could be used together to achieve filter and paging functionality. 574 // If the prefix is the folder name, then it could list all files under this folder (including the files under its subfolders). 575 // But if the delimiter is specified with '/', then it only returns that folder's files (no subfolder's files). The direct subfolders are in the commonPrefixes properties. 576 // For example, if the bucket has three objects fun/test.jpg, fun/movie/001.avi, fun/movie/007.avi. And if the prefix is "fun/", then it returns all three objects. 577 // But if the delimiter is '/', then only "fun/test.jpg" is returned as files and fun/movie/ is returned as common prefix. 578 // 579 // For common usage scenario, check out sample/list_object.go. 580 // 581 // ListObjectsResult the return value after operation succeeds (only valid when error is nil). 582 func (bucket Bucket) ListObjects(options ...Option) (ListObjectsResult, error) { 583 var out ListObjectsResult 584 585 options = append(options, EncodingType("url")) 586 params, err := GetRawParams(options) 587 if err != nil { 588 return out, err 589 } 590 591 resp, err := bucket.doInner("GET", "", params, options, nil, nil) 592 if err != nil { 593 return out, err 594 } 595 defer resp.Body.Close() 596 597 err = xmlUnmarshal(resp.Body, &out) 598 if err != nil { 599 return out, err 600 } 601 602 err = decodeListObjectsResult(&out) 603 return out, err 604 } 605 606 // ListObjectsV2 lists the objects under the current bucket. 607 // Recommend to use ListObjectsV2 to replace ListObjects 608 // ListObjectsResultV2 the return value after operation succeeds (only valid when error is nil). 609 func (bucket Bucket) ListObjectsV2(options ...Option) (ListObjectsResultV2, error) { 610 var out ListObjectsResultV2 611 612 options = append(options, EncodingType("url")) 613 options = append(options, ListType(2)) 614 params, err := GetRawParams(options) 615 if err != nil { 616 return out, err 617 } 618 619 resp, err := bucket.doInner("GET", "", params, options, nil, nil) 620 if err != nil { 621 return out, err 622 } 623 defer resp.Body.Close() 624 625 err = xmlUnmarshal(resp.Body, &out) 626 if err != nil { 627 return out, err 628 } 629 630 err = decodeListObjectsResultV2(&out) 631 return out, err 632 } 633 634 // ListObjectVersions lists objects of all versions under the current bucket. 635 func (bucket Bucket) ListObjectVersions(options ...Option) (ListObjectVersionsResult, error) { 636 var out ListObjectVersionsResult 637 638 options = append(options, EncodingType("url")) 639 params, err := GetRawParams(options) 640 if err != nil { 641 return out, err 642 } 643 params["versions"] = nil 644 645 resp, err := bucket.doInner("GET", "", params, options, nil, nil) 646 if err != nil { 647 return out, err 648 } 649 defer resp.Body.Close() 650 651 err = xmlUnmarshal(resp.Body, &out) 652 if err != nil { 653 return out, err 654 } 655 656 err = decodeListObjectVersionsResult(&out) 657 return out, err 658 } 659 660 // SetObjectMeta sets the metadata of the Object. 661 // 662 // objectKey object 663 // options options for setting the metadata. The valid options are CacheControl, ContentDisposition, ContentEncoding, Expires, 664 // 665 // ServerSideEncryption, and custom metadata. 666 // 667 // error it's nil if no error, otherwise it's an error object. 668 func (bucket Bucket) SetObjectMeta(objectKey string, options ...Option) error { 669 options = append(options, MetadataDirective(MetaReplace)) 670 _, err := bucket.CopyObject(objectKey, objectKey, options...) 671 return err 672 } 673 674 // GetObjectDetailedMeta gets the object's detailed metadata 675 // 676 // objectKey object key. 677 // options the constraints of the object. Only when the object meets the requirements this method will return the metadata. Otherwise returns error. Valid options are IfModifiedSince, IfUnmodifiedSince, 678 // 679 // IfMatch, IfNoneMatch. For more details check out https://www.alibabacloud.com/help/en/object-storage-service/latest/headobject 680 // 681 // http.Header object meta when error is nil. 682 // error it's nil if no error, otherwise it's an error object. 683 func (bucket Bucket) GetObjectDetailedMeta(objectKey string, options ...Option) (http.Header, error) { 684 params, _ := GetRawParams(options) 685 resp, err := bucket.do("HEAD", objectKey, params, options, nil, nil) 686 if err != nil { 687 return nil, err 688 } 689 defer resp.Body.Close() 690 691 return resp.Headers, nil 692 } 693 694 // GetObjectMeta gets object metadata. 695 // 696 // GetObjectMeta is more lightweight than GetObjectDetailedMeta as it only returns basic metadata including ETag 697 // size, LastModified. The size information is in the HTTP header Content-Length. 698 // 699 // objectKey object key 700 // 701 // http.Header the object's metadata, valid when error is nil. 702 // error it's nil if no error, otherwise it's an error object. 703 func (bucket Bucket) GetObjectMeta(objectKey string, options ...Option) (http.Header, error) { 704 params, _ := GetRawParams(options) 705 params["objectMeta"] = nil 706 //resp, err := bucket.do("GET", objectKey, "?objectMeta", "", nil, nil, nil) 707 resp, err := bucket.do("HEAD", objectKey, params, options, nil, nil) 708 if err != nil { 709 return nil, err 710 } 711 defer resp.Body.Close() 712 713 return resp.Headers, nil 714 } 715 716 // SetObjectACL updates the object's ACL. 717 // 718 // Only the bucket's owner could update object's ACL which priority is higher than bucket's ACL. 719 // For example, if the bucket ACL is private and object's ACL is public-read-write. 720 // Then object's ACL is used and it means all users could read or write that object. 721 // When the object's ACL is not set, then bucket's ACL is used as the object's ACL. 722 // 723 // Object read operations include GetObject, HeadObject, CopyObject and UploadPartCopy on the source object; 724 // Object write operations include PutObject, PostObject, AppendObject, DeleteObject, DeleteMultipleObjects, 725 // CompleteMultipartUpload and CopyObject on target object. 726 // 727 // objectKey the target object key (to set the ACL on) 728 // objectAcl object ACL. Valid options are PrivateACL, PublicReadACL, PublicReadWriteACL. 729 // 730 // error it's nil if no error, otherwise it's an error object. 731 func (bucket Bucket) SetObjectACL(objectKey string, objectACL ACLType, options ...Option) error { 732 options = append(options, ObjectACL(objectACL)) 733 params, _ := GetRawParams(options) 734 params["acl"] = nil 735 resp, err := bucket.do("PUT", objectKey, params, options, nil, nil) 736 if err != nil { 737 return err 738 } 739 defer resp.Body.Close() 740 return CheckRespCode(resp.StatusCode, []int{http.StatusOK}) 741 } 742 743 // GetObjectACL gets object's ACL 744 // 745 // objectKey the object to get ACL from. 746 // 747 // GetObjectACLResult the result object when error is nil. GetObjectACLResult.Acl is the object ACL. 748 // error it's nil if no error, otherwise it's an error object. 749 func (bucket Bucket) GetObjectACL(objectKey string, options ...Option) (GetObjectACLResult, error) { 750 var out GetObjectACLResult 751 params, _ := GetRawParams(options) 752 params["acl"] = nil 753 resp, err := bucket.do("GET", objectKey, params, options, nil, nil) 754 if err != nil { 755 return out, err 756 } 757 defer resp.Body.Close() 758 759 err = xmlUnmarshal(resp.Body, &out) 760 return out, err 761 } 762 763 // PutSymlink creates a symlink (to point to an existing object) 764 // 765 // Symlink cannot point to another symlink. 766 // When creating a symlink, it does not check the existence of the target file, and does not check if the target file is symlink. 767 // Neither it checks the caller's permission on the target file. All these checks are deferred to the actual GetObject call via this symlink. 768 // If trying to add an existing file, as long as the caller has the write permission, the existing one will be overwritten. 769 // If the x-oss-meta- is specified, it will be added as the metadata of the symlink file. 770 // 771 // symObjectKey the symlink object's key. 772 // targetObjectKey the target object key to point to. 773 // 774 // error it's nil if no error, otherwise it's an error object. 775 func (bucket Bucket) PutSymlink(symObjectKey string, targetObjectKey string, options ...Option) error { 776 options = append(options, symlinkTarget(url.QueryEscape(targetObjectKey))) 777 params, _ := GetRawParams(options) 778 params["symlink"] = nil 779 resp, err := bucket.do("PUT", symObjectKey, params, options, nil, nil) 780 if err != nil { 781 return err 782 } 783 defer resp.Body.Close() 784 return CheckRespCode(resp.StatusCode, []int{http.StatusOK}) 785 } 786 787 // GetSymlink gets the symlink object with the specified key. 788 // If the symlink object does not exist, returns 404. 789 // 790 // objectKey the symlink object's key. 791 // 792 // error it's nil if no error, otherwise it's an error object. 793 // 794 // When error is nil, the target file key is in the X-Oss-Symlink-Target header of the returned object. 795 func (bucket Bucket) GetSymlink(objectKey string, options ...Option) (http.Header, error) { 796 params, _ := GetRawParams(options) 797 params["symlink"] = nil 798 resp, err := bucket.do("GET", objectKey, params, options, nil, nil) 799 if err != nil { 800 return nil, err 801 } 802 defer resp.Body.Close() 803 804 targetObjectKey := resp.Headers.Get(HTTPHeaderOssSymlinkTarget) 805 targetObjectKey, err = url.QueryUnescape(targetObjectKey) 806 if err != nil { 807 return resp.Headers, err 808 } 809 resp.Headers.Set(HTTPHeaderOssSymlinkTarget, targetObjectKey) 810 return resp.Headers, err 811 } 812 813 // RestoreObject restores the object from the archive storage. 814 // 815 // An archive object is in cold status by default and it cannot be accessed. 816 // When restore is called on the cold object, it will become available for access after some time. 817 // If multiple restores are called on the same file when the object is being restored, server side does nothing for additional calls but returns success. 818 // By default, the restored object is available for access for one day. After that it will be unavailable again. 819 // But if another RestoreObject are called after the file is restored, then it will extend one day's access time of that object, up to 7 days. 820 // 821 // objectKey object key to restore. 822 // 823 // error it's nil if no error, otherwise it's an error object. 824 func (bucket Bucket) RestoreObject(objectKey string, options ...Option) error { 825 params, _ := GetRawParams(options) 826 params["restore"] = nil 827 resp, err := bucket.do("POST", objectKey, params, options, nil, nil) 828 if err != nil { 829 return err 830 } 831 defer resp.Body.Close() 832 return CheckRespCode(resp.StatusCode, []int{http.StatusOK, http.StatusAccepted}) 833 } 834 835 // RestoreObjectDetail support more features than RestoreObject 836 func (bucket Bucket) RestoreObjectDetail(objectKey string, restoreConfig RestoreConfiguration, options ...Option) error { 837 if restoreConfig.Tier == "" { 838 // Expedited, Standard, Bulk 839 restoreConfig.Tier = string(RestoreStandard) 840 } 841 842 if restoreConfig.Days == 0 { 843 restoreConfig.Days = 1 844 } 845 846 bs, err := xml.Marshal(restoreConfig) 847 if err != nil { 848 return err 849 } 850 851 buffer := new(bytes.Buffer) 852 buffer.Write(bs) 853 854 contentType := http.DetectContentType(buffer.Bytes()) 855 options = append(options, ContentType(contentType)) 856 857 params, _ := GetRawParams(options) 858 params["restore"] = nil 859 860 resp, err := bucket.do("POST", objectKey, params, options, buffer, nil) 861 if err != nil { 862 return err 863 } 864 defer resp.Body.Close() 865 return CheckRespCode(resp.StatusCode, []int{http.StatusOK, http.StatusAccepted}) 866 } 867 868 // RestoreObjectXML support more features than RestoreObject 869 func (bucket Bucket) RestoreObjectXML(objectKey, configXML string, options ...Option) error { 870 buffer := new(bytes.Buffer) 871 buffer.Write([]byte(configXML)) 872 873 contentType := http.DetectContentType(buffer.Bytes()) 874 options = append(options, ContentType(contentType)) 875 876 params, _ := GetRawParams(options) 877 params["restore"] = nil 878 879 resp, err := bucket.do("POST", objectKey, params, options, buffer, nil) 880 if err != nil { 881 return err 882 } 883 defer resp.Body.Close() 884 return CheckRespCode(resp.StatusCode, []int{http.StatusOK, http.StatusAccepted}) 885 } 886 887 // SignURL signs the URL. Users could access the object directly with this URL without getting the AK. 888 // 889 // objectKey the target object to sign. 890 // signURLConfig the configuration for the signed URL 891 // 892 // string returns the signed URL, when error is nil. 893 // error it's nil if no error, otherwise it's an error object. 894 func (bucket Bucket) SignURL(objectKey string, method HTTPMethod, expiredInSec int64, options ...Option) (string, error) { 895 err := CheckObjectNameEx(objectKey, isVerifyObjectStrict(bucket.GetConfig())) 896 if err != nil { 897 return "", err 898 } 899 900 if expiredInSec < 0 { 901 return "", fmt.Errorf("invalid expires: %d, expires must bigger than 0", expiredInSec) 902 } 903 expiration := time.Now().Unix() + expiredInSec 904 905 params, err := GetRawParams(options) 906 if err != nil { 907 return "", err 908 } 909 910 headers := make(map[string]string) 911 err = handleOptions(headers, options) 912 if err != nil { 913 return "", err 914 } 915 916 return bucket.Client.Conn.signURL(method, bucket.BucketName, objectKey, expiration, params, headers) 917 } 918 919 // PutObjectWithURL uploads an object with the URL. If the object exists, it will be overwritten. 920 // PutObjectWithURL It will not generate minetype according to the key name. 921 // 922 // signedURL signed URL. 923 // reader io.Reader the read instance for reading the data for the upload. 924 // options the options for uploading the data. The valid options are CacheControl, ContentDisposition, ContentEncoding, 925 // 926 // Expires, ServerSideEncryption, ObjectACL and custom metadata. Check out the following link for details: 927 // https://www.alibabacloud.com/help/en/object-storage-service/latest/putobject 928 // 929 // error it's nil if no error, otherwise it's an error object. 930 func (bucket Bucket) PutObjectWithURL(signedURL string, reader io.Reader, options ...Option) error { 931 resp, err := bucket.DoPutObjectWithURL(signedURL, reader, options) 932 if err != nil { 933 return err 934 } 935 defer resp.Body.Close() 936 937 return err 938 } 939 940 // PutObjectFromFileWithURL uploads an object from a local file with the signed URL. 941 // PutObjectFromFileWithURL It does not generate mimetype according to object key's name or the local file name. 942 // 943 // signedURL the signed URL. 944 // filePath local file path, such as dirfile.txt, for uploading. 945 // options options for uploading, same as the options in PutObject function. 946 // 947 // error it's nil if no error, otherwise it's an error object. 948 func (bucket Bucket) PutObjectFromFileWithURL(signedURL, filePath string, options ...Option) error { 949 fd, err := os.Open(filePath) 950 if err != nil { 951 return err 952 } 953 defer fd.Close() 954 955 resp, err := bucket.DoPutObjectWithURL(signedURL, fd, options) 956 if err != nil { 957 return err 958 } 959 defer resp.Body.Close() 960 961 return err 962 } 963 964 // DoPutObjectWithURL is the actual API that does the upload with URL work(internal for SDK) 965 // 966 // signedURL the signed URL. 967 // reader io.Reader the read instance for getting the data to upload. 968 // options options for uploading. 969 // 970 // Response the response object which contains the HTTP response. 971 // error it's nil if no error, otherwise it's an error object. 972 func (bucket Bucket) DoPutObjectWithURL(signedURL string, reader io.Reader, options []Option) (*Response, error) { 973 listener := GetProgressListener(options) 974 975 params := map[string]interface{}{} 976 resp, err := bucket.doURL("PUT", signedURL, params, options, reader, listener) 977 if err != nil { 978 return nil, err 979 } 980 981 if bucket.GetConfig().IsEnableCRC { 982 err = CheckCRC(resp, "DoPutObjectWithURL") 983 if err != nil { 984 return resp, err 985 } 986 } 987 988 err = CheckRespCode(resp.StatusCode, []int{http.StatusOK}) 989 990 return resp, err 991 } 992 993 // GetObjectWithURL downloads the object and returns the reader instance, with the signed URL. 994 // 995 // signedURL the signed URL. 996 // options options for downloading the object. Valid options are IfModifiedSince, IfUnmodifiedSince, IfMatch, 997 // 998 // IfNoneMatch, AcceptEncoding. For more information, check out the following link: 999 // https://www.alibabacloud.com/help/en/object-storage-service/latest/getobject 1000 // 1001 // io.ReadCloser the reader object for getting the data from response. It needs be closed after the usage. It's only valid when error is nil. 1002 // error it's nil if no error, otherwise it's an error object. 1003 func (bucket Bucket) GetObjectWithURL(signedURL string, options ...Option) (io.ReadCloser, error) { 1004 result, err := bucket.DoGetObjectWithURL(signedURL, options) 1005 if err != nil { 1006 return nil, err 1007 } 1008 return result.Response, nil 1009 } 1010 1011 // GetObjectToFileWithURL downloads the object into a local file with the signed URL. 1012 // 1013 // signedURL the signed URL 1014 // filePath the local file path to download to. 1015 // options the options for downloading object. Check out the parameter options in function GetObject for the reference. 1016 // 1017 // error it's nil if no error, otherwise it's an error object. 1018 func (bucket Bucket) GetObjectToFileWithURL(signedURL, filePath string, options ...Option) error { 1019 tempFilePath := filePath + TempFileSuffix 1020 1021 // Get the object's content 1022 result, err := bucket.DoGetObjectWithURL(signedURL, options) 1023 if err != nil { 1024 return err 1025 } 1026 defer result.Response.Close() 1027 1028 // If the file does not exist, create one. If exists, then overwrite it. 1029 fd, err := os.OpenFile(tempFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, FilePermMode) 1030 if err != nil { 1031 return err 1032 } 1033 1034 // Save the data to the file. 1035 _, err = io.Copy(fd, result.Response.Body) 1036 fd.Close() 1037 if err != nil { 1038 return err 1039 } 1040 1041 // Compare the CRC value. If CRC values do not match, return error. 1042 hasRange, _, _ := IsOptionSet(options, HTTPHeaderRange) 1043 encodeOpt, _ := FindOption(options, HTTPHeaderAcceptEncoding, nil) 1044 acceptEncoding := "" 1045 if encodeOpt != nil { 1046 acceptEncoding = encodeOpt.(string) 1047 } 1048 1049 if bucket.GetConfig().IsEnableCRC && !hasRange && acceptEncoding != "gzip" { 1050 result.Response.ClientCRC = result.ClientCRC.Sum64() 1051 err = CheckCRC(result.Response, "GetObjectToFileWithURL") 1052 if err != nil { 1053 os.Remove(tempFilePath) 1054 return err 1055 } 1056 } 1057 1058 return os.Rename(tempFilePath, filePath) 1059 } 1060 1061 // DoGetObjectWithURL is the actual API that downloads the file with the signed URL. 1062 // 1063 // signedURL the signed URL. 1064 // options the options for getting object. Check out parameter options in GetObject for the reference. 1065 // 1066 // GetObjectResult the result object when the error is nil. 1067 // error it's nil if no error, otherwise it's an error object. 1068 func (bucket Bucket) DoGetObjectWithURL(signedURL string, options []Option) (*GetObjectResult, error) { 1069 params, _ := GetRawParams(options) 1070 resp, err := bucket.doURL("GET", signedURL, params, options, nil, nil) 1071 if err != nil { 1072 return nil, err 1073 } 1074 1075 result := &GetObjectResult{ 1076 Response: resp, 1077 } 1078 1079 // CRC 1080 var crcCalc hash.Hash64 1081 hasRange, _, _ := IsOptionSet(options, HTTPHeaderRange) 1082 if bucket.GetConfig().IsEnableCRC && !hasRange { 1083 crcCalc = crc64.New(CrcTable()) 1084 result.ServerCRC = resp.ServerCRC 1085 result.ClientCRC = crcCalc 1086 } 1087 1088 // Progress 1089 listener := GetProgressListener(options) 1090 1091 contentLen, _ := strconv.ParseInt(resp.Headers.Get(HTTPHeaderContentLength), 10, 64) 1092 resp.Body = TeeReader(resp.Body, crcCalc, contentLen, listener, nil) 1093 1094 return result, nil 1095 } 1096 1097 // ProcessObject apply process on the specified image file. 1098 // 1099 // The supported process includes resize, rotate, crop, watermark, format, 1100 // udf, customized style, etc. 1101 // 1102 // objectKey object key to process. 1103 // process process string, such as "image/resize,w_100|sys/saveas,o_dGVzdC5qcGc,b_dGVzdA" 1104 // 1105 // error it's nil if no error, otherwise it's an error object. 1106 func (bucket Bucket) ProcessObject(objectKey string, process string, options ...Option) (ProcessObjectResult, error) { 1107 var out ProcessObjectResult 1108 params, _ := GetRawParams(options) 1109 params["x-oss-process"] = nil 1110 processData := fmt.Sprintf("%v=%v", "x-oss-process", process) 1111 data := strings.NewReader(processData) 1112 resp, err := bucket.do("POST", objectKey, params, nil, data, nil) 1113 if err != nil { 1114 return out, err 1115 } 1116 defer resp.Body.Close() 1117 1118 err = jsonUnmarshal(resp.Body, &out) 1119 return out, err 1120 } 1121 1122 // AsyncProcessObject apply async process on the specified image file. 1123 // 1124 // The supported process includes resize, rotate, crop, watermark, format, 1125 // udf, customized style, etc. 1126 // 1127 // objectKey object key to process. 1128 // asyncProcess process string, such as "image/resize,w_100|sys/saveas,o_dGVzdC5qcGc,b_dGVzdA" 1129 // 1130 // error it's nil if no error, otherwise it's an error object. 1131 func (bucket Bucket) AsyncProcessObject(objectKey string, asyncProcess string, options ...Option) (AsyncProcessObjectResult, error) { 1132 var out AsyncProcessObjectResult 1133 params, _ := GetRawParams(options) 1134 params["x-oss-async-process"] = nil 1135 processData := fmt.Sprintf("%v=%v", "x-oss-async-process", asyncProcess) 1136 data := strings.NewReader(processData) 1137 1138 resp, err := bucket.do("POST", objectKey, params, nil, data, nil) 1139 if err != nil { 1140 return out, err 1141 } 1142 defer resp.Body.Close() 1143 1144 err = jsonUnmarshal(resp.Body, &out) 1145 return out, err 1146 } 1147 1148 // PutObjectTagging add tagging to object 1149 // 1150 // objectKey object key to add tagging 1151 // tagging tagging to be added 1152 // 1153 // error nil if success, otherwise error 1154 func (bucket Bucket) PutObjectTagging(objectKey string, tagging Tagging, options ...Option) error { 1155 bs, err := xml.Marshal(tagging) 1156 if err != nil { 1157 return err 1158 } 1159 1160 buffer := new(bytes.Buffer) 1161 buffer.Write(bs) 1162 1163 params, _ := GetRawParams(options) 1164 params["tagging"] = nil 1165 resp, err := bucket.do("PUT", objectKey, params, options, buffer, nil) 1166 if err != nil { 1167 return err 1168 } 1169 defer resp.Body.Close() 1170 1171 return nil 1172 } 1173 1174 // 1175 // GetObjectTagging get tagging of the object 1176 // 1177 // objectKey object key to get tagging 1178 // 1179 // Tagging 1180 // error nil if success, otherwise error 1181 1182 func (bucket Bucket) GetObjectTagging(objectKey string, options ...Option) (GetObjectTaggingResult, error) { 1183 var out GetObjectTaggingResult 1184 params, _ := GetRawParams(options) 1185 params["tagging"] = nil 1186 1187 resp, err := bucket.do("GET", objectKey, params, options, nil, nil) 1188 if err != nil { 1189 return out, err 1190 } 1191 defer resp.Body.Close() 1192 1193 err = xmlUnmarshal(resp.Body, &out) 1194 return out, err 1195 } 1196 1197 // DeleteObjectTagging delete object taggging 1198 // 1199 // objectKey object key to delete tagging 1200 // 1201 // error nil if success, otherwise error 1202 func (bucket Bucket) DeleteObjectTagging(objectKey string, options ...Option) error { 1203 params, _ := GetRawParams(options) 1204 params["tagging"] = nil 1205 resp, err := bucket.do("DELETE", objectKey, params, options, nil, nil) 1206 if err != nil { 1207 return err 1208 } 1209 defer resp.Body.Close() 1210 1211 return CheckRespCode(resp.StatusCode, []int{http.StatusNoContent}) 1212 } 1213 1214 func (bucket Bucket) OptionsMethod(objectKey string, options ...Option) (http.Header, error) { 1215 var out http.Header 1216 resp, err := bucket.doInner("OPTIONS", objectKey, nil, options, nil, nil) 1217 if err != nil { 1218 return out, err 1219 } 1220 defer resp.Body.Close() 1221 out = resp.Headers 1222 return out, nil 1223 } 1224 1225 // public 1226 func (bucket Bucket) Do(method, objectName string, params map[string]interface{}, options []Option, 1227 data io.Reader, listener ProgressListener) (*Response, error) { 1228 return bucket.doInner(method, objectName, params, options, data, listener) 1229 } 1230 1231 // Private 1232 func (bucket Bucket) doInner(method, objectName string, params map[string]interface{}, options []Option, 1233 data io.Reader, listener ProgressListener) (*Response, error) { 1234 headers := make(map[string]string) 1235 err := handleOptions(headers, options) 1236 if err != nil { 1237 return nil, err 1238 } 1239 1240 err = CheckBucketName(bucket.BucketName) 1241 if len(bucket.BucketName) > 0 && err != nil { 1242 return nil, err 1243 } 1244 1245 ctxArg, _ := FindOption(options, contextArg, nil) 1246 ctx, _ := ctxArg.(context.Context) 1247 1248 resp, err := bucket.Client.Conn.DoWithContext(ctx, method, bucket.BucketName, objectName, 1249 params, headers, data, 0, listener) 1250 1251 // get response header 1252 respHeader, _ := FindOption(options, responseHeader, nil) 1253 if respHeader != nil && resp != nil { 1254 pRespHeader := respHeader.(*http.Header) 1255 if resp != nil { 1256 *pRespHeader = resp.Headers 1257 } 1258 } 1259 1260 return resp, err 1261 } 1262 1263 // Private check object name before bucket.do 1264 func (bucket Bucket) do(method, objectName string, params map[string]interface{}, options []Option, 1265 data io.Reader, listener ProgressListener) (*Response, error) { 1266 err := CheckObjectName(objectName) 1267 if err != nil { 1268 return nil, err 1269 } 1270 resp, err := bucket.doInner(method, objectName, params, options, data, listener) 1271 return resp, err 1272 } 1273 1274 func (bucket Bucket) doURL(method HTTPMethod, signedURL string, params map[string]interface{}, options []Option, 1275 data io.Reader, listener ProgressListener) (*Response, error) { 1276 1277 headers := make(map[string]string) 1278 err := handleOptions(headers, options) 1279 if err != nil { 1280 return nil, err 1281 } 1282 1283 ctxArg, _ := FindOption(options, contextArg, nil) 1284 ctx, _ := ctxArg.(context.Context) 1285 1286 resp, err := bucket.Client.Conn.DoURLWithContext(ctx, method, signedURL, headers, data, 0, listener) 1287 1288 // get response header 1289 respHeader, _ := FindOption(options, responseHeader, nil) 1290 if respHeader != nil { 1291 pRespHeader := respHeader.(*http.Header) 1292 if resp != nil { 1293 *pRespHeader = resp.Headers 1294 } 1295 } 1296 1297 return resp, err 1298 } 1299 1300 func (bucket Bucket) GetConfig() *Config { 1301 return bucket.Client.Config 1302 } 1303 1304 func AddContentType(options []Option, keys ...string) []Option { 1305 typ := TypeByExtension("") 1306 for _, key := range keys { 1307 typ = TypeByExtension(key) 1308 if typ != "" { 1309 break 1310 } 1311 } 1312 1313 if typ == "" { 1314 typ = "application/octet-stream" 1315 } 1316 1317 opts := []Option{ContentType(typ)} 1318 opts = append(opts, options...) 1319 1320 return opts 1321 }