// Copyright 2011 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.package multipartimport ()// ErrMessageTooLarge is returned by ReadForm if the message form// data is too large to be processed.varErrMessageTooLarge = errors.New("multipart: message too large")// TODO(adg,bradfitz): find a way to unify the DoS-prevention strategy here// with that of the http package's ParseForm.// ReadForm parses an entire multipart message whose parts have// a Content-Disposition of "form-data".// It stores up to maxMemory bytes + 10MB (reserved for non-file parts)// in memory. File parts which can't be stored in memory will be stored on// disk in temporary files.// It returns [ErrMessageTooLarge] if all non-file parts can't be stored in// memory.func ( *Reader) ( int64) (*Form, error) {return .readForm()}var ( multipartfiles = godebug.New("#multipartfiles") // TODO: document and remove # multipartmaxparts = godebug.New("multipartmaxparts"))func ( *Reader) ( int64) ( *Form, error) { := &Form{make(map[string][]string), make(map[string][]*FileHeader)}var ( *os.Fileint64 ) := 0 := trueifmultipartfiles.Value() == "distinct" { = false// multipartfiles.IncNonDefault() // TODO: uncomment after documenting } := 1000if := multipartmaxparts.Value(); != "" {if , := strconv.Atoi(); == nil && >= 0 { = multipartmaxparts.IncNonDefault() } } := maxMIMEHeaders()deferfunc() {if != nil {if := .Close(); == nil { = } }if && > 1 {for , := range .File {for , := range { .tmpshared = true } } }if != nil { .RemoveAll()if != nil {os.Remove(.Name()) } } }()// maxFileMemoryBytes is the maximum bytes of file data we will store in memory. // Data past this limit is written to disk. // This limit strictly applies to content, not metadata (filenames, MIME headers, etc.), // since metadata is always stored in memory, not disk. // // maxMemoryBytes is the maximum bytes we will store in memory, including file content, // non-file part values, metadata, and map entry overhead. // // We reserve an additional 10 MB in maxMemoryBytes for non-file data. // // The relationship between these parameters, as well as the overly-large and // unconfigurable 10 MB added on to maxMemory, is unfortunate but difficult to change // within the constraints of the API as documented. := if == math.MaxInt64 { -- } := + int64(10<<20)if <= 0 {if < 0 { = 0 } else { = math.MaxInt64 } }var []bytefor { , := .nextPart(false, , )if == io.EOF {break }if != nil {returnnil, }if <= 0 {returnnil, ErrMessageTooLarge } -- := .FormName()if == "" {continue } := .FileName()// Multiple values for the same key (one map entry, longer slice) are cheaper // than the same number of values for different keys (many map entries), but // using a consistent per-value cost for overhead is simpler.const = 200 -= int64(len()) -= if < 0 {// We can't actually take this path, since nextPart would already have // rejected the MIME headers for being too large. Check anyway.returnnil, ErrMessageTooLarge }varbytes.Bufferif == "" {// value, store as string in memory , := io.CopyN(&, , +1)if != nil && != io.EOF {returnnil, } -= if < 0 {returnnil, ErrMessageTooLarge } .Value[] = append(.Value[], .String())continue }// file, store in memory or on diskconst = 100 -= mimeHeaderSize(.Header) -= -= if < 0 {returnnil, ErrMessageTooLarge }for , := range .Header { -= int64(len()) } := &FileHeader{Filename: ,Header: .Header, } , := io.CopyN(&, , +1)if != nil && != io.EOF {returnnil, }if > {if == nil { , = os.CreateTemp(.tempDir, "multipart-")if != nil {returnnil, } } ++if , := .Write(.Bytes()); != nil {returnnil, }if == nil { = make([]byte, 32*1024) // same buffer size as io.Copy uses }// os.File.ReadFrom will allocate its own copy buffer if we let io.Copy use it.typestruct{ io.Writer } , := io.CopyBuffer({}, , )if != nil {returnnil, } .tmpfile = .Name() .Size = int64(.Len()) + .tmpoff = += .Sizeif ! {if := .Close(); != nil {returnnil, } = nil } } else { .content = .Bytes() .Size = int64(len(.content)) -= -= } .File[] = append(.File[], ) }return , nil}func mimeHeaderSize( textproto.MIMEHeader) ( int64) { = 400for , := range { += int64(len()) += 200// map entry overheadfor , := range { += int64(len()) } }return}// Form is a parsed multipart form.// Its File parts are stored either in memory or on disk,// and are accessible via the [*FileHeader]'s Open method.// Its Value parts are stored as strings.// Both are keyed by field name.typeFormstruct { Value map[string][]string File map[string][]*FileHeader}// RemoveAll removes any temporary files associated with a [Form].func ( *Form) () error {varerrorfor , := range .File {for , := range {if .tmpfile != "" { := os.Remove(.tmpfile)if != nil && !errors.Is(, os.ErrNotExist) && == nil { = } } } }return}// A FileHeader describes a file part of a multipart request.typeFileHeaderstruct { Filename string Header textproto.MIMEHeader Size int64 content []byte tmpfile string tmpoff int64 tmpshared bool}// Open opens and returns the [FileHeader]'s associated File.func ( *FileHeader) () (File, error) {if := .content; != nil { := io.NewSectionReader(bytes.NewReader(), 0, int64(len()))returnsectionReadCloser{, nil}, nil }if .tmpshared { , := os.Open(.tmpfile)if != nil {returnnil, } := io.NewSectionReader(, .tmpoff, .Size)returnsectionReadCloser{, }, nil }returnos.Open(.tmpfile)}// File is an interface to access the file part of a multipart message.// Its contents may be either stored in memory or on disk.// If stored on disk, the File's underlying concrete type will be an *os.File.typeFileinterface {io.Readerio.ReaderAtio.Seekerio.Closer}// helper types to turn a []byte into a Filetype sectionReadCloser struct { *io.SectionReaderio.Closer}func ( sectionReadCloser) () error {if .Closer != nil {return .Closer.Close() }returnnil}
The pages are generated with Goldsv0.7.0-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.