github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/pkg/blobserver/handlers/upload.go (about) 1 /* 2 Copyright 2011 Google Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package handlers 18 19 import ( 20 "bytes" 21 "crypto/sha1" 22 "errors" 23 "fmt" 24 "io" 25 "log" 26 "mime" 27 "net/http" 28 "path" 29 "strings" 30 "time" 31 32 "camlistore.org/pkg/blob" 33 "camlistore.org/pkg/blobserver" 34 "camlistore.org/pkg/blobserver/protocol" 35 "camlistore.org/pkg/httputil" 36 "camlistore.org/pkg/jsonsign/signhandler" 37 "camlistore.org/pkg/readerutil" 38 "camlistore.org/pkg/schema" 39 ) 40 41 // CreateBatchUploadHandler returns the handler that receives multi-part form uploads 42 // to upload many blobs at once. See doc/protocol/blob-upload-protocol.txt. 43 func CreateBatchUploadHandler(storage blobserver.BlobReceiveConfiger) http.Handler { 44 return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 45 handleMultiPartUpload(rw, req, storage) 46 }) 47 } 48 49 // CreatePutUploadHandler returns the handler that receives a single 50 // blob at the blob's final URL, via the PUT method. See 51 // doc/protocol/blob-upload-protocol.txt. 52 func CreatePutUploadHandler(storage blobserver.BlobReceiver) http.Handler { 53 return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 54 if req.Method != "PUT" { 55 log.Printf("Inconfigured upload handler.") 56 httputil.BadRequestError(rw, "Inconfigured handler.") 57 return 58 } 59 // For non-chunked uploads, we catch it here. For chunked uploads, it's caught 60 // by blobserver.Receive's LimitReader. 61 if req.ContentLength > blobserver.MaxBlobSize { 62 httputil.BadRequestError(rw, "blob too big") 63 return 64 } 65 blobrefStr := path.Base(req.URL.Path) 66 br, ok := blob.Parse(blobrefStr) 67 if !ok { 68 log.Printf("Invalid PUT request to %q", req.URL.Path) 69 httputil.BadRequestError(rw, "Bad path") 70 return 71 } 72 if !br.IsSupported() { 73 httputil.BadRequestError(rw, "unsupported object hash function") 74 return 75 } 76 _, err := blobserver.Receive(storage, br, req.Body) 77 if err == blobserver.ErrCorruptBlob { 78 httputil.BadRequestError(rw, "data doesn't match declared digest") 79 return 80 } 81 if err != nil { 82 httputil.ServeError(rw, req, err) 83 return 84 } 85 rw.WriteHeader(http.StatusNoContent) 86 }) 87 } 88 89 // vivify verifies that all the chunks for the file described by fileblob are on the blobserver. 90 // It makes a planned permanode, signs it, and uploads it. It finally makes a camliContent claim 91 // on that permanode for fileblob, signs it, and uploads it to the blobserver. 92 func vivify(blobReceiver blobserver.BlobReceiveConfiger, fileblob blob.SizedRef) error { 93 sf, ok := blobReceiver.(blob.StreamingFetcher) 94 if !ok { 95 return fmt.Errorf("BlobReceiver is not a StreamingFetcher") 96 } 97 fetcher := blob.SeekerFromStreamingFetcher(sf) 98 fr, err := schema.NewFileReader(fetcher, fileblob.Ref) 99 if err != nil { 100 return fmt.Errorf("Filereader error for blobref %v: %v", fileblob.Ref.String(), err) 101 } 102 defer fr.Close() 103 104 h := sha1.New() 105 n, err := io.Copy(h, fr) 106 if err != nil { 107 return fmt.Errorf("Could not read all file of blobref %v: %v", fileblob.Ref.String(), err) 108 } 109 if n != fr.Size() { 110 return fmt.Errorf("Could not read all file of blobref %v. Wanted %v, got %v", fileblob.Ref.String(), fr.Size(), n) 111 } 112 113 config := blobReceiver.Config() 114 if config == nil { 115 return errors.New("blobReceiver has no config") 116 } 117 hf := config.HandlerFinder 118 if hf == nil { 119 return errors.New("blobReceiver config has no HandlerFinder") 120 } 121 JSONSignRoot, sh, err := hf.FindHandlerByType("jsonsign") 122 if err != nil || sh == nil { 123 return errors.New("jsonsign handler not found") 124 } 125 sigHelper, ok := sh.(*signhandler.Handler) 126 if !ok { 127 return errors.New("handler is not a JSON signhandler") 128 } 129 discoMap := sigHelper.DiscoveryMap(JSONSignRoot) 130 publicKeyBlobRef, ok := discoMap["publicKeyBlobRef"].(string) 131 if !ok { 132 return fmt.Errorf("Discovery: json decoding error: %v", err) 133 } 134 135 // The file schema must have a modtime to vivify, as the modtime is used for all three of: 136 // 1) the permanode's signature 137 // 2) the camliContent attribute claim's "claimDate" 138 // 3) the signature time of 2) 139 claimDate, err := time.Parse(time.RFC3339, fr.FileSchema().UnixMtime) 140 if err != nil { 141 return fmt.Errorf("While parsing modtime for file %v: %v", fr.FileSchema().FileName, err) 142 } 143 144 permanodeBB := schema.NewHashPlannedPermanode(h) 145 permanodeBB.SetSigner(blob.MustParse(publicKeyBlobRef)) 146 permanodeBB.SetClaimDate(claimDate) 147 permanodeSigned, err := sigHelper.Sign(permanodeBB) 148 if err != nil { 149 return fmt.Errorf("Signing permanode %v: %v", permanodeSigned, err) 150 } 151 permanodeRef := blob.SHA1FromString(permanodeSigned) 152 _, err = blobserver.ReceiveNoHash(blobReceiver, permanodeRef, strings.NewReader(permanodeSigned)) 153 if err != nil { 154 return fmt.Errorf("While uploading signed permanode %v, %v: %v", permanodeRef, permanodeSigned, err) 155 } 156 157 contentClaimBB := schema.NewSetAttributeClaim(permanodeRef, "camliContent", fileblob.Ref.String()) 158 contentClaimBB.SetSigner(blob.MustParse(publicKeyBlobRef)) 159 contentClaimBB.SetClaimDate(claimDate) 160 contentClaimSigned, err := sigHelper.Sign(contentClaimBB) 161 if err != nil { 162 return fmt.Errorf("Signing camliContent claim: %v", err) 163 } 164 contentClaimRef := blob.SHA1FromString(contentClaimSigned) 165 _, err = blobserver.ReceiveNoHash(blobReceiver, contentClaimRef, strings.NewReader(contentClaimSigned)) 166 if err != nil { 167 return fmt.Errorf("While uploading signed camliContent claim %v, %v: %v", contentClaimRef, contentClaimSigned, err) 168 } 169 return nil 170 } 171 172 func handleMultiPartUpload(conn http.ResponseWriter, req *http.Request, blobReceiver blobserver.BlobReceiveConfiger) { 173 res := new(protocol.UploadResponse) 174 175 if !(req.Method == "POST" && strings.Contains(req.URL.Path, "/camli/upload")) { 176 log.Printf("Inconfigured handler upload handler") 177 httputil.BadRequestError(conn, "Inconfigured handler.") 178 return 179 } 180 181 receivedBlobs := make([]blob.SizedRef, 0, 10) 182 183 multipart, err := req.MultipartReader() 184 if multipart == nil { 185 httputil.BadRequestError(conn, fmt.Sprintf( 186 "Expected multipart/form-data POST request; %v", err)) 187 return 188 } 189 190 var errBuf bytes.Buffer 191 addError := func(s string) { 192 log.Printf("Client error: %s", s) 193 if errBuf.Len() > 0 { 194 errBuf.WriteByte('\n') 195 } 196 errBuf.WriteString(s) 197 } 198 199 for { 200 mimePart, err := multipart.NextPart() 201 if err == io.EOF { 202 break 203 } 204 if err != nil { 205 addError(fmt.Sprintf("Error reading multipart section: %v", err)) 206 break 207 } 208 209 contentDisposition, params, err := mime.ParseMediaType(mimePart.Header.Get("Content-Disposition")) 210 if err != nil { 211 addError("invalid Content-Disposition") 212 break 213 } 214 215 if contentDisposition != "form-data" { 216 addError(fmt.Sprintf("Expected Content-Disposition of \"form-data\"; got %q", contentDisposition)) 217 break 218 } 219 220 formName := params["name"] 221 ref, ok := blob.Parse(formName) 222 if !ok { 223 addError(fmt.Sprintf("Ignoring form key %q", formName)) 224 continue 225 } 226 227 var tooBig int64 = blobserver.MaxBlobSize + 1 228 var readBytes int64 229 blobGot, err := blobserver.Receive(blobReceiver, ref, &readerutil.CountingReader{ 230 io.LimitReader(mimePart, tooBig), 231 &readBytes, 232 }) 233 if readBytes == tooBig { 234 err = fmt.Errorf("blob over the limit of %d bytes", blobserver.MaxBlobSize) 235 } 236 if err != nil { 237 addError(fmt.Sprintf("Error receiving blob %v: %v\n", ref, err)) 238 break 239 } 240 log.Printf("Received blob %v\n", blobGot) 241 receivedBlobs = append(receivedBlobs, blobGot) 242 } 243 244 for _, got := range receivedBlobs { 245 res.Received = append(res.Received, &protocol.RefAndSize{ 246 Ref: got.Ref, 247 Size: uint32(got.Size), 248 }) 249 } 250 251 if req.Header.Get("X-Camlistore-Vivify") == "1" { 252 for _, got := range receivedBlobs { 253 err := vivify(blobReceiver, got) 254 if err != nil { 255 addError(fmt.Sprintf("Error vivifying blob %v: %v\n", got.Ref.String(), err)) 256 } else { 257 conn.Header().Add("X-Camlistore-Vivified", got.Ref.String()) 258 } 259 } 260 } 261 262 res.ErrorText = errBuf.String() 263 264 httputil.ReturnJSON(conn, res) 265 }