github.com/saucelabs/saucectl@v0.175.1/internal/multipartext/multipart.go (about)

     1  package multipartext
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"mime/multipart"
     8  	"net/textproto"
     9  	"strings"
    10  )
    11  
    12  var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
    13  
    14  // NewMultipartReader creates a new io.Reader that serves multipart form-data from src.
    15  // Also returns the form data content type (see multipart.Writer#FormDataContentType).
    16  func NewMultipartReader(field, filename, description string, src io.Reader) (io.Reader, string, error) {
    17  	// Create the multipart header.
    18  	buffy := &bytes.Buffer{}
    19  	writer := multipart.NewWriter(buffy)
    20  	header := make(textproto.MIMEHeader)
    21  	header.Set("Content-Disposition",
    22  		fmt.Sprintf(`form-data; name="%s"; filename="%s"`, field, quoteEscaper.Replace(filename)))
    23  	header.Set("Content-Type", "application/octet-stream")
    24  
    25  	// Create the actual part that will hold the data. Though we won't actually write the data just yet, since we want
    26  	// to stream it later.
    27  	if _, err := writer.CreatePart(header); err != nil {
    28  		return nil, "", err
    29  	}
    30  	headerSize := buffy.Len()
    31  
    32  	if err := writer.WriteField("description", description); err != nil {
    33  		return nil, "", err
    34  	}
    35  
    36  	// Finish the multipart message.
    37  	if err := writer.Close(); err != nil {
    38  		return nil, "", err
    39  	}
    40  
    41  	if srcReadSeeker, ok := src.(io.ReadSeeker); ok {
    42  		mrs, err := MultiReadSeeker(
    43  			bytes.NewReader(buffy.Bytes()[:headerSize]),
    44  			srcReadSeeker,
    45  			bytes.NewReader(buffy.Bytes()[headerSize:]),
    46  		)
    47  
    48  		return mrs,
    49  			writer.FormDataContentType(),
    50  			err
    51  	}
    52  
    53  	return io.MultiReader(
    54  			bytes.NewReader(buffy.Bytes()[:headerSize]),
    55  			src,
    56  			bytes.NewReader(buffy.Bytes()[headerSize:]),
    57  		),
    58  		writer.FormDataContentType(),
    59  		nil
    60  }