github.com/blend/go-sdk@v1.20220411.3/webutil/posted_files.go (about)

     1  /*
     2  
     3  Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package webutil
     9  
    10  import (
    11  	"io"
    12  	"net/http"
    13  
    14  	"github.com/blend/go-sdk/ex"
    15  )
    16  
    17  const (
    18  	// DefaultPostedFilesMaxMemory is the maximum post body size we will typically consume.
    19  	DefaultPostedFilesMaxMemory = 67_108_864 //64mb
    20  )
    21  
    22  // PostedFile is a file that has been posted to an hc endpoint.
    23  type PostedFile struct {
    24  	Key         string
    25  	FileName    string
    26  	Contents    []byte
    27  	ContentType string
    28  }
    29  
    30  // PostedFilesOptions are options for the PostedFiles function.
    31  type PostedFilesOptions struct {
    32  	MaxMemory          int64
    33  	ParseMultipartForm bool
    34  	ParseForm          bool
    35  }
    36  
    37  // PostedFileOption mutates posted file options.
    38  type PostedFileOption func(*PostedFilesOptions)
    39  
    40  // OptPostedFilesMaxMemory sets the max memory for the posted files options (defaults to 64mb).
    41  func OptPostedFilesMaxMemory(maxMemory int64) PostedFileOption {
    42  	return func(pfo *PostedFilesOptions) { pfo.MaxMemory = maxMemory }
    43  }
    44  
    45  // OptPostedFilesParseMultipartForm sets if we should parse the multipart form for files (defaults to true).
    46  func OptPostedFilesParseMultipartForm(parseMultipartForm bool) PostedFileOption {
    47  	return func(pfo *PostedFilesOptions) { pfo.ParseMultipartForm = parseMultipartForm }
    48  }
    49  
    50  // OptPostedFilesParseForm sets if we should parse the post form for files (defaults to false).
    51  func OptPostedFilesParseForm(parseForm bool) PostedFileOption {
    52  	return func(pfo *PostedFilesOptions) { pfo.ParseForm = parseForm }
    53  }
    54  
    55  // PostedFiles returns any files posted to the request.
    56  //
    57  // The files are held in memory, if you need to stream the files out because they may be large,
    58  // you should use the `*net/http.Request.FormFile(...)` function directly instead of this method.
    59  func PostedFiles(r *http.Request, opts ...PostedFileOption) ([]PostedFile, error) {
    60  	var files []PostedFile
    61  
    62  	options := PostedFilesOptions{
    63  		MaxMemory:          DefaultPostedFilesMaxMemory,
    64  		ParseMultipartForm: true,
    65  		ParseForm:          false,
    66  	}
    67  	for _, opt := range opts {
    68  		opt(&options)
    69  	}
    70  
    71  	if options.ParseMultipartForm {
    72  		if err := r.ParseMultipartForm(options.MaxMemory); err != nil {
    73  			return nil, ex.New(err)
    74  		}
    75  		for key := range r.MultipartForm.File {
    76  			file, err := readPostedFile(r, key)
    77  			if err != nil {
    78  				return nil, err
    79  			}
    80  			files = append(files, *file)
    81  		}
    82  	}
    83  	if options.ParseForm {
    84  		if err := r.ParseForm(); err != nil {
    85  			return nil, ex.New(err)
    86  		}
    87  		for key := range r.PostForm {
    88  			file, err := readPostedFile(r, key)
    89  			if err != nil {
    90  				return nil, err
    91  			}
    92  			files = append(files, *file)
    93  		}
    94  	}
    95  	return files, nil
    96  }
    97  
    98  func readPostedFile(r *http.Request, key string) (*PostedFile, error) {
    99  	fileReader, fileHeader, err := r.FormFile(key)
   100  	if err != nil {
   101  		return nil, ex.New(err)
   102  	}
   103  	defer fileReader.Close()
   104  	fileContents, err := io.ReadAll(fileReader)
   105  	if err != nil {
   106  		return nil, ex.New(err)
   107  	}
   108  	return &PostedFile{Key: key, FileName: fileHeader.Filename, Contents: fileContents}, nil
   109  }