//This implementation of the GET operation returns a list of all buckets owned by the authenticated sender of the request. func (s *service) serviceGet(w http.ResponseWriter, r *http.Request) { owner, err := s3intf.GetOwner(s, r, s.fqdn) log.Printf("%#v.serviceGet owner=%s err=%s", s, owner.ID(), err) if err != nil { writeError(w, &HTTPError{Code: 5, Message: "error getting owner: " + err.Error()}) return } else if owner == nil { writeError(w, &HTTPError{Code: 6, HTTPCode: 403, Message: "no owner"}) return } buckets, err := s.ListBuckets(owner) if err != nil { writeError(w, &HTTPError{Code: 7, Message: err.Error()}) return } w.Header().Set("Content-Type", "text/xml") bw := bufio.NewWriter(w) bw.WriteString(`<?xml version="1.0" encoding="UTF-8"?> <ListAllMyBucketsResult xmlns="http://doc.s3.amazonaws.com/2006-03-01"> <Owner><ID>` + owner.ID() + "</ID><DisplayName>" + owner.Name() + "</DisplayName></Owner><Buckets>") for _, bucket := range buckets { bw.WriteString("<Bucket><Name>" + bucket.Name + "</Name>") bw.WriteString("<CreationDate>" + bucket.Created.Format(S3Date) + "</CreationDate></Bucket>") } bw.WriteString("</Buckets></ListAllMyBucketsResult>") bw.Flush() }
//This operation is useful to determine if a bucket exists and you have permission to access it. //The operation returns a 200 OK if the bucket exists and you have permission to access it. //Otherwise, the operation might return responses such as 404 Not Found and 403 Forbidden. func (bucket bucketHandler) check(w http.ResponseWriter, r *http.Request) { owner, err := s3intf.GetOwner(bucket.Service, r, bucket.Service.Host()) if err != nil { writeError(w, &HTTPError{Code: 15, HTTPCode: http.StatusBadRequest, Message: "error getting owner: " + err.Error(), Resource: "/" + bucket.Name}) return } if bucket.Service.CheckBucket(owner, bucket.Name) { w.WriteHeader(http.StatusOK) return } w.WriteHeader(http.StatusNotFound) return }
//This implementation of the DELETE operation deletes the bucket named in the URI. //All objects (including all object versions and Delete Markers) in the bucket //must be deleted before the bucket itself can be deleted. func (bucket bucketHandler) del(w http.ResponseWriter, r *http.Request) { owner, err := s3intf.GetOwner(bucket.Service, r, bucket.Service.fqdn) if err != nil { writeError(w, &HTTPError{Code: 8, Message: "error getting owner: " + err.Error()}) return } if err := bucket.Service.DelBucket(owner, bucket.Name); err != nil { if err == s3intf.NotFound { w.WriteHeader(http.StatusNotFound) return } writeError(w, &HTTPError{Code: 9, Message: err.Error(), Resource: "/" + bucket.Name}) return } }
func (obj objectHandler) get(w http.ResponseWriter, r *http.Request) { owner, err := s3intf.GetOwner(obj.Bucket.Service, r, obj.Bucket.Service.Host()) if err != nil { writeError(w, &HTTPError{Code: 20, HTTPCode: http.StatusBadRequest, Message: "error getting owner: " + err.Error(), Resource: "/" + obj.Bucket.Name + "/" + obj.object}) return } fn, media, body, size, md5, err := obj.Bucket.Service.Get(owner, obj.Bucket.Name, obj.object) log.Printf("GETing %s/%s: %q %s", obj.Bucket.Name, obj.object, fn, err) if err != nil { if err == s3intf.NotFound { w.WriteHeader(http.StatusNotFound) return } writeError(w, &HTTPError{Code: 21, Message: "error getting " + obj.Bucket.Name + "/" + obj.object + ": " + err.Error(), Resource: "/" + obj.Bucket.Name + "/" + obj.object}) return } if err = r.ParseForm(); err != nil { writeError(w, &HTTPError{Code: 22, HTTPCode: http.StatusBadRequest, Message: "cannot parse form values: " + err.Error(), Resource: "/" + obj.Bucket.Name + "/" + obj.object}) return } w.Header().Set("Content-Type", media) w.Header().Set("Content-Disposition", "inline; filename=\""+fn+"\"") w.Header().Set("Content-Length", strconv.Itoa(int(size))) w.Header().Set("ETag", hex.EncodeToString(md5)) for k, v := range r.Form { k = textproto.CanonicalMIMEHeaderKey(k) switch k { case "Content-Type", "Content-Language", "Expires", "Cache-Control", "Content-Disposition", "Content-Encoding", "Content-Length": (map[string][]string(w.Header()))[k] = v } } if Debug { log.Printf("headers: %s", w.Header()) } io.Copy(w, body) }
//This implementation of the PUT operation creates a new bucket. //Anonymous requests are never allowed to create buckets. //By creating the bucket, you become the bucket owner. // //Not every string is an acceptable bucket name. For information on bucket naming restrictions, see Working with Amazon S3 Buckets. //DNS name constraints -> max length is 63 func (bucket bucketHandler) put(w http.ResponseWriter, r *http.Request) { log.Printf("%s.put", bucket) owner, err := s3intf.GetOwner(bucket.Service, r, bucket.Service.Host()) if err != nil { writeError(w, &HTTPError{Code: 16, HTTPCode: http.StatusBadRequest, Message: "error getting owner: " + err.Error(), Resource: "/" + bucket.Name}) return } log.Printf("creating bucket %s for %s", bucket.Name, owner.ID()) if err := bucket.Service.CreateBucket(owner, bucket.Name); err != nil { writeError(w, &HTTPError{Code: 17, Message: "error creating bucket: " + err.Error(), Resource: "/" + bucket.Name}) return } w.WriteHeader(http.StatusOK) return }
func (obj objectHandler) del(w http.ResponseWriter, r *http.Request) { owner, err := s3intf.GetOwner(obj.Bucket.Service, r, obj.Bucket.Service.Host()) if err != nil { writeError(w, &HTTPError{Code: 18, HTTPCode: http.StatusBadRequest, Message: "error getting owner: " + err.Error(), Resource: "/" + obj.Bucket.Name + "/" + obj.object}) return } if err := obj.Bucket.Service.Del(owner, obj.Bucket.Name, obj.object); err != nil { he := &HTTPError{Code: 19, Message: "error deleting " + obj.Bucket.Name + "/" + obj.object + ": " + err.Error(), Resource: "/" + obj.Bucket.Name + "/" + obj.object} if err == s3intf.NotFound { he.HTTPCode = http.StatusNotFound } writeError(w, he) return } w.WriteHeader(http.StatusNoContent) }
func (obj objectHandler) put(w http.ResponseWriter, r *http.Request) { if r.Body == nil { writeError(w, &HTTPError{Code: 23, HTTPCode: http.StatusBadRequest, Message: "nil body", Resource: "/" + obj.Bucket.Name + "/" + obj.object}) return } defer r.Body.Close() owner, err := s3intf.GetOwner(obj.Bucket.Service, r, obj.Bucket.Service.Host()) if err != nil { writeError(w, &HTTPError{Code: 24, Message: "error getting owner: " + err.Error(), Resource: "/" + obj.Bucket.Name + "/" + obj.object}) return } var ( fn, media string body io.Reader size int64 ) if r.Method == "POST" { mpf, mph, err := r.FormFile("file") if err != nil { return } fn = mph.Filename media = mph.Header.Get("Content-Type") size, _ = mpf.Seek(0, 2) mpf.Seek(0, 0) body = mpf } else { media = r.Header.Get("Content-Type") if disp := r.Header.Get("Content-Disposition"); disp != "" { if _, params, err := mime.ParseMediaType(disp); err == nil { fn = params["filename"] } else { writeError(w, &HTTPError{Code: 25, HTTPCode: http.StatusBadRequest, Message: "cannot parse Content-Disposition " + disp + ": " + err.Error(), Resource: "/" + obj.Bucket.Name + "/" + obj.object}) return } } if body, size, err = GetReaderSize(r.Body, 1<<20); err != nil { writeError(w, &HTTPError{Code: 28, Message: "error reading request body: " + err.Error(), Resource: "/" + obj.Bucket.Name + "/" + obj.object}) } } hsh := crypto.MD5.New() if body, err = TeeRead(hsh, body, 1<<20); err != nil { writeError(w, &HTTPError{Code: 29, Message: "error reading request body: " + err.Error(), Resource: "/" + obj.Bucket.Name + "/" + obj.object}) } md5Computed := hex.EncodeToString(hsh.Sum(nil)) md5Given := r.Header.Get("Content-MD5") if md5Given != "" && md5Computed != md5Given { writeError(w, &HTTPError{Code: 27, HTTPCode: http.StatusBadRequest, Message: fmt.Sprintf("got MD5=%q computed=%q", md5Given, md5Computed), Resource: "/" + obj.Bucket.Name + "/" + obj.object}) } if fn == "" { log.Printf("no filename in %s", r.Header) fn = md5Computed } if err := obj.Bucket.Service.Put(owner, obj.Bucket.Name, obj.object, fn, media, body, size, hsh.Sum(nil)); err != nil { if err == s3intf.NotFound { w.WriteHeader(http.StatusNotFound) return } writeError(w, &HTTPError{Code: 26, HTTPCode: http.StatusBadRequest, Message: "error while storing " + fn + " in " + obj.Bucket.Name + "/" + obj.object + ": " + err.Error(), Resource: "/" + obj.Bucket.Name + "/" + obj.object}) return } w.Header().Set("ETag", md5Computed) w.WriteHeader(http.StatusOK) }
//This implementation of the GET operation returns some or all (up to 1000) //of the objects in a bucket. //You can use the request parameters as selection criteria to return a subset //of the objects in a bucket. // //To use this implementation of the operation, you must have READ access to the bucket. func (bucket bucketHandler) list(w http.ResponseWriter, r *http.Request) { err := r.ParseForm() if err != nil { writeError(w, &HTTPError{Code: 10, HTTPCode: http.StatusBadRequest, Message: "cannot parse form values: " + err.Error(), Resource: "/" + bucket.Name}) return } delimiter := r.Form.Get("delimiter") marker := r.Form.Get("marker") limit := 1000 maxkeys := r.Form.Get("max-keys") if maxkeys != "" { if limit, err = strconv.Atoi(maxkeys); err != nil { writeError(w, &HTTPError{Code: 11, HTTPCode: http.StatusBadRequest, Message: "cannot parse max-keys value: " + err.Error(), Resource: "/" + bucket.Name}) return } } skip := 0 skipkeys := r.Form.Get("skip-keys") if skipkeys != "" { if skip, err = strconv.Atoi(skipkeys); err != nil { writeError(w, &HTTPError{Code: 12, HTTPCode: http.StatusBadRequest, Message: "cannot parse skip-keys value: " + err.Error(), Resource: "/" + bucket.Name}) return } } prefix := r.Form.Get("prefix") owner, err := s3intf.GetOwner(bucket.Service, r, bucket.Service.fqdn) if err != nil { writeError(w, &HTTPError{Code: 13, Message: "error getting owner: " + err.Error(), Resource: "/" + bucket.Name}) return } if Debug { log.Printf("listing bucket %s/%s", owner.ID(), bucket.Name) } objects, commonprefixes, truncated, err := bucket.Service.List(owner, bucket.Name, prefix, delimiter, marker, limit, skip) if err != nil { log.Printf("error with bucket.Service.List(%s, %s, %q, %q, %q, %d, %d): %s", owner.ID(), bucket.Name, prefix, delimiter, marker, limit, skip, err) if err == s3intf.NotFound { w.WriteHeader(http.StatusNotFound) return } writeError(w, &HTTPError{Code: 14, Resource: "/" + bucket.Name, Message: "error getting list: " + err.Error()}) return } isTruncated := "false" if truncated { isTruncated = "true" } w.Header().Set("Content-Type", "text/xml") var ( logw *bytes.Buffer bw *bufio.Writer etag string ) if Debug { logw = bytes.NewBuffer(nil) bw = bufio.NewWriter(io.MultiWriter(w, logw)) } else { bw = bufio.NewWriter(w) } bw.WriteString(`<?xml version="1.0" encoding="UTF-8"?> <ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Name>` + bucket.Name + "</Name><Prefix>" + prefix + "</Prefix><Marker>" + marker + "</Marker><MaxKeys>" + strconv.Itoa(limit) + "</MaxKeys><IsTruncated>" + isTruncated + "</IsTruncated>") for _, object := range objects { etag = object.ETag if etag != "" { etag = "quot;" + etag + "quot;" } bw.WriteString("<Contents><Key>" + object.Key + "</Key><Size>" + strconv.FormatInt(object.Size, 10) + "</Size><Owner><ID>" + object.Owner.ID() + "</ID><DisplayName>" + object.Owner.Name() + "</DisplayName></Owner>" + "<LastModified>" + object.LastModified.Format(S3Date) + "</LastModified>" + "<ETag>" + etag + "</ETag>" + "</Contents>") } for _, cp := range commonprefixes { bw.WriteString("<CommonPrefixes><Prefix>" + cp + "</Prefix></CommonPrefixes>") } bw.WriteString("</ListBucketResult>") bw.Flush() if logw != nil { log.Printf("sent %q", logw.Bytes()) } }