New upstream version 1.0.52
Drew Parsons
3 years ago
12 | 12 | - 1.10.x |
13 | 13 | - 1.11.x |
14 | 14 | - 1.12.x |
15 | - 1.13.x | |
16 | - 1.14.x | |
15 | 17 | - master |
16 | 18 | |
17 | 19 | matrix: |
18 | 20 | include: |
19 | - go: 1.12.x | |
21 | - go: 1.14.x | |
20 | 22 | env: TEST_REAL_SERVER=rackspace |
21 | - go: 1.12.x | |
23 | - go: 1.14.x | |
22 | 24 | env: TEST_REAL_SERVER=memset |
23 | 25 | allow_failures: |
24 | - go: 1.12.x | |
26 | - go: 1.14.x | |
25 | 27 | env: TEST_REAL_SERVER=rackspace |
26 | - go: 1.12.x | |
28 | - go: 1.14.x | |
27 | 29 | env: TEST_REAL_SERVER=memset |
28 | 30 | install: go test -i ./... |
29 | 31 | script: |
156 | 156 | - Jérémy Clerc <jeremy.clerc@tagpay.fr> |
157 | 157 | - 4xicom <37339705+4xicom@users.noreply.github.com> |
158 | 158 | - Bo <bo@4xi.com> |
159 | - Thiago da Silva <thiagodasilva@users.noreply.github.com> | |
160 | - Brandon WELSCH <dev@brandon-welsch.eu> | |
161 | - Damien Tournoud <damien@platform.sh> | |
162 | - Pedro Kiefer <pedro@kiefer.com.br> |
221 | 221 | for i, obj := range objects { |
222 | 222 | filenames[i] = obj[0] + "/" + obj[1] |
223 | 223 | } |
224 | _, err = c.doBulkDelete(filenames) | |
224 | _, err = c.doBulkDelete(filenames, nil) | |
225 | 225 | // Don't fail on ObjectNotFound because eventual consistency |
226 | 226 | // makes this situation normal. |
227 | 227 | if err != nil && err != Forbidden && err != ObjectNotFound { |
124 | 124 | Expires time.Time // time the token expires, may be Zero if unknown |
125 | 125 | client *http.Client |
126 | 126 | Auth Authenticator `json:"-" xml:"-"` // the current authenticator |
127 | authLock sync.Mutex // lock when R/W StorageUrl, AuthToken, Auth | |
127 | authLock *sync.Mutex // lock when R/W StorageUrl, AuthToken, Auth | |
128 | 128 | // swiftInfo is filled after QueryInfo is called |
129 | 129 | swiftInfo SwiftInfo |
130 | 130 | } |
457 | 457 | // If you don't call it before calling one of the connection methods |
458 | 458 | // then it will be called for you on the first access. |
459 | 459 | func (c *Connection) Authenticate() (err error) { |
460 | if c.authLock == nil { | |
461 | c.authLock = &sync.Mutex{} | |
462 | } | |
460 | 463 | c.authLock.Lock() |
461 | 464 | defer c.authLock.Unlock() |
462 | 465 | return c.authenticate() |
579 | 582 | // |
580 | 583 | // Doesn't actually check the credentials against the server. |
581 | 584 | func (c *Connection) Authenticated() bool { |
585 | if c.authLock == nil { | |
586 | c.authLock = &sync.Mutex{} | |
587 | } | |
582 | 588 | c.authLock.Lock() |
583 | 589 | defer c.authLock.Unlock() |
584 | 590 | return c.authenticated() |
957 | 963 | return containers, nil |
958 | 964 | } |
959 | 965 | |
960 | // ContainerNamesAll is like ContainerNamess but it returns all the Containers | |
966 | // ContainerNamesAll is like ContainerNames but it returns all the Containers | |
961 | 967 | // |
962 | 968 | // It calls ContainerNames multiple times using the Marker parameter |
963 | 969 | // |
1365 | 1371 | return |
1366 | 1372 | } |
1367 | 1373 | |
1374 | // CloseWithError closes the object, aborting the upload. | |
1375 | func (file *ObjectCreateFile) CloseWithError(err error) error { | |
1376 | _ = file.pipeWriter.CloseWithError(err) | |
1377 | <-file.done | |
1378 | return nil | |
1379 | } | |
1380 | ||
1368 | 1381 | // Close the object and checks the md5sum if it was required. |
1369 | 1382 | // |
1370 | 1383 | // Also returns any other errors from the server (eg container not |
1481 | 1494 | pipeReader.Close() |
1482 | 1495 | close(file.done) |
1483 | 1496 | }() |
1497 | return | |
1498 | } | |
1499 | ||
1500 | func (c *Connection) ObjectSymlinkCreate(container string, symlink string, targetAccount string, targetContainer string, targetObject string, targetEtag string) (headers Headers, err error) { | |
1501 | ||
1502 | EMPTY_MD5 := "d41d8cd98f00b204e9800998ecf8427e" | |
1503 | symHeaders := Headers{} | |
1504 | contents := bytes.NewBufferString("") | |
1505 | if targetAccount != "" { | |
1506 | symHeaders["X-Symlink-Target-Account"] = targetAccount | |
1507 | } | |
1508 | if targetEtag != "" { | |
1509 | symHeaders["X-Symlink-Target-Etag"] = targetEtag | |
1510 | } | |
1511 | symHeaders["X-Symlink-Target"] = fmt.Sprintf("%s/%s", targetContainer, targetObject) | |
1512 | _, err = c.ObjectPut(container, symlink, contents, true, EMPTY_MD5, "application/symlink", symHeaders) | |
1484 | 1513 | return |
1485 | 1514 | } |
1486 | 1515 | |
1879 | 1908 | Headers Headers // Response HTTP headers. |
1880 | 1909 | } |
1881 | 1910 | |
1882 | func (c *Connection) doBulkDelete(objects []string) (result BulkDeleteResult, err error) { | |
1911 | func (c *Connection) doBulkDelete(objects []string, h Headers) (result BulkDeleteResult, err error) { | |
1883 | 1912 | var buffer bytes.Buffer |
1884 | 1913 | for _, s := range objects { |
1885 | 1914 | u := url.URL{Path: s} |
1886 | 1915 | buffer.WriteString(u.String() + "\n") |
1887 | 1916 | } |
1917 | extraHeaders := Headers{ | |
1918 | "Accept": "application/json", | |
1919 | "Content-Type": "text/plain", | |
1920 | "Content-Length": strconv.Itoa(buffer.Len()), | |
1921 | } | |
1922 | for key, value := range h { | |
1923 | extraHeaders[key] = value | |
1924 | } | |
1888 | 1925 | resp, headers, err := c.storage(RequestOpts{ |
1889 | 1926 | Operation: "DELETE", |
1890 | 1927 | Parameters: url.Values{"bulk-delete": []string{"1"}}, |
1891 | Headers: Headers{ | |
1892 | "Accept": "application/json", | |
1893 | "Content-Type": "text/plain", | |
1894 | "Content-Length": strconv.Itoa(buffer.Len()), | |
1895 | }, | |
1896 | ErrorMap: ContainerErrorMap, | |
1897 | Body: &buffer, | |
1928 | Headers: extraHeaders, | |
1929 | ErrorMap: ContainerErrorMap, | |
1930 | Body: &buffer, | |
1898 | 1931 | }) |
1899 | 1932 | if err != nil { |
1900 | 1933 | return |
1934 | 1967 | // * http://docs.openstack.org/trunk/openstack-object-storage/admin/content/object-storage-bulk-delete.html |
1935 | 1968 | // * http://docs.rackspace.com/files/api/v1/cf-devguide/content/Bulk_Delete-d1e2338.html |
1936 | 1969 | func (c *Connection) BulkDelete(container string, objectNames []string) (result BulkDeleteResult, err error) { |
1970 | return c.BulkDeleteHeaders(container, objectNames, nil) | |
1971 | } | |
1972 | ||
1973 | // BulkDeleteHeaders deletes multiple objectNames from container in one operation. | |
1974 | // | |
1975 | // Some servers may not accept bulk-delete requests since bulk-delete is | |
1976 | // an optional feature of swift - these will return the Forbidden error. | |
1977 | // | |
1978 | // See also: | |
1979 | // * http://docs.openstack.org/trunk/openstack-object-storage/admin/content/object-storage-bulk-delete.html | |
1980 | // * http://docs.rackspace.com/files/api/v1/cf-devguide/content/Bulk_Delete-d1e2338.html | |
1981 | func (c *Connection) BulkDeleteHeaders(container string, objectNames []string, h Headers) (result BulkDeleteResult, err error) { | |
1937 | 1982 | if len(objectNames) == 0 { |
1938 | 1983 | result.Errors = make(map[string]error) |
1939 | 1984 | return |
1942 | 1987 | for i, name := range objectNames { |
1943 | 1988 | fullPaths[i] = fmt.Sprintf("/%s/%s", container, name) |
1944 | 1989 | } |
1945 | return c.doBulkDelete(fullPaths) | |
1990 | return c.doBulkDelete(fullPaths, h) | |
1946 | 1991 | } |
1947 | 1992 | |
1948 | 1993 | // BulkUploadResult stores results of BulkUpload(). |
50 | 50 | CURRENT_CONTAINER = "GoSwiftUnitTestCurrent" |
51 | 51 | OBJECT = "test_object" |
52 | 52 | OBJECT2 = "test_object2" |
53 | SYMLINK_OBJECT = "test_symlink" | |
54 | SYMLINK_OBJECT2 = "test_symlink2" | |
53 | 55 | EMPTYOBJECT = "empty_test_object" |
54 | 56 | CONTENTS = "12345" |
55 | 57 | CONTENTS2 = "54321" |
56 | 58 | CONTENT_SIZE = int64(len(CONTENTS)) |
57 | 59 | CONTENT_MD5 = "827ccb0eea8a706c4c34a16891f84e7b" |
60 | CONTENT2_MD5 = "01cfcd4f6b8770febfb40cb906715822" | |
58 | 61 | EMPTY_MD5 = "d41d8cd98f00b204e9800998ecf8427e" |
59 | 62 | SECRET_KEY = "b3968d0207b54ece87cccc06515a89d4" |
60 | 63 | ) |
296 | 299 | func isV3Api() bool { |
297 | 300 | AuthUrl := os.Getenv("SWIFT_AUTH_URL") |
298 | 301 | return strings.Contains(AuthUrl, "v3") |
302 | } | |
303 | ||
304 | func getSwinftInfo(t *testing.T) (info swift.SwiftInfo, err error) { | |
305 | c, rollback := makeConnectionAuth(t) | |
306 | defer rollback() | |
307 | return c.QueryInfo() | |
299 | 308 | } |
300 | 309 | |
301 | 310 | func TestTransport(t *testing.T) { |
907 | 916 | } |
908 | 917 | } |
909 | 918 | |
919 | func TestSymlinkObject(t *testing.T) { | |
920 | info, err := getSwinftInfo(t) | |
921 | if err != nil { | |
922 | t.Fatal(err) | |
923 | } | |
924 | if _, ok := info["symlink"]; !ok { | |
925 | // skip, symlink not supported | |
926 | t.Skip("skip, symlink not supported") | |
927 | return | |
928 | } | |
929 | c, rollback := makeConnectionWithContainer(t) | |
930 | defer rollback() | |
931 | ||
932 | // write target objects | |
933 | err = c.ObjectPutBytes(CONTAINER, OBJECT, []byte(CONTENTS), "text/potato") | |
934 | if err != nil { | |
935 | t.Fatal(err) | |
936 | } | |
937 | defer func() { | |
938 | err = c.ObjectDelete(CONTAINER, OBJECT) | |
939 | if err != nil { | |
940 | t.Error(err) | |
941 | } | |
942 | }() | |
943 | ||
944 | // test dynamic link | |
945 | _, err = c.ObjectSymlinkCreate(CONTAINER, SYMLINK_OBJECT, "", CONTAINER, OBJECT, "") | |
946 | if err != nil { | |
947 | t.Fatal(err) | |
948 | } | |
949 | defer func() { | |
950 | err = c.ObjectDelete(CONTAINER, SYMLINK_OBJECT) | |
951 | if err != nil { | |
952 | t.Error(err) | |
953 | } | |
954 | }() | |
955 | ||
956 | md, _, err := c.Object(CONTAINER, SYMLINK_OBJECT) | |
957 | if err != nil { | |
958 | t.Error(err) | |
959 | } | |
960 | if md.ContentType != "text/potato" { | |
961 | t.Error("Bad content type", md.ContentType) | |
962 | } | |
963 | if md.Bytes != CONTENT_SIZE { | |
964 | t.Errorf("Bad length want 5 got %v", md.Bytes) | |
965 | } | |
966 | if md.Hash != CONTENT_MD5 { | |
967 | t.Errorf("Bad MD5 want %v got %v", CONTENT_MD5, md.Hash) | |
968 | } | |
969 | ||
970 | } | |
971 | ||
972 | func TestStaticSymlinkObject(t *testing.T) { | |
973 | info, err := getSwinftInfo(t) | |
974 | if err != nil { | |
975 | t.Fatal(err) | |
976 | } | |
977 | if sym, ok := info["symlink"].(map[string]interface{}); ok { | |
978 | if _, ok := sym["static_links"]; !ok { | |
979 | t.Skip("skip, static symlink not supported") | |
980 | return | |
981 | } | |
982 | } else { | |
983 | t.Skip("skip, symlink not supported") | |
984 | return | |
985 | } | |
986 | ||
987 | c, rollback := makeConnectionWithContainer(t) | |
988 | defer rollback() | |
989 | ||
990 | // write target objects | |
991 | err = c.ObjectPutBytes(CONTAINER, OBJECT2, []byte(CONTENTS2), "text/tomato") | |
992 | if err != nil { | |
993 | t.Fatal(err) | |
994 | } | |
995 | defer func() { | |
996 | err = c.ObjectDelete(CONTAINER, OBJECT2) | |
997 | if err != nil { | |
998 | t.Error(err) | |
999 | } | |
1000 | }() | |
1001 | ||
1002 | // test static link | |
1003 | // first with the wrong target etag | |
1004 | _, err = c.ObjectSymlinkCreate(CONTAINER, SYMLINK_OBJECT2, "", CONTAINER, OBJECT2, CONTENT_MD5) | |
1005 | if err == nil { | |
1006 | t.Error("Symlink with wrong target etag should have failed") | |
1007 | } | |
1008 | ||
1009 | _, err = c.ObjectSymlinkCreate(CONTAINER, SYMLINK_OBJECT2, "", CONTAINER, OBJECT2, CONTENT2_MD5) | |
1010 | if err != nil { | |
1011 | t.Fatal(err) | |
1012 | } | |
1013 | defer func() { | |
1014 | err = c.ObjectDelete(CONTAINER, SYMLINK_OBJECT2) | |
1015 | if err != nil { | |
1016 | t.Error(err) | |
1017 | } | |
1018 | }() | |
1019 | ||
1020 | md, _, err := c.Object(CONTAINER, SYMLINK_OBJECT2) | |
1021 | if err != nil { | |
1022 | t.Error(err) | |
1023 | } | |
1024 | if md.ContentType != "text/tomato" { | |
1025 | t.Error("Bad content type", md.ContentType) | |
1026 | } | |
1027 | if md.Bytes != CONTENT_SIZE { | |
1028 | t.Errorf("Bad length want 5 got %v", md.Bytes) | |
1029 | } | |
1030 | if md.Hash != CONTENT2_MD5 { | |
1031 | t.Errorf("Bad MD5 want %v got %v", CONTENT2_MD5, md.Hash) | |
1032 | } | |
1033 | } | |
1034 | ||
910 | 1035 | func TestObjectPutBytes(t *testing.T) { |
911 | 1036 | c, rollback := makeConnectionWithContainer(t) |
912 | 1037 | defer rollback() |
1042 | 1167 | err = out.Close() |
1043 | 1168 | if err != swift.ObjectCorrupted { |
1044 | 1169 | t.Error("Expecting object corrupted not", err) |
1170 | } | |
1171 | } | |
1172 | ||
1173 | func TestObjectCreateAbort(t *testing.T) { | |
1174 | c, rollback := makeConnectionWithContainer(t) | |
1175 | defer rollback() | |
1176 | ||
1177 | out, err := c.ObjectCreate(CONTAINER, OBJECT2, true, "", "", nil) | |
1178 | if err != nil { | |
1179 | t.Fatal(err) | |
1180 | } | |
1181 | defer func() { | |
1182 | _ = c.ObjectDelete(CONTAINER, OBJECT2) // Ignore error | |
1183 | }() | |
1184 | ||
1185 | expectedContents := "foo" | |
1186 | _, err = out.Write([]byte(expectedContents)) | |
1187 | if err != nil { | |
1188 | t.Error(err) | |
1189 | } | |
1190 | ||
1191 | errAbort := fmt.Errorf("abort") | |
1192 | err = out.CloseWithError(errAbort) | |
1193 | if err != nil { | |
1194 | t.Errorf("Unexpected error %#v", err) | |
1195 | } | |
1196 | ||
1197 | _, err = c.ObjectGetString(CONTAINER, OBJECT2) | |
1198 | if err != swift.ObjectNotFound { | |
1199 | t.Errorf("Unexpected error: %#v", err) | |
1045 | 1200 | } |
1046 | 1201 | } |
1047 | 1202 |
6 | 6 | package swifttest |
7 | 7 | |
8 | 8 | import ( |
9 | "archive/tar" | |
9 | 10 | "bytes" |
11 | "compress/bzip2" | |
12 | "compress/gzip" | |
10 | 13 | "crypto/hmac" |
11 | 14 | "crypto/md5" |
12 | 15 | "crypto/rand" |
23 | 26 | "net/http/httptest" |
24 | 27 | "net/url" |
25 | 28 | "path" |
29 | "reflect" | |
26 | 30 | "regexp" |
27 | 31 | "sort" |
28 | 32 | "strconv" |
33 | 37 | "time" |
34 | 38 | ) |
35 | 39 | |
36 | const ( | |
40 | var ( | |
37 | 41 | DEBUG = false |
38 | 42 | TEST_ACCOUNT = "swifttest" |
39 | 43 | ) |
79 | 83 | Subdir string `json:"subdir"` |
80 | 84 | } |
81 | 85 | |
86 | type AutoExtractResponse struct { | |
87 | CreatedFiles int64 `json:"Number Files Created"` | |
88 | Status string `json:"Response Status"` | |
89 | Errors [][]string `json:"Errors"` | |
90 | } | |
91 | ||
82 | 92 | type swiftError struct { |
83 | 93 | statusCode int |
84 | 94 | Code string |
347 | 357 | } |
348 | 358 | |
349 | 359 | func (r containerResource) put(a *action) interface{} { |
350 | if a.req.URL.Query().Get("extract-archive") != "" { | |
351 | fatalf(403, "Operation forbidden", "Bulk upload is not supported") | |
352 | } | |
353 | ||
354 | 360 | if r.container == nil { |
355 | 361 | if !validContainerName(r.name) { |
356 | 362 | fatalf(400, "InvalidContainerName", "The specified container is not valid") |
368 | 374 | a.user.Containers[r.name] = r.container |
369 | 375 | a.user.swiftaccount.Containers++ |
370 | 376 | a.user.Unlock() |
377 | } | |
378 | ||
379 | if format := a.req.URL.Query().Get("extract-archive"); format != "" { | |
380 | _, _, objectName, _ := a.srv.parseURL(a.req.URL) | |
381 | ||
382 | data, err := ioutil.ReadAll(a.req.Body) | |
383 | if err != nil { | |
384 | fatalf(400, "TODO", "read error") | |
385 | } | |
386 | if a.req.ContentLength >= 0 && int64(len(data)) != a.req.ContentLength { | |
387 | fatalf(400, "IncompleteBody", "You did not provide the number of bytes specified by the Content-Length HTTP header") | |
388 | } | |
389 | ||
390 | dataReader := bytes.NewReader(data) | |
391 | var reader *tar.Reader | |
392 | switch format { | |
393 | case "tar": | |
394 | reader = tar.NewReader(dataReader) | |
395 | case "tar.gz": | |
396 | gzr, err := gzip.NewReader(dataReader) | |
397 | if err != nil { | |
398 | fatalf(400, "TODO", "Invalid tar.gz") | |
399 | } | |
400 | defer gzr.Close() | |
401 | reader = tar.NewReader(gzr) | |
402 | case "tar.bz2": | |
403 | bzr := bzip2.NewReader(dataReader) | |
404 | reader = tar.NewReader(bzr) | |
405 | default: | |
406 | fatalf(400, "TODO", "Invalid format %s", format) | |
407 | } | |
408 | ||
409 | resp := AutoExtractResponse{} | |
410 | for { | |
411 | header, err := reader.Next() | |
412 | if err == io.EOF { | |
413 | break | |
414 | } else if err != nil { | |
415 | //return location, err | |
416 | } | |
417 | if header == nil { | |
418 | continue | |
419 | } | |
420 | ||
421 | if header.Typeflag == tar.TypeDir { | |
422 | continue | |
423 | } | |
424 | ||
425 | var fullPath string | |
426 | if objectName != "" { | |
427 | fullPath = objectName + "/" + header.Name | |
428 | } else { | |
429 | fullPath = header.Name | |
430 | } | |
431 | ||
432 | obj := r.container.objects[fullPath] | |
433 | if obj == nil { | |
434 | // new object | |
435 | obj = &object{ | |
436 | name: fullPath, | |
437 | metadata: metadata{ | |
438 | meta: make(http.Header), | |
439 | }, | |
440 | } | |
441 | atomic.AddInt64(&a.user.Objects, 1) | |
442 | } else { | |
443 | atomic.AddInt64(&r.container.bytes, -header.Size) | |
444 | atomic.AddInt64(&a.user.BytesUsed, -header.Size) | |
445 | } | |
446 | ||
447 | // Default content_type | |
448 | obj.content_type = "application/octet-stream" | |
449 | ||
450 | // handle extended attributes | |
451 | records := getPAXRecords(header) | |
452 | for k, v := range records { | |
453 | ks := strings.SplitN(k, "SCHILY.xattr.user.", 2) | |
454 | if len(ks) < 2 { | |
455 | continue | |
456 | } | |
457 | ||
458 | if ks[1] == "mime_type" { | |
459 | obj.content_type = v | |
460 | } | |
461 | ||
462 | if strings.HasPrefix(ks[1], "meta.") { | |
463 | meta := strings.TrimLeft(ks[1], "meta.") | |
464 | obj.meta["X-Object-Meta-"+strings.Title(meta)] = []string{v} | |
465 | } | |
466 | } | |
467 | ||
468 | sum := md5.New() | |
469 | objData, err := ioutil.ReadAll(io.TeeReader(reader, sum)) | |
470 | if err != nil { | |
471 | errArr := []string{fullPath, fmt.Sprintf("read error: %v", err)} | |
472 | resp.Errors = append(resp.Errors, errArr) | |
473 | continue | |
474 | } | |
475 | gotHash := sum.Sum(nil) | |
476 | ||
477 | obj.data = objData | |
478 | obj.checksum = gotHash | |
479 | obj.mtime = time.Now().UTC() | |
480 | r.container.Lock() | |
481 | r.container.objects[fullPath] = obj | |
482 | r.container.bytes += header.Size | |
483 | r.container.Unlock() | |
484 | ||
485 | atomic.AddInt64(&a.user.BytesUsed, header.Size) | |
486 | atomic.AddInt64(&resp.CreatedFiles, 1) | |
487 | } | |
488 | ||
489 | resp.Status = "201 Accepted" | |
490 | status := 201 | |
491 | if len(resp.Errors) > 0 { | |
492 | resp.Status = "400 Error" | |
493 | status = 400 | |
494 | } | |
495 | a.w.Header().Set("Content-Type", "application/json") | |
496 | a.w.WriteHeader(status) | |
497 | jsonMarshal(a.w, resp) | |
371 | 498 | } |
372 | 499 | |
373 | 500 | return nil |
392 | 519 | } |
393 | 520 | |
394 | 521 | func (containerResource) copy(a *action) interface{} { return notAllowed() } |
522 | ||
523 | func getPAXRecords(h *tar.Header) map[string]string { | |
524 | rHeader := reflect.ValueOf(h) | |
525 | ||
526 | // Try PAXRecords - go 1.10 | |
527 | paxField := rHeader.Elem().FieldByName("PAXRecords") | |
528 | if paxField.IsValid() { | |
529 | return paxField.Interface().(map[string]string) | |
530 | } | |
531 | ||
532 | // Try Xattrs - go 1.3 | |
533 | xAttrsField := rHeader.Elem().FieldByName("Xattrs") | |
534 | if xAttrsField.IsValid() { | |
535 | return xAttrsField.Interface().(map[string]string) | |
536 | } | |
537 | return map[string]string{} | |
538 | } | |
395 | 539 | |
396 | 540 | // validContainerName returns whether name is a valid bucket name. |
397 | 541 | // Here are the rules, from: |
782 | 926 | defer func() { |
783 | 927 | switch err := recover().(type) { |
784 | 928 | case *swiftError: |
929 | if DEBUG { | |
930 | fmt.Printf("\t%d - %s\n", err.statusCode, err.Message) | |
931 | } | |
785 | 932 | w.Header().Set("Content-Type", `text/plain; charset=utf-8`) |
786 | 933 | http.Error(w, err.Message, err.statusCode) |
787 | 934 | case nil: |
788 | 935 | default: |
936 | if DEBUG { | |
937 | fmt.Printf("\tpanic %s\n", err) | |
938 | } | |
789 | 939 | panic(err) |
790 | 940 | } |
791 | 941 | }() |
1062 | 1212 | return nil |
1063 | 1213 | } |
1064 | 1214 | |
1065 | func (rootResource) delete(a *action) interface{} { | |
1215 | func (r rootResource) delete(a *action) interface{} { | |
1066 | 1216 | if a.req.URL.Query().Get("bulk-delete") == "1" { |
1067 | fatalf(403, "Operation forbidden", "Bulk delete is not supported") | |
1217 | data, err := ioutil.ReadAll(a.req.Body) | |
1218 | if err != nil { | |
1219 | fatalf(400, "Bad Request", "read error") | |
1220 | } | |
1221 | var nb, notFound int | |
1222 | for _, obj := range strings.Fields(string(data)) { | |
1223 | parts := strings.SplitN(obj, "/", 3) | |
1224 | if len(parts) < 3 { | |
1225 | fatalf(403, "Operation forbidden", "Bulk delete is not supported for containers") | |
1226 | } | |
1227 | b := containerResource{ | |
1228 | name: parts[1], | |
1229 | container: a.user.Containers[parts[1]], | |
1230 | } | |
1231 | if b.container == nil { | |
1232 | notFound++ | |
1233 | continue | |
1234 | } | |
1235 | ||
1236 | objr := objectResource{ | |
1237 | name: parts[2], | |
1238 | container: b.container, | |
1239 | } | |
1240 | objr.container.RLock() | |
1241 | if obj := objr.container.objects[objr.name]; obj != nil { | |
1242 | objr.object = obj | |
1243 | } | |
1244 | objr.container.RUnlock() | |
1245 | if objr.object == nil { | |
1246 | notFound++ | |
1247 | continue | |
1248 | } | |
1249 | ||
1250 | objr.container.Lock() | |
1251 | objr.object.Lock() | |
1252 | objr.container.bytes -= int64(len(objr.object.data)) | |
1253 | delete(objr.container.objects, objr.name) | |
1254 | objr.object.Unlock() | |
1255 | objr.container.Unlock() | |
1256 | ||
1257 | atomic.AddInt64(&a.user.BytesUsed, -int64(len(objr.object.data))) | |
1258 | atomic.AddInt64(&a.user.Objects, -1) | |
1259 | nb++ | |
1260 | } | |
1261 | ||
1262 | accept := a.req.Header.Get("Accept") | |
1263 | if strings.HasPrefix(accept, "application/json") { | |
1264 | a.w.Header().Set("Content-Type", "application/json") | |
1265 | resp := map[string]interface{}{ | |
1266 | "Number Deleted": nb, | |
1267 | "Number Not Found": notFound, | |
1268 | "Errors": []string{}, | |
1269 | "Response Status": "200 OK", | |
1270 | "Response Body": "", | |
1271 | } | |
1272 | jsonMarshal(a.w, resp) | |
1273 | return nil | |
1274 | } | |
1275 | ||
1276 | resp := fmt.Sprintf("Number Deleted: %d\nNumber Not Found: %d\nErrors: \nResponse Status: 200 OK\n", nb, notFound) | |
1277 | a.w.Write([]byte(resp)) | |
1278 | return nil | |
1068 | 1279 | } |
1069 | 1280 | |
1070 | 1281 | return notAllowed() |