package multipartdetection import ( "bufio" "bytes" "errors" ) const ( // MultipartBodySniffLength declares the amount of bytes to read from the multipart body in // order to detect the file length. MultipartBodySniffLength = 256 ) var ( // ErrBoundaryInvalid indicates that the boundary is invalid or not even set. It may also be // returned if the boundary is too long (more than the 70 allowed characters, see RFC 2046 // http://www.ietf.org/rfc/rfc2046.txt). ErrBoundaryInvalid = errors.New("boundary is too long") // ErrUnknownCharacter indicates that a different character was expected so therefore the // multipart body is invalid. ErrUnknownCharacter = errors.New("expected different character") // ErrSniffSizeExceeded indicates that the sniff (length: MultipartBodySniffLength) was not enough // to scan for the whole multipart boundary prefix. ErrSniffSizeExceeded = errors.New("the maximum size of the multipart body sniff was exceeded") // ErrInvalidContentLength indicates that the provided content length is wrong because of the // calculated file length (assuming the multipart body is correct). ErrInvalidContentLength = errors.New("invalid content length provided") ) // CalculateSingleMultipartFileLength calculates and in a successful case returns the real file // length. Please note that this method only works if the multipart request contains one file. func CalculateSingleMultipartFileLength(boundary string, bodyBuffer *bufio.Reader, contentLength int64) (fileLength int64, err error) { if len(boundary) > 70 { return 0, ErrBoundaryInvalid } sniff, _ := bodyBuffer.Peek(MultipartBodySniffLength) err = nil byteBoundary := []byte(boundary) var i int // iterate through the body sniff for i = 0; i < len(sniff); i++ { c := rune(sniff[i]) var ok bool /* check for dash prefix (--) */ if i <= 1 { if c != '-' { return 0, ErrUnknownCharacter } continue } else /* check boundary*/ if i >= 2 && i < 2+len(byteBoundary) { i, ok = validateBoundary(i, byteBoundary, sniff) if !ok { return 0, ErrBoundaryInvalid } continue } else /* check for two consecutive line separators indicating the beginning of the real body */ { if i, ok = validateLineSeparator(i, sniff); !ok { continue } // increment i to allow the next line separator check i++ if i, ok = validateLineSeparator(i, sniff); ok { goto lengthCalcuation } } } return 0, ErrSniffSizeExceeded lengthCalcuation: // subtract the calculated prefix length (added by one because array start at 0), two preceding // and ending line separators as well as the boundary length and the line separators from the // total content length fileLength = contentLength - (int64(i + 1 + 4 + len(byteBoundary) + 2 + 2)) if fileLength < 0 { return 0, ErrInvalidContentLength } return } func validateLineSeparator(i int, sniff []byte) (int, bool) { if len(sniff) < i+2 { return i, false } if sniff[i] != '\r' { return i, false } if sniff[i+1] != '\n' { return i, false } return i + 1, true } func validateBoundary(i int, byteBoundary []byte, sniff []byte) (int, bool) { if len(sniff) <= len(byteBoundary)+i { return 0, false } if bytes.Equal(byteBoundary, sniff[i:i+len(byteBoundary)]) { return i + len(byteBoundary) - 1, true } return 0, false }