github.com/Kolosok86/http@v0.1.2/internal/multipart/formdata.go (about)

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package multipart
     6  
     7  import (
     8  	"bytes"
     9  	"errors"
    10  	"io"
    11  	"math"
    12  	"os"
    13  	"strings"
    14  
    15  	"github.com/Kolosok86/http/textproto"
    16  )
    17  
    18  // ErrMessageTooLarge is returned by ReadForm if the message form
    19  // data is too large to be processed.
    20  var ErrMessageTooLarge = errors.New("multipart: message too large")
    21  
    22  // TODO(adg,bradfitz): find a way to unify the DoS-prevention strategy here
    23  // with that of the http package's ParseForm.
    24  
    25  // ReadForm parses an entire multipart message whose parts have
    26  // a Content-Disposition of "form-data".
    27  // It stores up to maxMemory bytes + 10MB (reserved for non-file parts)
    28  // in memory. File parts which can't be stored in memory will be stored on
    29  // disk in temporary files.
    30  // It returns ErrMessageTooLarge if all non-file parts can't be stored in
    31  // memory.
    32  func (r *Reader) ReadForm(maxMemory int64) (*Form, error) {
    33  	return r.readForm(maxMemory)
    34  }
    35  
    36  func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) {
    37  	form := &Form{make(map[string][]string), make(map[string][]*FileHeader)}
    38  	var (
    39  		file    *os.File
    40  		fileOff int64
    41  	)
    42  	numDiskFiles := 0
    43  	combineFiles := strings.Contains(os.Getenv("MULTIPARTFILES"), "1")
    44  
    45  	defer func() {
    46  		if file != nil {
    47  			if cerr := file.Close(); err == nil {
    48  				err = cerr
    49  			}
    50  		}
    51  		if combineFiles && numDiskFiles > 1 {
    52  			for _, fhs := range form.File {
    53  				for _, fh := range fhs {
    54  					fh.tmpshared = true
    55  				}
    56  			}
    57  		}
    58  		if err != nil {
    59  			form.RemoveAll()
    60  			if file != nil {
    61  				os.Remove(file.Name())
    62  			}
    63  		}
    64  	}()
    65  
    66  	// maxFileMemoryBytes is the maximum bytes of file data we will store in memory.
    67  	// Data past this limit is written to disk.
    68  	// This limit strictly applies to content, not metadata (filenames, MIME headers, etc.),
    69  	// since metadata is always stored in memory, not disk.
    70  	//
    71  	// maxMemoryBytes is the maximum bytes we will store in memory, including file content,
    72  	// non-file part values, metdata, and map entry overhead.
    73  	//
    74  	// We reserve an additional 10 MB in maxMemoryBytes for non-file data.
    75  	//
    76  	// The relationship between these parameters, as well as the overly-large and
    77  	// unconfigurable 10 MB added on to maxMemory, is unfortunate but difficult to change
    78  	// within the constraints of the API as documented.
    79  	maxFileMemoryBytes := maxMemory
    80  	if maxFileMemoryBytes == math.MaxInt64 {
    81  		maxFileMemoryBytes--
    82  	}
    83  	maxMemoryBytes := maxMemory + int64(10<<20)
    84  	if maxMemoryBytes <= 0 {
    85  		if maxMemory < 0 {
    86  			maxMemoryBytes = 0
    87  		} else {
    88  			maxMemoryBytes = math.MaxInt64
    89  		}
    90  	}
    91  	for {
    92  		p, err := r.nextPart(false, maxMemoryBytes)
    93  		if err == io.EOF {
    94  			break
    95  		}
    96  		if err != nil {
    97  			return nil, err
    98  		}
    99  
   100  		name := p.FormName()
   101  		if name == "" {
   102  			continue
   103  		}
   104  		filename := p.FileName()
   105  
   106  		// Multiple values for the same key (one map entry, longer slice) are cheaper
   107  		// than the same number of values for different keys (many map entries), but
   108  		// using a consistent per-value cost for overhead is simpler.
   109  		maxMemoryBytes -= int64(len(name))
   110  		maxMemoryBytes -= 100 // map overhead
   111  		if maxMemoryBytes < 0 {
   112  			// We can't actually take this path, since nextPart would already have
   113  			// rejected the MIME headers for being too large. Check anyway.
   114  			return nil, ErrMessageTooLarge
   115  		}
   116  
   117  		var b bytes.Buffer
   118  
   119  		if filename == "" {
   120  			// value, store as string in memory
   121  			n, err := io.CopyN(&b, p, maxMemoryBytes+1)
   122  			if err != nil && err != io.EOF {
   123  				return nil, err
   124  			}
   125  			maxMemoryBytes -= n
   126  			if maxMemoryBytes < 0 {
   127  				return nil, ErrMessageTooLarge
   128  			}
   129  			form.Value[name] = append(form.Value[name], b.String())
   130  			continue
   131  		}
   132  
   133  		// file, store in memory or on disk
   134  		maxMemoryBytes -= mimeHeaderSize(p.Header)
   135  		if maxMemoryBytes < 0 {
   136  			return nil, ErrMessageTooLarge
   137  		}
   138  		fh := &FileHeader{
   139  			Filename: filename,
   140  			Header:   p.Header,
   141  		}
   142  		n, err := io.CopyN(&b, p, maxFileMemoryBytes+1)
   143  		if err != nil && err != io.EOF {
   144  			return nil, err
   145  		}
   146  		if n > maxFileMemoryBytes {
   147  			if file == nil {
   148  				file, err = os.CreateTemp(r.tempDir, "multipart-")
   149  				if err != nil {
   150  					return nil, err
   151  				}
   152  			}
   153  			numDiskFiles++
   154  			size, err := io.Copy(file, io.MultiReader(&b, p))
   155  			if err != nil {
   156  				return nil, err
   157  			}
   158  			fh.tmpfile = file.Name()
   159  			fh.Size = size
   160  			fh.tmpoff = fileOff
   161  			fileOff += size
   162  			if !combineFiles {
   163  				if err := file.Close(); err != nil {
   164  					return nil, err
   165  				}
   166  				file = nil
   167  			}
   168  		} else {
   169  			fh.content = b.Bytes()
   170  			fh.Size = int64(len(fh.content))
   171  			maxFileMemoryBytes -= n
   172  			maxMemoryBytes -= n
   173  		}
   174  		form.File[name] = append(form.File[name], fh)
   175  	}
   176  
   177  	return form, nil
   178  }
   179  
   180  func mimeHeaderSize(h textproto.MIMEHeader) (size int64) {
   181  	for k, vs := range h {
   182  		size += int64(len(k))
   183  		size += 100 // map entry overhead
   184  		for _, v := range vs {
   185  			size += int64(len(v))
   186  		}
   187  	}
   188  	return size
   189  }
   190  
   191  // Form is a parsed multipart form.
   192  // Its File parts are stored either in memory or on disk,
   193  // and are accessible via the *FileHeader's Open method.
   194  // Its Value parts are stored as strings.
   195  // Both are keyed by field name.
   196  type Form struct {
   197  	Value map[string][]string
   198  	File  map[string][]*FileHeader
   199  }
   200  
   201  // RemoveAll removes any temporary files associated with a Form.
   202  func (f *Form) RemoveAll() error {
   203  	var err error
   204  	for _, fhs := range f.File {
   205  		for _, fh := range fhs {
   206  			if fh.tmpfile != "" {
   207  				e := os.Remove(fh.tmpfile)
   208  				if e != nil && !errors.Is(e, os.ErrNotExist) && err == nil {
   209  					err = e
   210  				}
   211  			}
   212  		}
   213  	}
   214  	return err
   215  }
   216  
   217  // A FileHeader describes a file part of a multipart request.
   218  type FileHeader struct {
   219  	Filename string
   220  	Header   textproto.MIMEHeader
   221  	Size     int64
   222  
   223  	content   []byte
   224  	tmpfile   string
   225  	tmpoff    int64
   226  	tmpshared bool
   227  }
   228  
   229  // Open opens and returns the FileHeader's associated File.
   230  func (fh *FileHeader) Open() (File, error) {
   231  	if b := fh.content; b != nil {
   232  		r := io.NewSectionReader(bytes.NewReader(b), 0, int64(len(b)))
   233  		return sectionReadCloser{r, nil}, nil
   234  	}
   235  	if fh.tmpshared {
   236  		f, err := os.Open(fh.tmpfile)
   237  		if err != nil {
   238  			return nil, err
   239  		}
   240  		r := io.NewSectionReader(f, fh.tmpoff, fh.Size)
   241  		return sectionReadCloser{r, f}, nil
   242  	}
   243  	return os.Open(fh.tmpfile)
   244  }
   245  
   246  // File is an interface to access the file part of a multipart message.
   247  // Its contents may be either stored in memory or on disk.
   248  // If stored on disk, the File's underlying concrete type will be an *os.File.
   249  type File interface {
   250  	io.Reader
   251  	io.ReaderAt
   252  	io.Seeker
   253  	io.Closer
   254  }
   255  
   256  // helper types to turn a []byte into a File
   257  
   258  type sectionReadCloser struct {
   259  	*io.SectionReader
   260  	io.Closer
   261  }
   262  
   263  func (rc sectionReadCloser) Close() error {
   264  	if rc.Closer != nil {
   265  		return rc.Closer.Close()
   266  	}
   267  	return nil
   268  }