storage_test.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. // Copyright 2014 Google Inc. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package storage
  15. import (
  16. "fmt"
  17. "io/ioutil"
  18. "log"
  19. "net/http"
  20. "strings"
  21. "testing"
  22. "time"
  23. "golang.org/x/net/context"
  24. "google.golang.org/cloud"
  25. )
  26. func TestSignedURL(t *testing.T) {
  27. expires, _ := time.Parse(time.RFC3339, "2002-10-02T10:00:00-05:00")
  28. url, err := SignedURL("bucket-name", "object-name", &SignedURLOptions{
  29. GoogleAccessID: "xxx@clientid",
  30. PrivateKey: dummyKey("rsa"),
  31. Method: "GET",
  32. MD5: []byte("202cb962ac59075b964b07152d234b70"),
  33. Expires: expires,
  34. ContentType: "application/json",
  35. Headers: []string{"x-header1", "x-header2"},
  36. })
  37. if err != nil {
  38. t.Error(err)
  39. }
  40. want := "https://storage.googleapis.com/bucket-name/object-name?" +
  41. "Expires=1033570800&GoogleAccessId=xxx%40clientid&Signature=" +
  42. "ITqNWQHr7ayIj%2B0Ds5%2FzUT2cWMQQouuFmu6L11Zd3kfNKvm3sjyGIzO" +
  43. "gZsSUoter1SxP7BcrCzgqIZ9fQmgQnuIpqqLL4kcGmTbKsQS6hTknpJM%2F" +
  44. "2lS4NY6UH1VXBgm2Tce28kz8rnmqG6svcGvtWuOgJsETeSIl1R9nAEIDCEq" +
  45. "ZJzoOiru%2BODkHHkpoFjHWAwHugFHX%2B9EX4SxaytiN3oEy48HpYGWV0I" +
  46. "h8NvU1hmeWzcLr41GnTADeCn7Eg%2Fb5H2GCNO70Cz%2Bw2fn%2BofLCUeR" +
  47. "YQd%2FhES8oocv5kpHZkstc8s8uz3aKMsMauzZ9MOmGy%2F6VULBgIVvi6a" +
  48. "AwEBIYOw%3D%3D"
  49. if url != want {
  50. t.Fatalf("Unexpected signed URL; found %v", url)
  51. }
  52. }
  53. func TestSignedURL_PEMPrivateKey(t *testing.T) {
  54. expires, _ := time.Parse(time.RFC3339, "2002-10-02T10:00:00-05:00")
  55. url, err := SignedURL("bucket-name", "object-name", &SignedURLOptions{
  56. GoogleAccessID: "xxx@clientid",
  57. PrivateKey: dummyKey("pem"),
  58. Method: "GET",
  59. MD5: []byte("202cb962ac59075b964b07152d234b70"),
  60. Expires: expires,
  61. ContentType: "application/json",
  62. Headers: []string{"x-header1", "x-header2"},
  63. })
  64. if err != nil {
  65. t.Error(err)
  66. }
  67. want := "https://storage.googleapis.com/bucket-name/object-name?" +
  68. "Expires=1033570800&GoogleAccessId=xxx%40clientid&Signature=" +
  69. "B7XkS4dfmVDoe%2FoDeXZkWlYmg8u2kI0SizTrzL5%2B9RmKnb5j7Kf34DZ" +
  70. "JL8Hcjr1MdPFLNg2QV4lEH86Gqgqt%2Fv3jFOTRl4wlzcRU%2FvV5c5HU8M" +
  71. "qW0FZ0IDbqod2RdsMONLEO6yQWV2HWFrMLKl2yMFlWCJ47et%2BFaHe6v4Z" +
  72. "EBc0%3D"
  73. if url != want {
  74. t.Fatalf("Unexpected signed URL; found %v", url)
  75. }
  76. }
  77. func TestSignedURL_MissingOptions(t *testing.T) {
  78. pk := dummyKey("rsa")
  79. var tests = []struct {
  80. opts *SignedURLOptions
  81. errMsg string
  82. }{
  83. {
  84. &SignedURLOptions{},
  85. "missing required credentials",
  86. },
  87. {
  88. &SignedURLOptions{GoogleAccessID: "access_id"},
  89. "missing required credentials",
  90. },
  91. {
  92. &SignedURLOptions{
  93. GoogleAccessID: "access_id",
  94. PrivateKey: pk,
  95. },
  96. "missing required method",
  97. },
  98. {
  99. &SignedURLOptions{
  100. GoogleAccessID: "access_id",
  101. PrivateKey: pk,
  102. Method: "PUT",
  103. },
  104. "missing required expires",
  105. },
  106. }
  107. for _, test := range tests {
  108. _, err := SignedURL("bucket", "name", test.opts)
  109. if !strings.Contains(err.Error(), test.errMsg) {
  110. t.Errorf("expected err: %v, found: %v", test.errMsg, err)
  111. }
  112. }
  113. }
  114. func dummyKey(kind string) []byte {
  115. slurp, err := ioutil.ReadFile(fmt.Sprintf("./testdata/dummy_%s", kind))
  116. if err != nil {
  117. log.Fatal(err)
  118. }
  119. return slurp
  120. }
  121. func TestCopyObjectMissingFields(t *testing.T) {
  122. var tests = []struct {
  123. srcBucket, srcName, destBucket, destName string
  124. errMsg string
  125. }{
  126. {
  127. "mybucket", "", "mybucket", "destname",
  128. "srcName and destName must be non-empty",
  129. },
  130. {
  131. "mybucket", "srcname", "mybucket", "",
  132. "srcName and destName must be non-empty",
  133. },
  134. {
  135. "", "srcfile", "mybucket", "destname",
  136. "srcBucket and destBucket must both be non-empty",
  137. },
  138. {
  139. "mybucket", "srcfile", "", "destname",
  140. "srcBucket and destBucket must both be non-empty",
  141. },
  142. }
  143. ctx := context.Background()
  144. client, err := NewClient(ctx, cloud.WithBaseHTTP(&http.Client{Transport: &fakeTransport{}}))
  145. if err != nil {
  146. panic(err)
  147. }
  148. for i, test := range tests {
  149. _, err := client.CopyObject(ctx, test.srcBucket, test.srcName, test.destBucket, test.destName, nil)
  150. if !strings.Contains(err.Error(), test.errMsg) {
  151. t.Errorf("CopyObject test #%v: err = %v, want %v", i, err, test.errMsg)
  152. }
  153. }
  154. }
  155. func TestObjectNames(t *testing.T) {
  156. // Naming requirements: https://cloud.google.com/storage/docs/bucket-naming
  157. const maxLegalLength = 1024
  158. type testT struct {
  159. name, want string
  160. }
  161. tests := []testT{
  162. // Embedded characters important in URLs.
  163. {"foo % bar", "foo%20%25%20bar"},
  164. {"foo ? bar", "foo%20%3F%20bar"},
  165. {"foo / bar", "foo%20/%20bar"},
  166. {"foo %?/ bar", "foo%20%25%3F/%20bar"},
  167. // Non-Roman scripts
  168. {"타코", "%ED%83%80%EC%BD%94"},
  169. {"世界", "%E4%B8%96%E7%95%8C"},
  170. // Longest legal name
  171. {strings.Repeat("a", maxLegalLength), strings.Repeat("a", maxLegalLength)},
  172. // Line terminators besides CR and LF: https://en.wikipedia.org/wiki/Newline#Unicode
  173. {"foo \u000b bar", "foo%20%0B%20bar"},
  174. {"foo \u000c bar", "foo%20%0C%20bar"},
  175. {"foo \u0085 bar", "foo%20%C2%85%20bar"},
  176. {"foo \u2028 bar", "foo%20%E2%80%A8%20bar"},
  177. {"foo \u2029 bar", "foo%20%E2%80%A9%20bar"},
  178. // Null byte.
  179. {"foo \u0000 bar", "foo%20%00%20bar"},
  180. // Non-control characters that are discouraged, but not forbidden, according to the documentation.
  181. {"foo # bar", "foo%20%23%20bar"},
  182. {"foo []*? bar", "foo%20%5B%5D%2A%3F%20bar"},
  183. // Angstrom symbol singleton and normalized forms: http://unicode.org/reports/tr15/
  184. {"foo \u212b bar", "foo%20%E2%84%AB%20bar"},
  185. {"foo \u0041\u030a bar", "foo%20A%CC%8A%20bar"},
  186. {"foo \u00c5 bar", "foo%20%C3%85%20bar"},
  187. // Hangul separating jamo: http://www.unicode.org/versions/Unicode7.0.0/ch18.pdf (Table 18-10)
  188. {"foo \u3131\u314f bar", "foo%20%E3%84%B1%E3%85%8F%20bar"},
  189. {"foo \u1100\u1161 bar", "foo%20%E1%84%80%E1%85%A1%20bar"},
  190. {"foo \uac00 bar", "foo%20%EA%B0%80%20bar"},
  191. }
  192. // C0 control characters not forbidden by the docs.
  193. var runes []rune
  194. for r := rune(0x01); r <= rune(0x1f); r++ {
  195. if r != '\u000a' && r != '\u000d' {
  196. runes = append(runes, r)
  197. }
  198. }
  199. tests = append(tests, testT{fmt.Sprintf("foo %s bar", string(runes)), "foo%20%01%02%03%04%05%06%07%08%09%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20bar"})
  200. // C1 control characters, plus DEL.
  201. runes = nil
  202. for r := rune(0x7f); r <= rune(0x9f); r++ {
  203. runes = append(runes, r)
  204. }
  205. tests = append(tests, testT{fmt.Sprintf("foo %s bar", string(runes)), "foo%20%7F%C2%80%C2%81%C2%82%C2%83%C2%84%C2%85%C2%86%C2%87%C2%88%C2%89%C2%8A%C2%8B%C2%8C%C2%8D%C2%8E%C2%8F%C2%90%C2%91%C2%92%C2%93%C2%94%C2%95%C2%96%C2%97%C2%98%C2%99%C2%9A%C2%9B%C2%9C%C2%9D%C2%9E%C2%9F%20bar"})
  206. opts := &SignedURLOptions{
  207. GoogleAccessID: "xxx@clientid",
  208. PrivateKey: dummyKey("rsa"),
  209. Method: "GET",
  210. MD5: []byte("202cb962ac59075b964b07152d234b70"),
  211. Expires: time.Date(2002, time.October, 2, 10, 0, 0, 0, time.UTC),
  212. ContentType: "application/json",
  213. Headers: []string{"x-header1", "x-header2"},
  214. }
  215. for _, test := range tests {
  216. g, err := SignedURL("bucket-name", test.name, opts)
  217. if err != nil {
  218. t.Errorf("SignedURL(%q) err=%v, want nil", test.name, err)
  219. }
  220. if w := "/bucket-name/" + test.want; !strings.Contains(g, w) {
  221. t.Errorf("SignedURL(%q)=%q, want substring %q", test.name, g, w)
  222. }
  223. }
  224. }