requests.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. package objects
  2. import (
  3. "bytes"
  4. "crypto/hmac"
  5. "crypto/md5"
  6. "crypto/sha1"
  7. "fmt"
  8. "io"
  9. "strings"
  10. "time"
  11. "devel.mephi.ru/iacherepanov/openstack-gophercloud"
  12. "devel.mephi.ru/iacherepanov/openstack-gophercloud/openstack/objectstorage/v1/accounts"
  13. "devel.mephi.ru/iacherepanov/openstack-gophercloud/pagination"
  14. )
  15. // ListOptsBuilder allows extensions to add additional parameters to the List
  16. // request.
  17. type ListOptsBuilder interface {
  18. ToObjectListParams() (bool, string, error)
  19. }
  20. // ListOpts is a structure that holds parameters for listing objects.
  21. type ListOpts struct {
  22. // Full is a true/false value that represents the amount of object information
  23. // returned. If Full is set to true, then the content-type, number of bytes,
  24. // hash date last modified, and name are returned. If set to false or not set,
  25. // then only the object names are returned.
  26. Full bool
  27. Limit int `q:"limit"`
  28. Marker string `q:"marker"`
  29. EndMarker string `q:"end_marker"`
  30. Format string `q:"format"`
  31. Prefix string `q:"prefix"`
  32. Delimiter string `q:"delimiter"`
  33. Path string `q:"path"`
  34. }
  35. // ToObjectListParams formats a ListOpts into a query string and boolean
  36. // representing whether to list complete information for each object.
  37. func (opts ListOpts) ToObjectListParams() (bool, string, error) {
  38. q, err := gophercloud.BuildQueryString(opts)
  39. return opts.Full, q.String(), err
  40. }
  41. // List is a function that retrieves all objects in a container. It also returns
  42. // the details for the container. To extract only the object information or names,
  43. // pass the ListResult response to the ExtractInfo or ExtractNames function,
  44. // respectively.
  45. func List(c *gophercloud.ServiceClient, containerName string, opts ListOptsBuilder) pagination.Pager {
  46. headers := map[string]string{"Accept": "text/plain", "Content-Type": "text/plain"}
  47. url := listURL(c, containerName)
  48. if opts != nil {
  49. full, query, err := opts.ToObjectListParams()
  50. if err != nil {
  51. return pagination.Pager{Err: err}
  52. }
  53. url += query
  54. if full {
  55. headers = map[string]string{"Accept": "application/json", "Content-Type": "application/json"}
  56. }
  57. }
  58. pager := pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page {
  59. p := ObjectPage{pagination.MarkerPageBase{PageResult: r}}
  60. p.MarkerPageBase.Owner = p
  61. return p
  62. })
  63. pager.Headers = headers
  64. return pager
  65. }
  66. // DownloadOptsBuilder allows extensions to add additional parameters to the
  67. // Download request.
  68. type DownloadOptsBuilder interface {
  69. ToObjectDownloadParams() (map[string]string, string, error)
  70. }
  71. // DownloadOpts is a structure that holds parameters for downloading an object.
  72. type DownloadOpts struct {
  73. IfMatch string `h:"If-Match"`
  74. IfModifiedSince time.Time `h:"If-Modified-Since"`
  75. IfNoneMatch string `h:"If-None-Match"`
  76. IfUnmodifiedSince time.Time `h:"If-Unmodified-Since"`
  77. Range string `h:"Range"`
  78. Expires string `q:"expires"`
  79. MultipartManifest string `q:"multipart-manifest"`
  80. Signature string `q:"signature"`
  81. }
  82. // ToObjectDownloadParams formats a DownloadOpts into a query string and map of
  83. // headers.
  84. func (opts DownloadOpts) ToObjectDownloadParams() (map[string]string, string, error) {
  85. q, err := gophercloud.BuildQueryString(opts)
  86. if err != nil {
  87. return nil, "", err
  88. }
  89. h, err := gophercloud.BuildHeaders(opts)
  90. if err != nil {
  91. return nil, q.String(), err
  92. }
  93. return h, q.String(), nil
  94. }
  95. // Download is a function that retrieves the content and metadata for an object.
  96. // To extract just the content, pass the DownloadResult response to the
  97. // ExtractContent function.
  98. func Download(c *gophercloud.ServiceClient, containerName, objectName string, opts DownloadOptsBuilder) (r DownloadResult) {
  99. url := downloadURL(c, containerName, objectName)
  100. h := make(map[string]string)
  101. if opts != nil {
  102. headers, query, err := opts.ToObjectDownloadParams()
  103. if err != nil {
  104. r.Err = err
  105. return
  106. }
  107. for k, v := range headers {
  108. h[k] = v
  109. }
  110. url += query
  111. }
  112. resp, err := c.Get(url, nil, &gophercloud.RequestOpts{
  113. MoreHeaders: h,
  114. OkCodes: []int{200, 304},
  115. })
  116. if resp != nil {
  117. r.Header = resp.Header
  118. r.Body = resp.Body
  119. }
  120. r.Err = err
  121. return
  122. }
  123. // CreateOptsBuilder allows extensions to add additional parameters to the
  124. // Create request.
  125. type CreateOptsBuilder interface {
  126. ToObjectCreateParams() (io.Reader, map[string]string, string, error)
  127. }
  128. // CreateOpts is a structure that holds parameters for creating an object.
  129. type CreateOpts struct {
  130. Content io.Reader
  131. Metadata map[string]string
  132. NoETag bool
  133. CacheControl string `h:"Cache-Control"`
  134. ContentDisposition string `h:"Content-Disposition"`
  135. ContentEncoding string `h:"Content-Encoding"`
  136. ContentLength int64 `h:"Content-Length"`
  137. ContentType string `h:"Content-Type"`
  138. CopyFrom string `h:"X-Copy-From"`
  139. DeleteAfter int `h:"X-Delete-After"`
  140. DeleteAt int `h:"X-Delete-At"`
  141. DetectContentType string `h:"X-Detect-Content-Type"`
  142. ETag string `h:"ETag"`
  143. IfNoneMatch string `h:"If-None-Match"`
  144. ObjectManifest string `h:"X-Object-Manifest"`
  145. TransferEncoding string `h:"Transfer-Encoding"`
  146. Expires string `q:"expires"`
  147. MultipartManifest string `q:"multipart-manifest"`
  148. Signature string `q:"signature"`
  149. }
  150. // ToObjectCreateParams formats a CreateOpts into a query string and map of
  151. // headers.
  152. func (opts CreateOpts) ToObjectCreateParams() (io.Reader, map[string]string, string, error) {
  153. q, err := gophercloud.BuildQueryString(opts)
  154. if err != nil {
  155. return nil, nil, "", err
  156. }
  157. h, err := gophercloud.BuildHeaders(opts)
  158. if err != nil {
  159. return nil, nil, "", err
  160. }
  161. for k, v := range opts.Metadata {
  162. h["X-Object-Meta-"+k] = v
  163. }
  164. if opts.NoETag {
  165. delete(h, "etag")
  166. return opts.Content, h, q.String(), nil
  167. }
  168. if h["ETag"] != "" {
  169. return opts.Content, h, q.String(), nil
  170. }
  171. hash := md5.New()
  172. buf := bytes.NewBuffer([]byte{})
  173. _, err = io.Copy(io.MultiWriter(hash, buf), opts.Content)
  174. if err != nil {
  175. return nil, nil, "", err
  176. }
  177. localChecksum := fmt.Sprintf("%x", hash.Sum(nil))
  178. h["ETag"] = localChecksum
  179. return buf, h, q.String(), nil
  180. }
  181. // Create is a function that creates a new object or replaces an existing
  182. // object. If the returned response's ETag header fails to match the local
  183. // checksum, the failed request will automatically be retried up to a maximum
  184. // of 3 times.
  185. func Create(c *gophercloud.ServiceClient, containerName, objectName string, opts CreateOptsBuilder) (r CreateResult) {
  186. url := createURL(c, containerName, objectName)
  187. h := make(map[string]string)
  188. var b io.Reader
  189. if opts != nil {
  190. tmpB, headers, query, err := opts.ToObjectCreateParams()
  191. if err != nil {
  192. r.Err = err
  193. return
  194. }
  195. for k, v := range headers {
  196. h[k] = v
  197. }
  198. url += query
  199. b = tmpB
  200. }
  201. resp, err := c.Put(url, nil, nil, &gophercloud.RequestOpts{
  202. RawBody: b,
  203. MoreHeaders: h,
  204. })
  205. r.Err = err
  206. if resp != nil {
  207. r.Header = resp.Header
  208. }
  209. return
  210. }
  211. // CopyOptsBuilder allows extensions to add additional parameters to the
  212. // Copy request.
  213. type CopyOptsBuilder interface {
  214. ToObjectCopyMap() (map[string]string, error)
  215. }
  216. // CopyOpts is a structure that holds parameters for copying one object to
  217. // another.
  218. type CopyOpts struct {
  219. Metadata map[string]string
  220. ContentDisposition string `h:"Content-Disposition"`
  221. ContentEncoding string `h:"Content-Encoding"`
  222. ContentType string `h:"Content-Type"`
  223. Destination string `h:"Destination" required:"true"`
  224. }
  225. // ToObjectCopyMap formats a CopyOpts into a map of headers.
  226. func (opts CopyOpts) ToObjectCopyMap() (map[string]string, error) {
  227. h, err := gophercloud.BuildHeaders(opts)
  228. if err != nil {
  229. return nil, err
  230. }
  231. for k, v := range opts.Metadata {
  232. h["X-Object-Meta-"+k] = v
  233. }
  234. return h, nil
  235. }
  236. // Copy is a function that copies one object to another.
  237. func Copy(c *gophercloud.ServiceClient, containerName, objectName string, opts CopyOptsBuilder) (r CopyResult) {
  238. h := make(map[string]string)
  239. headers, err := opts.ToObjectCopyMap()
  240. if err != nil {
  241. r.Err = err
  242. return
  243. }
  244. for k, v := range headers {
  245. h[k] = v
  246. }
  247. url := copyURL(c, containerName, objectName)
  248. resp, err := c.Request("COPY", url, &gophercloud.RequestOpts{
  249. MoreHeaders: h,
  250. OkCodes: []int{201},
  251. })
  252. if resp != nil {
  253. r.Header = resp.Header
  254. }
  255. r.Err = err
  256. return
  257. }
  258. // DeleteOptsBuilder allows extensions to add additional parameters to the
  259. // Delete request.
  260. type DeleteOptsBuilder interface {
  261. ToObjectDeleteQuery() (string, error)
  262. }
  263. // DeleteOpts is a structure that holds parameters for deleting an object.
  264. type DeleteOpts struct {
  265. MultipartManifest string `q:"multipart-manifest"`
  266. }
  267. // ToObjectDeleteQuery formats a DeleteOpts into a query string.
  268. func (opts DeleteOpts) ToObjectDeleteQuery() (string, error) {
  269. q, err := gophercloud.BuildQueryString(opts)
  270. return q.String(), err
  271. }
  272. // Delete is a function that deletes an object.
  273. func Delete(c *gophercloud.ServiceClient, containerName, objectName string, opts DeleteOptsBuilder) (r DeleteResult) {
  274. url := deleteURL(c, containerName, objectName)
  275. if opts != nil {
  276. query, err := opts.ToObjectDeleteQuery()
  277. if err != nil {
  278. r.Err = err
  279. return
  280. }
  281. url += query
  282. }
  283. resp, err := c.Delete(url, nil)
  284. if resp != nil {
  285. r.Header = resp.Header
  286. }
  287. r.Err = err
  288. return
  289. }
  290. // GetOptsBuilder allows extensions to add additional parameters to the
  291. // Get request.
  292. type GetOptsBuilder interface {
  293. ToObjectGetQuery() (string, error)
  294. }
  295. // GetOpts is a structure that holds parameters for getting an object's
  296. // metadata.
  297. type GetOpts struct {
  298. Expires string `q:"expires"`
  299. Signature string `q:"signature"`
  300. }
  301. // ToObjectGetQuery formats a GetOpts into a query string.
  302. func (opts GetOpts) ToObjectGetQuery() (string, error) {
  303. q, err := gophercloud.BuildQueryString(opts)
  304. return q.String(), err
  305. }
  306. // Get is a function that retrieves the metadata of an object. To extract just
  307. // the custom metadata, pass the GetResult response to the ExtractMetadata
  308. // function.
  309. func Get(c *gophercloud.ServiceClient, containerName, objectName string, opts GetOptsBuilder) (r GetResult) {
  310. url := getURL(c, containerName, objectName)
  311. if opts != nil {
  312. query, err := opts.ToObjectGetQuery()
  313. if err != nil {
  314. r.Err = err
  315. return
  316. }
  317. url += query
  318. }
  319. resp, err := c.Head(url, &gophercloud.RequestOpts{
  320. OkCodes: []int{200, 204},
  321. })
  322. if resp != nil {
  323. r.Header = resp.Header
  324. }
  325. r.Err = err
  326. return
  327. }
  328. // UpdateOptsBuilder allows extensions to add additional parameters to the
  329. // Update request.
  330. type UpdateOptsBuilder interface {
  331. ToObjectUpdateMap() (map[string]string, error)
  332. }
  333. // UpdateOpts is a structure that holds parameters for updating, creating, or
  334. // deleting an object's metadata.
  335. type UpdateOpts struct {
  336. Metadata map[string]string
  337. ContentDisposition string `h:"Content-Disposition"`
  338. ContentEncoding string `h:"Content-Encoding"`
  339. ContentType string `h:"Content-Type"`
  340. DeleteAfter int `h:"X-Delete-After"`
  341. DeleteAt int `h:"X-Delete-At"`
  342. DetectContentType bool `h:"X-Detect-Content-Type"`
  343. }
  344. // ToObjectUpdateMap formats a UpdateOpts into a map of headers.
  345. func (opts UpdateOpts) ToObjectUpdateMap() (map[string]string, error) {
  346. h, err := gophercloud.BuildHeaders(opts)
  347. if err != nil {
  348. return nil, err
  349. }
  350. for k, v := range opts.Metadata {
  351. h["X-Object-Meta-"+k] = v
  352. }
  353. return h, nil
  354. }
  355. // Update is a function that creates, updates, or deletes an object's metadata.
  356. func Update(c *gophercloud.ServiceClient, containerName, objectName string, opts UpdateOptsBuilder) (r UpdateResult) {
  357. h := make(map[string]string)
  358. if opts != nil {
  359. headers, err := opts.ToObjectUpdateMap()
  360. if err != nil {
  361. r.Err = err
  362. return
  363. }
  364. for k, v := range headers {
  365. h[k] = v
  366. }
  367. }
  368. url := updateURL(c, containerName, objectName)
  369. resp, err := c.Post(url, nil, nil, &gophercloud.RequestOpts{
  370. MoreHeaders: h,
  371. })
  372. if resp != nil {
  373. r.Header = resp.Header
  374. }
  375. r.Err = err
  376. return
  377. }
  378. // HTTPMethod represents an HTTP method string (e.g. "GET").
  379. type HTTPMethod string
  380. var (
  381. // GET represents an HTTP "GET" method.
  382. GET HTTPMethod = "GET"
  383. // POST represents an HTTP "POST" method.
  384. POST HTTPMethod = "POST"
  385. )
  386. // CreateTempURLOpts are options for creating a temporary URL for an object.
  387. type CreateTempURLOpts struct {
  388. // (REQUIRED) Method is the HTTP method to allow for users of the temp URL.
  389. // Valid values are "GET" and "POST".
  390. Method HTTPMethod
  391. // (REQUIRED) TTL is the number of seconds the temp URL should be active.
  392. TTL int
  393. // (Optional) Split is the string on which to split the object URL. Since only
  394. // the object path is used in the hash, the object URL needs to be parsed. If
  395. // empty, the default OpenStack URL split point will be used ("/v1/").
  396. Split string
  397. }
  398. // CreateTempURL is a function for creating a temporary URL for an object. It
  399. // allows users to have "GET" or "POST" access to a particular tenant's object
  400. // for a limited amount of time.
  401. func CreateTempURL(c *gophercloud.ServiceClient, containerName, objectName string, opts CreateTempURLOpts) (string, error) {
  402. if opts.Split == "" {
  403. opts.Split = "/v1/"
  404. }
  405. duration := time.Duration(opts.TTL) * time.Second
  406. expiry := time.Now().Add(duration).Unix()
  407. getHeader, err := accounts.Get(c, nil).Extract()
  408. if err != nil {
  409. return "", err
  410. }
  411. secretKey := []byte(getHeader.TempURLKey)
  412. url := getURL(c, containerName, objectName)
  413. splitPath := strings.Split(url, opts.Split)
  414. baseURL, objectPath := splitPath[0], splitPath[1]
  415. objectPath = opts.Split + objectPath
  416. body := fmt.Sprintf("%s\n%d\n%s", opts.Method, expiry, objectPath)
  417. hash := hmac.New(sha1.New, secretKey)
  418. hash.Write([]byte(body))
  419. hexsum := fmt.Sprintf("%x", hash.Sum(nil))
  420. return fmt.Sprintf("%s%s?temp_url_sig=%s&temp_url_expires=%d", baseURL, objectPath, hexsum, expiry), nil
  421. }