github.com/aarzilli/tools@v0.0.0-20151123112009-0d27094f75e0/appengine/blobstore_mgt/official-multipart-parse.go (about) 1 package blobstore_mgt 2 3 import ( 4 "bufio" 5 "encoding/base64" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "mime" 10 "mime/multipart" 11 "net/http" 12 "net/textproto" 13 14 "net/url" 15 "strconv" 16 "strings" 17 "time" 18 19 "appengine" 20 ) 21 22 func errorf(format string, args ...interface{}) error { 23 return fmt.Errorf("blobstore: "+format, args...) 24 } 25 26 27 28 // ParseUpload parses the synthetic POST request that your app gets from 29 // App Engine after a user's successful upload of blobs. Given the request, 30 // ParseUpload returns a map of the blobs received (keyed by HTML form 31 // element name) and other non-blob POST parameters. 32 func OfficialParseUpload(req *http.Request) (blobs map[string][]*BlobInfo, other url.Values, err error) { 33 _, params, err := mime.ParseMediaType(req.Header.Get("Content-Type")) 34 if err != nil { 35 return nil, nil, err 36 } 37 boundary := params["boundary"] 38 if boundary == "" { 39 return nil, nil, errorf("did not find MIME multipart boundary") 40 } 41 42 blobs = make(map[string][]*BlobInfo) 43 other = make(url.Values) 44 45 mreader := multipart.NewReader(io.MultiReader(req.Body, strings.NewReader("\r\n\r\n")), boundary) 46 for { 47 part, perr := mreader.NextPart() 48 if perr == io.EOF { 49 break 50 } 51 if perr != nil { 52 return nil, nil, errorf("error reading next mime part with boundary %q (len=%d): %v", 53 boundary, len(boundary), perr) 54 } 55 56 bi := &BlobInfo{} 57 ctype, params, err := mime.ParseMediaType(part.Header.Get("Content-Disposition")) 58 if err != nil { 59 return nil, nil, err 60 } 61 bi.Filename = params["filename"] 62 formKey := params["name"] 63 64 ctype, params, err = mime.ParseMediaType(part.Header.Get("Content-Type")) 65 if err != nil { 66 return nil, nil, err 67 } 68 bi.BlobKey = appengine.BlobKey(params["blob-key"]) 69 if ctype != "message/external-body" || bi.BlobKey == "" { 70 if formKey != "" { 71 slurp, serr := ioutil.ReadAll(part) 72 if serr != nil { 73 return nil, nil, errorf("error reading %q MIME part", formKey) 74 } 75 other[formKey] = append(other[formKey], string(slurp)) 76 } 77 continue 78 } 79 80 // App Engine sends a MIME header as the body of each MIME part. 81 tp := textproto.NewReader(bufio.NewReader(part)) 82 header, mimeerr := tp.ReadMIMEHeader() 83 if mimeerr != nil { 84 return nil, nil, mimeerr 85 } 86 bi.Size, err = strconv.ParseInt(header.Get("Content-Length"), 10, 64) 87 if err != nil { 88 return nil, nil, err 89 } 90 bi.ContentType = header.Get("Content-Type") 91 92 // Parse the time from the MIME header like: 93 // X-AppEngine-Upload-Creation: 2011-03-15 21:38:34.712136 94 createDate := header.Get("X-AppEngine-Upload-Creation") 95 if createDate == "" { 96 return nil, nil, errorf("expected to find an X-AppEngine-Upload-Creation header") 97 } 98 bi.CreationTime, err = time.Parse("2006-01-02 15:04:05.000000", createDate) 99 if err != nil { 100 return nil, nil, errorf("error parsing X-AppEngine-Upload-Creation: %s", err) 101 } 102 103 if hdr := header.Get("Content-MD5"); hdr != "" { 104 md5, err := base64.URLEncoding.DecodeString(hdr) 105 if err != nil { 106 return nil, nil, errorf("bad Content-MD5 %q: %v", hdr, err) 107 } 108 bi.MD5 = string(md5) 109 } 110 111 // If the GCS object name was provided, record it. 112 bi.ObjectName = header.Get("X-AppEngine-Cloud-Storage-Object") 113 114 blobs[formKey] = append(blobs[formKey], bi) 115 } 116 return 117 } 118 119 120 121