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 }