You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

408 lines
9.9 KiB

4 years ago
6 years ago
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
4 years ago
  1. package cos
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/base64"
  6. "encoding/xml"
  7. "fmt"
  8. "io"
  9. "io/ioutil"
  10. "net/http"
  11. "net/url"
  12. "reflect"
  13. "strings"
  14. "text/template"
  15. "strconv"
  16. "github.com/google/go-querystring/query"
  17. "github.com/mozillazg/go-httpheader"
  18. )
  19. const (
  20. // Version current go sdk version
  21. Version = "0.7.11"
  22. userAgent = "cos-go-sdk-v5/" + Version
  23. contentTypeXML = "application/xml"
  24. defaultServiceBaseURL = "http://service.cos.myqcloud.com"
  25. )
  26. var bucketURLTemplate = template.Must(
  27. template.New("bucketURLFormat").Parse(
  28. "{{.Schema}}://{{.BucketName}}.cos.{{.Region}}.myqcloud.com",
  29. ),
  30. )
  31. // BaseURL 访问各 API 所需的基础 URL
  32. type BaseURL struct {
  33. // 访问 bucket, object 相关 API 的基础 URL(不包含 path 部分): http://example.com
  34. BucketURL *url.URL
  35. // 访问 service API 的基础 URL(不包含 path 部分): http://example.com
  36. ServiceURL *url.URL
  37. // 访问 job API 的基础 URL (不包含 path 部分): http://example.com
  38. BatchURL *url.URL
  39. }
  40. // NewBucketURL 生成 BaseURL 所需的 BucketURL
  41. //
  42. // bucketName: bucket名称, bucket的命名规则为{name}-{appid} ,此处填写的存储桶名称必须为此格式
  43. // Region: 区域代码: ap-beijing-1,ap-beijing,ap-shanghai,ap-guangzhou...
  44. // secure: 是否使用 https
  45. func NewBucketURL(bucketName, region string, secure bool) *url.URL {
  46. schema := "https"
  47. if !secure {
  48. schema = "http"
  49. }
  50. w := bytes.NewBuffer(nil)
  51. bucketURLTemplate.Execute(w, struct {
  52. Schema string
  53. BucketName string
  54. Region string
  55. }{
  56. schema, bucketName, region,
  57. })
  58. u, _ := url.Parse(w.String())
  59. return u
  60. }
  61. // Client is a client manages communication with the COS API.
  62. type Client struct {
  63. client *http.Client
  64. Host string
  65. UserAgent string
  66. BaseURL *BaseURL
  67. common service
  68. Service *ServiceService
  69. Bucket *BucketService
  70. Object *ObjectService
  71. Batch *BatchService
  72. }
  73. type service struct {
  74. client *Client
  75. }
  76. // NewClient returns a new COS API client.
  77. func NewClient(uri *BaseURL, httpClient *http.Client) *Client {
  78. if httpClient == nil {
  79. httpClient = &http.Client{}
  80. }
  81. baseURL := &BaseURL{}
  82. if uri != nil {
  83. baseURL.BucketURL = uri.BucketURL
  84. baseURL.ServiceURL = uri.ServiceURL
  85. baseURL.BatchURL = uri.BatchURL
  86. }
  87. if baseURL.ServiceURL == nil {
  88. baseURL.ServiceURL, _ = url.Parse(defaultServiceBaseURL)
  89. }
  90. c := &Client{
  91. client: httpClient,
  92. UserAgent: userAgent,
  93. BaseURL: baseURL,
  94. }
  95. c.common.client = c
  96. c.Service = (*ServiceService)(&c.common)
  97. c.Bucket = (*BucketService)(&c.common)
  98. c.Object = (*ObjectService)(&c.common)
  99. c.Batch = (*BatchService)(&c.common)
  100. return c
  101. }
  102. func (c *Client) newRequest(ctx context.Context, baseURL *url.URL, uri, method string, body interface{}, optQuery interface{}, optHeader interface{}) (req *http.Request, err error) {
  103. uri, err = addURLOptions(uri, optQuery)
  104. if err != nil {
  105. return
  106. }
  107. u, _ := url.Parse(uri)
  108. urlStr := baseURL.ResolveReference(u).String()
  109. var reader io.Reader
  110. contentType := ""
  111. contentMD5 := ""
  112. if body != nil {
  113. // 上传文件
  114. if r, ok := body.(io.Reader); ok {
  115. reader = r
  116. } else {
  117. b, err := xml.Marshal(body)
  118. if err != nil {
  119. return nil, err
  120. }
  121. contentType = contentTypeXML
  122. reader = bytes.NewReader(b)
  123. contentMD5 = base64.StdEncoding.EncodeToString(calMD5Digest(b))
  124. }
  125. }
  126. req, err = http.NewRequest(method, urlStr, reader)
  127. if err != nil {
  128. return
  129. }
  130. req.Header, err = addHeaderOptions(req.Header, optHeader)
  131. if err != nil {
  132. return
  133. }
  134. if v := req.Header.Get("Content-Length"); req.ContentLength == 0 && v != "" && v != "0" {
  135. req.ContentLength, _ = strconv.ParseInt(v, 10, 64)
  136. }
  137. if contentMD5 != "" {
  138. req.Header["Content-MD5"] = []string{contentMD5}
  139. }
  140. if c.UserAgent != "" {
  141. req.Header.Set("User-Agent", c.UserAgent)
  142. }
  143. if req.Header.Get("Content-Type") == "" && contentType != "" {
  144. req.Header.Set("Content-Type", contentType)
  145. }
  146. if c.Host != "" {
  147. req.Host = c.Host
  148. }
  149. return
  150. }
  151. func (c *Client) doAPI(ctx context.Context, req *http.Request, result interface{}, closeBody bool) (*Response, error) {
  152. req = req.WithContext(ctx)
  153. resp, err := c.client.Do(req)
  154. if err != nil {
  155. // If we got an error, and the context has been canceled,
  156. // the context's error is probably more useful.
  157. select {
  158. case <-ctx.Done():
  159. return nil, ctx.Err()
  160. default:
  161. }
  162. return nil, err
  163. }
  164. defer func() {
  165. if closeBody {
  166. // Close the body to let the Transport reuse the connection
  167. io.Copy(ioutil.Discard, resp.Body)
  168. resp.Body.Close()
  169. }
  170. }()
  171. response := newResponse(resp)
  172. err = checkResponse(resp)
  173. if err != nil {
  174. // even though there was an error, we still return the response
  175. // in case the caller wants to inspect it further
  176. return response, err
  177. }
  178. if result != nil {
  179. if w, ok := result.(io.Writer); ok {
  180. io.Copy(w, resp.Body)
  181. } else {
  182. err = xml.NewDecoder(resp.Body).Decode(result)
  183. if err == io.EOF {
  184. err = nil // ignore EOF errors caused by empty response body
  185. }
  186. }
  187. }
  188. return response, err
  189. }
  190. type sendOptions struct {
  191. // 基础 URL
  192. baseURL *url.URL
  193. // URL 中除基础 URL 外的剩余部分
  194. uri string
  195. // 请求方法
  196. method string
  197. body interface{}
  198. // url 查询参数
  199. optQuery interface{}
  200. // http header 参数
  201. optHeader interface{}
  202. // 用 result 反序列化 resp.Body
  203. result interface{}
  204. // 是否禁用自动调用 resp.Body.Close()
  205. // 自动调用 Close() 是为了能够重用连接
  206. disableCloseBody bool
  207. }
  208. func (c *Client) send(ctx context.Context, opt *sendOptions) (resp *Response, err error) {
  209. req, err := c.newRequest(ctx, opt.baseURL, opt.uri, opt.method, opt.body, opt.optQuery, opt.optHeader)
  210. if err != nil {
  211. return
  212. }
  213. resp, err = c.doAPI(ctx, req, opt.result, !opt.disableCloseBody)
  214. if err != nil {
  215. return
  216. }
  217. return
  218. }
  219. // addURLOptions adds the parameters in opt as URL query parameters to s. opt
  220. // must be a struct whose fields may contain "url" tags.
  221. func addURLOptions(s string, opt interface{}) (string, error) {
  222. v := reflect.ValueOf(opt)
  223. if v.Kind() == reflect.Ptr && v.IsNil() {
  224. return s, nil
  225. }
  226. u, err := url.Parse(s)
  227. if err != nil {
  228. return s, err
  229. }
  230. qs, err := query.Values(opt)
  231. if err != nil {
  232. return s, err
  233. }
  234. // 保留原有的参数,并且放在前面。因为 cos 的 url 路由是以第一个参数作为路由的
  235. // e.g. /?uploads
  236. q := u.RawQuery
  237. rq := qs.Encode()
  238. if q != "" {
  239. if rq != "" {
  240. u.RawQuery = fmt.Sprintf("%s&%s", q, qs.Encode())
  241. }
  242. } else {
  243. u.RawQuery = rq
  244. }
  245. return u.String(), nil
  246. }
  247. // addHeaderOptions adds the parameters in opt as Header fields to req. opt
  248. // must be a struct whose fields may contain "header" tags.
  249. func addHeaderOptions(header http.Header, opt interface{}) (http.Header, error) {
  250. v := reflect.ValueOf(opt)
  251. if v.Kind() == reflect.Ptr && v.IsNil() {
  252. return header, nil
  253. }
  254. h, err := httpheader.Header(opt)
  255. if err != nil {
  256. return nil, err
  257. }
  258. for key, values := range h {
  259. for _, value := range values {
  260. header.Add(key, value)
  261. }
  262. }
  263. return header, nil
  264. }
  265. // Owner defines Bucket/Object's owner
  266. type Owner struct {
  267. UIN string `xml:"uin,omitempty"`
  268. ID string `xml:",omitempty"`
  269. DisplayName string `xml:",omitempty"`
  270. }
  271. // Initiator same to the Owner struct
  272. type Initiator Owner
  273. // Response API 响应
  274. type Response struct {
  275. *http.Response
  276. }
  277. func newResponse(resp *http.Response) *Response {
  278. return &Response{
  279. Response: resp,
  280. }
  281. }
  282. // ACLHeaderOptions is the option of ACLHeader
  283. type ACLHeaderOptions struct {
  284. XCosACL string `header:"x-cos-acl,omitempty" url:"-" xml:"-"`
  285. XCosGrantRead string `header:"x-cos-grant-read,omitempty" url:"-" xml:"-"`
  286. XCosGrantWrite string `header:"x-cos-grant-write,omitempty" url:"-" xml:"-"`
  287. XCosGrantFullControl string `header:"x-cos-grant-full-control,omitempty" url:"-" xml:"-"`
  288. XCosGrantReadACP string `header:"x-cos-grant-read-acp,omitempty" url:"-" xml:"-"`
  289. XCosGrantWriteACP string `header:"x-cos-grant-write-acp,omitempty" url:"-" xml:"-"`
  290. }
  291. // ACLGrantee is the param of ACLGrant
  292. type ACLGrantee struct {
  293. Type string `xml:"type,attr"`
  294. UIN string `xml:"uin,omitempty"`
  295. URI string `xml:"URI,omitempty"`
  296. ID string `xml:",omitempty"`
  297. DisplayName string `xml:",omitempty"`
  298. SubAccount string `xml:"Subaccount,omitempty"`
  299. }
  300. // ACLGrant is the param of ACLXml
  301. type ACLGrant struct {
  302. Grantee *ACLGrantee
  303. Permission string
  304. }
  305. // ACLXml is the ACL body struct
  306. type ACLXml struct {
  307. XMLName xml.Name `xml:"AccessControlPolicy"`
  308. Owner *Owner
  309. AccessControlList []ACLGrant `xml:"AccessControlList>Grant,omitempty"`
  310. }
  311. func decodeACL(resp *Response, res *ACLXml) {
  312. ItemMap := map[string]string{
  313. "ACL": "x-cos-acl",
  314. "READ": "x-cos-grant-read",
  315. "WRITE": "x-cos-grant-write",
  316. "READ_ACP": "x-cos-grant-read-acp",
  317. "WRITE_ACP": "x-cos-grant-write-acp",
  318. "FULL_CONTROL": "x-cos-grant-full-control",
  319. }
  320. publicACL := make(map[string]int)
  321. resACL := make(map[string][]string)
  322. for _, item := range res.AccessControlList {
  323. if item.Grantee == nil {
  324. continue
  325. }
  326. if item.Grantee.ID == "qcs::cam::anyone:anyone" || item.Grantee.URI == "http://cam.qcloud.com/groups/global/AllUsers" {
  327. publicACL[item.Permission] = 1
  328. } else if item.Grantee.ID != res.Owner.ID {
  329. resACL[item.Permission] = append(resACL[item.Permission], "id=\""+item.Grantee.ID+"\"")
  330. }
  331. }
  332. if publicACL["FULL_CONTROL"] == 1 || (publicACL["READ"] == 1 && publicACL["WRITE"] == 1) {
  333. resACL["ACL"] = []string{"public-read-write"}
  334. } else if publicACL["READ"] == 1 {
  335. resACL["ACL"] = []string{"public-read"}
  336. } else {
  337. resACL["ACL"] = []string{"private"}
  338. }
  339. for item, header := range ItemMap {
  340. if len(resp.Header.Get(header)) > 0 || len(resACL[item]) == 0 {
  341. continue
  342. }
  343. resp.Header.Set(header, uniqueGrantID(resACL[item]))
  344. }
  345. }
  346. func uniqueGrantID(grantIDs []string) string {
  347. res := []string{}
  348. filter := make(map[string]int)
  349. for _, id := range grantIDs {
  350. if filter[id] != 0 {
  351. continue
  352. }
  353. filter[id] = 1
  354. res = append(res, id)
  355. }
  356. return strings.Join(res, ",")
  357. }