gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/skyfilereader.go (about)

     1  package skymodules
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"mime"
    10  	"mime/multipart"
    11  	"net/http"
    12  	"os"
    13  	"sort"
    14  
    15  	"gitlab.com/NebulousLabs/errors"
    16  	"gitlab.com/SkynetLabs/skyd/build"
    17  )
    18  
    19  var (
    20  	// ErrIllegalFormName is returned when the multipart form contains a part
    21  	// under an illegal form name, only 'files[]' or 'file' are allowed.
    22  	ErrIllegalFormName = errors.New("multipart file submitted under an illegal form name, allowed values are 'files[]' and 'file'")
    23  
    24  	// ErrEmptyFilename is returned when the multipart form contains a part
    25  	// with an empty filename
    26  	ErrEmptyFilename = errors.New("no filename provided")
    27  
    28  	// ErrSkyfileMetadataUnavailable is returned when the context passed to
    29  	// SkyfileMetadata is cancelled before the metadata became available
    30  	ErrSkyfileMetadataUnavailable = errors.New("metadata unavailable")
    31  )
    32  
    33  type (
    34  	// SkyfileUploadReader is an interface that wraps a reader, containing the
    35  	// Skyfile data, and adds a method to fetch the SkyfileMetadata.
    36  	SkyfileUploadReader interface {
    37  		SetReadBuffer(data []byte)
    38  		SkyfileMetadata(ctx context.Context) (SkyfileMetadata, error)
    39  		io.Reader
    40  	}
    41  
    42  	// skyfileMultipartReader is a helper struct that implements the
    43  	// SkyfileUploadReader interface for a multipart upload.
    44  	//
    45  	// NOTE: reading from this object is not threadsafe and thus should not be
    46  	// done from more than one thread if you want the reads to be deterministic.
    47  	skyfileMultipartReader struct {
    48  		reader  *multipart.Reader
    49  		readBuf []byte
    50  
    51  		currLen  uint64
    52  		currOff  uint64
    53  		currPart *multipart.Part
    54  
    55  		metadata      SkyfileMetadata
    56  		metadataAvail chan struct{}
    57  	}
    58  
    59  	// skyfileReader is a helper struct that implements the SkyfileUploadReader
    60  	// interface for a regular upload
    61  	//
    62  	// NOTE: reading from this object is not threadsafe and thus should not be
    63  	// done from more than one thread if you want the reads to be deterministic.
    64  	skyfileReader struct {
    65  		reader  io.Reader
    66  		readBuf []byte
    67  
    68  		currLen uint64
    69  
    70  		metadata      SkyfileMetadata
    71  		metadataAvail chan struct{}
    72  	}
    73  )
    74  
    75  // NewSkyfileReader wraps the given reader and metadata and returns a
    76  // SkyfileUploadReader
    77  func NewSkyfileReader(reader io.Reader, sup SkyfileUploadParameters) SkyfileUploadReader {
    78  	// Define the skyfileReader
    79  	return &skyfileReader{
    80  		reader: reader,
    81  		metadata: SkyfileMetadata{
    82  			Filename: sup.Filename,
    83  			Mode:     sup.Mode,
    84  		},
    85  		metadataAvail: make(chan struct{}),
    86  	}
    87  }
    88  
    89  // SetReadBuffer sets the given bytes as the read buffer. The next reads will
    90  // read from this buffer until it is entirely consumed, after which we continue
    91  // reading from the underlying reader.
    92  func (sr *skyfileReader) SetReadBuffer(b []byte) {
    93  	sr.readBuf = b
    94  }
    95  
    96  // SkyfileMetadata returns the SkyfileMetadata associated with this reader.
    97  //
    98  // NOTE: this method will block until the metadata becomes available
    99  func (sr *skyfileReader) SkyfileMetadata(ctx context.Context) (SkyfileMetadata, error) {
   100  	// Wait for the metadata to become available, that will be the case when
   101  	// the reader returned an EOF, or until the context is cancelled.
   102  	select {
   103  	case <-ctx.Done():
   104  		return SkyfileMetadata{}, errors.AddContext(ErrSkyfileMetadataUnavailable, "context cancelled")
   105  	case <-sr.metadataAvail:
   106  	}
   107  
   108  	return sr.metadata, nil
   109  }
   110  
   111  // Read implements the io.Reader part of the interface and reads data from the
   112  // underlying reader.
   113  func (sr *skyfileReader) Read(p []byte) (n int, err error) {
   114  	if len(sr.readBuf) > 0 {
   115  		n = copy(p, sr.readBuf)
   116  		sr.readBuf = sr.readBuf[n:]
   117  		if len(sr.readBuf) == 0 {
   118  			sr.readBuf = nil // reset for GC
   119  		}
   120  	}
   121  
   122  	// check if we've already read until EOF, that will be the case if
   123  	// `metadataAvail` is closed.
   124  	select {
   125  	case <-sr.metadataAvail:
   126  		return n, io.EOF
   127  	default:
   128  	}
   129  
   130  	// return early if possible
   131  	if n == len(p) {
   132  		return
   133  	}
   134  
   135  	var nn int
   136  	nn, err = sr.reader.Read(p[n:])
   137  	n += nn
   138  	sr.currLen += uint64(nn)
   139  
   140  	if errors.Contains(err, io.EOF) {
   141  		close(sr.metadataAvail)
   142  		sr.metadata.Length = sr.currLen
   143  	}
   144  	return
   145  }
   146  
   147  // NewMultipartReader creates a multipart.Reader from an io.Reader and the
   148  // provided subfiles. This reader can then be used to create
   149  // a NewSkyfileMultipartReader.
   150  func NewMultipartReader(reader io.Reader, subFiles SkyfileSubfiles) (*multipart.Reader, error) {
   151  	// Read data from reader
   152  	data, err := ioutil.ReadAll(reader)
   153  	if err != nil {
   154  		return nil, errors.AddContext(err, "unable to read data from reader")
   155  	}
   156  
   157  	// Sort the subFiles by offset
   158  	subFilesArray := make([]SkyfileSubfileMetadata, 0, len(subFiles))
   159  	for _, sfm := range subFiles {
   160  		subFilesArray = append(subFilesArray, sfm)
   161  	}
   162  	sort.Slice(subFilesArray, func(i, j int) bool {
   163  		return subFilesArray[i].Offset < subFilesArray[j].Offset
   164  	})
   165  
   166  	// Build multipart reader from subFiles
   167  	body := new(bytes.Buffer)
   168  	writer := multipart.NewWriter(body)
   169  	var offset uint64
   170  	for _, sfm := range subFilesArray {
   171  		_, err = AddMultipartFile(writer, data[sfm.Offset:sfm.Offset+sfm.Len], "files[]", sfm.Filename, DefaultFilePerm, &offset)
   172  		if err != nil {
   173  			return nil, errors.AddContext(err, "unable to add multipart file")
   174  		}
   175  	}
   176  	multiReader := multipart.NewReader(body, writer.Boundary())
   177  	if err = writer.Close(); err != nil {
   178  		return nil, errors.AddContext(err, "unable to close writer")
   179  	}
   180  	return multiReader, nil
   181  }
   182  
   183  // NewSkyfileMultipartReader wraps the given reader and returns a
   184  // SkyfileUploadReader. By reading from this reader until an EOF is reached, the
   185  // SkyfileMetadata will be constructed incrementally every time a new Part is
   186  // read.
   187  func NewSkyfileMultipartReader(reader *multipart.Reader, sup SkyfileUploadParameters) SkyfileUploadReader {
   188  	return newSkyfileMultipartReader(reader, sup)
   189  }
   190  
   191  // NewSkyfileMultipartReaderFromRequest generates a multipart reader from the
   192  // http request and returns a SkyfileUploadReader.
   193  func NewSkyfileMultipartReaderFromRequest(req *http.Request, sup SkyfileUploadParameters) (SkyfileUploadReader, error) {
   194  	// Error checks on request pulled from http package MultipartReader()
   195  	// http.Request method.
   196  	//
   197  	// https://golang.org/src/net/http/request.go?s=16785:16847#L453
   198  	if req.MultipartForm != nil {
   199  		return nil, errors.New("http: multipart previously handled")
   200  	}
   201  	req.MultipartForm = &multipart.Form{
   202  		Value: make(map[string][]string),
   203  		File:  make(map[string][]*multipart.FileHeader),
   204  	}
   205  	v := req.Header.Get("Content-Type")
   206  	if v == "" {
   207  		return nil, http.ErrNotMultipart
   208  	}
   209  	d, params, err := mime.ParseMediaType(v)
   210  	allowMixed := true // pulled from http package
   211  	if err != nil || !(d == "multipart/form-data" || allowMixed && d == "multipart/mixed") {
   212  		return nil, http.ErrNotMultipart
   213  	}
   214  	boundary, ok := params["boundary"]
   215  	if !ok {
   216  		return nil, http.ErrMissingBoundary
   217  	}
   218  
   219  	// Create the multipart reader.
   220  	mpr := multipart.NewReader(req.Body, boundary)
   221  	return NewSkyfileMultipartReader(mpr, sup), nil
   222  }
   223  
   224  // newSkyfileMultipartReader wraps the given reader and returns a
   225  // SkyfileUploadReader. By reading from this reader until an EOF is reached, the
   226  // SkyfileMetadata will be constructed incrementally every time a new Part is
   227  // read.
   228  func newSkyfileMultipartReader(reader *multipart.Reader, sup SkyfileUploadParameters) *skyfileMultipartReader {
   229  	return &skyfileMultipartReader{
   230  		reader: reader,
   231  		metadata: SkyfileMetadata{
   232  			Filename:           sup.Filename,
   233  			Mode:               sup.Mode,
   234  			DefaultPath:        sup.DefaultPath,
   235  			DisableDefaultPath: sup.DisableDefaultPath,
   236  			TryFiles:           sup.TryFiles,
   237  			ErrorPages:         sup.ErrorPages,
   238  			Subfiles:           make(SkyfileSubfiles),
   239  		},
   240  		metadataAvail: make(chan struct{}),
   241  	}
   242  }
   243  
   244  // SetReadBuffer sets the given bytes as the read buffer. The next reads will
   245  // read from this buffer until it is entirely consumed, after which we continue
   246  // reading from the underlying reader.
   247  func (sr *skyfileMultipartReader) SetReadBuffer(b []byte) {
   248  	sr.readBuf = b
   249  }
   250  
   251  // SkyfileMetadata returns the SkyfileMetadata associated with this reader.
   252  func (sr *skyfileMultipartReader) SkyfileMetadata(ctx context.Context) (SkyfileMetadata, error) {
   253  	// Wait for the metadata to become available, that will be the case when
   254  	// the reader returned an EOF, or until the context is cancelled.
   255  	select {
   256  	case <-ctx.Done():
   257  		return SkyfileMetadata{}, errors.AddContext(ErrSkyfileMetadataUnavailable, "context cancelled")
   258  	case <-sr.metadataAvail:
   259  	}
   260  
   261  	// Check whether we found multipart files
   262  	if len(sr.metadata.Subfiles) == 0 {
   263  		return SkyfileMetadata{}, errors.New("could not find multipart file")
   264  	}
   265  
   266  	// Use the filename of the first subfile if it's not passed as query
   267  	// string parameter and there's only one subfile.
   268  	if sr.metadata.Filename == "" && len(sr.metadata.Subfiles) == 1 {
   269  		for _, sf := range sr.metadata.Subfiles {
   270  			sr.metadata.Filename = sf.Filename
   271  			break
   272  		}
   273  	}
   274  
   275  	// Set the total length as the sum of the lengths of every subfile
   276  	if sr.metadata.Length == 0 {
   277  		for _, sf := range sr.metadata.Subfiles {
   278  			sr.metadata.Length += sf.Len
   279  		}
   280  	}
   281  
   282  	return sr.metadata, nil
   283  }
   284  
   285  // Read implements the io.Reader part of the interface and reads data from the
   286  // underlying multipart reader. While the data is being read, the metadata is
   287  // being constructed.
   288  func (sr *skyfileMultipartReader) Read(p []byte) (n int, err error) {
   289  	if len(sr.readBuf) > 0 {
   290  		n = copy(p, sr.readBuf)
   291  		sr.readBuf = sr.readBuf[n:]
   292  		if len(sr.readBuf) == 0 {
   293  			sr.readBuf = nil // reset for GC
   294  		}
   295  	}
   296  
   297  	// check if we've already read until EOF, that will be the case if
   298  	// `metadataAvail` is closed.
   299  	select {
   300  	case <-sr.metadataAvail:
   301  		return n, io.EOF
   302  	default:
   303  	}
   304  
   305  	for n < len(p) && err == nil {
   306  		// only read the next part if the current part is not set
   307  		if sr.currPart == nil {
   308  			sr.currPart, err = sr.reader.NextPart()
   309  			if err != nil {
   310  				// only when `NextPart` errors out we want to signal the
   311  				// metadata is ready, on any error not only EOF
   312  				close(sr.metadataAvail)
   313  				break
   314  			}
   315  			sr.currOff += sr.currLen
   316  			sr.currLen = 0
   317  
   318  			// verify the multipart file is submitted under the expected name
   319  			if !isLegalFormName(sr.currPart.FormName()) {
   320  				err = ErrIllegalFormName
   321  				break
   322  			}
   323  		}
   324  
   325  		// read data from the part
   326  		var nn int
   327  		nn, err = sr.currPart.Read(p[n:])
   328  		n += nn
   329  
   330  		// update the length
   331  		sr.currLen += uint64(nn)
   332  
   333  		// ignore the EOF to continue reading from the next part if necessary,
   334  		if err == io.EOF {
   335  			err = nil
   336  
   337  			// create the metadata for the current subfile before resetting the
   338  			// current part
   339  			err = sr.createSubfileFromCurrPart()
   340  			if err != nil {
   341  				break
   342  			}
   343  			sr.currPart = nil
   344  		}
   345  	}
   346  
   347  	return
   348  }
   349  
   350  // createSubfileFromCurrPart adds a subfile for the current part.
   351  func (sr *skyfileMultipartReader) createSubfileFromCurrPart() error {
   352  	// sanity check the reader has a current part set
   353  	if sr.currPart == nil {
   354  		build.Critical("createSubfileFromCurrPart called when currPart is nil")
   355  		return errors.New("could not create metadata for subfile")
   356  	}
   357  
   358  	// parse the mode from the part header
   359  	mode, err := parseMode(sr.currPart.Header.Get("Mode"))
   360  	if err != nil {
   361  		return errors.AddContext(err, "failed to parse file mode")
   362  	}
   363  
   364  	// parse the filename
   365  	// WARNING: don't use currPart.Filename() here since it caused
   366  	// unexpected behaviour on some deploys. Paths were trimmed from the
   367  	// filename, flattening the directory structure of the upload.
   368  	values := sr.currPart.Header.Get("Content-Disposition")
   369  	_, m, err := mime.ParseMediaType(values)
   370  	if err != nil {
   371  		return errors.AddContext(err, "failed to parse media type for subfile")
   372  	}
   373  	filename, exists := m["filename"]
   374  	if !exists || filename == "" {
   375  		return ErrEmptyFilename
   376  	}
   377  
   378  	sr.metadata.Subfiles[filename] = SkyfileSubfileMetadata{
   379  		FileMode:    mode,
   380  		Filename:    filename,
   381  		ContentType: sr.currPart.Header.Get("Content-Type"),
   382  		Offset:      sr.currOff,
   383  		Len:         sr.currLen,
   384  	}
   385  	return nil
   386  }
   387  
   388  // isLegalFormName is a helper function that returns true if the given form name
   389  // is allowed to submit a Skyfile subfile.
   390  func isLegalFormName(formName string) bool {
   391  	return formName == "file" || formName == "files[]"
   392  }
   393  
   394  // parseMode is a helper function that parses an os.FileMode from the given
   395  // string.
   396  func parseMode(modeStr string) (os.FileMode, error) {
   397  	var mode os.FileMode
   398  	if modeStr != "" {
   399  		_, err := fmt.Sscanf(modeStr, "%o", &mode)
   400  		if err != nil {
   401  			return mode, err
   402  		}
   403  	}
   404  	return mode, nil
   405  }