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  }