github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/resource/api/upload.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package api
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"mime"
    10  	"net/http"
    11  
    12  	"github.com/juju/errors"
    13  	charmresource "gopkg.in/juju/charm.v6/resource"
    14  	"gopkg.in/juju/names.v2"
    15  
    16  	"github.com/juju/juju/resource"
    17  )
    18  
    19  // UploadRequest defines a single upload request.
    20  type UploadRequest struct {
    21  	// Application is the application ID.
    22  	Application string
    23  
    24  	// Name is the resource name.
    25  	Name string
    26  
    27  	// Filename is the name of the file as it exists on disk.
    28  	Filename string
    29  
    30  	// Size is the size of the uploaded data, in bytes.
    31  	Size int64
    32  
    33  	// Fingerprint is the fingerprint of the uploaded data.
    34  	Fingerprint charmresource.Fingerprint
    35  
    36  	// PendingID is the pending ID to associate with this upload, if any.
    37  	PendingID string
    38  }
    39  
    40  // NewUploadRequest generates a new upload request for the given resource.
    41  func NewUploadRequest(application, name, filename string, r io.ReadSeeker) (UploadRequest, error) {
    42  	if !names.IsValidApplication(application) {
    43  		return UploadRequest{}, errors.Errorf("invalid application %q", application)
    44  	}
    45  
    46  	content, err := resource.GenerateContent(r)
    47  	if err != nil {
    48  		return UploadRequest{}, errors.Trace(err)
    49  	}
    50  
    51  	ur := UploadRequest{
    52  		Application: application,
    53  		Name:        name,
    54  		Filename:    filename,
    55  		Size:        content.Size,
    56  		Fingerprint: content.Fingerprint,
    57  	}
    58  	return ur, nil
    59  }
    60  
    61  func setFilename(filename string, req *http.Request) {
    62  	filename = mime.BEncoding.Encode("utf-8", filename)
    63  
    64  	disp := mime.FormatMediaType(
    65  		MediaTypeFormData,
    66  		map[string]string{FilenameParamForContentDispositionHeader: filename},
    67  	)
    68  
    69  	req.Header.Set(HeaderContentDisposition, disp)
    70  }
    71  
    72  // FilenameParamForContentDispositionHeader is the name of the parameter that
    73  // contains the name of the file being uploaded, see mime.FormatMediaType and
    74  // RFC 1867 (http://tools.ietf.org/html/rfc1867):
    75  //
    76  //   The original local file name may be supplied as well, either as a
    77  //  'filename' parameter either of the 'content-disposition: form-data'
    78  //   header or in the case of multiple files in a 'content-disposition:
    79  //   file' header of the subpart.
    80  const FilenameParamForContentDispositionHeader = "filename"
    81  
    82  // HTTPRequest generates a new HTTP request.
    83  func (ur UploadRequest) HTTPRequest() (*http.Request, error) {
    84  	// TODO(ericsnow) What about the rest of the URL?
    85  	urlStr := NewEndpointPath(ur.Application, ur.Name)
    86  
    87  	req, err := http.NewRequest(http.MethodPut, urlStr, nil)
    88  	if err != nil {
    89  		return nil, errors.Trace(err)
    90  	}
    91  
    92  	req.Header.Set(HeaderContentType, ContentTypeRaw)
    93  	req.Header.Set(HeaderContentSha384, ur.Fingerprint.String())
    94  	req.Header.Set(HeaderContentLength, fmt.Sprint(ur.Size))
    95  	setFilename(ur.Filename, req)
    96  
    97  	req.ContentLength = ur.Size
    98  
    99  	if ur.PendingID != "" {
   100  		query := req.URL.Query()
   101  		query.Set(QueryParamPendingID, ur.PendingID)
   102  		req.URL.RawQuery = query.Encode()
   103  	}
   104  
   105  	return req, nil
   106  }