// parse http multipart form data body // calls a callback for each part // returns a count of parts // multipart_fn // // Example: // ------geckoformboundary8a20292b9973036de6f4d2c70f63e1d8 // Content-Disposition: form-data; name="file"; filename="genulid.c" // Content-Type: text/x-csrc // // The final marker has -- at the end. // // ------geckoformboundary8a20292b9973036de6f4d2c70f63e1d8-- typedef void (*multipart_fn)(void *opaque, int index, str name, str mime_type, str filename, str databytes); static str http_header_get_attr(str header, str attr_prefix){ str_pair x = str_split_at_str(header, attr_prefix); if(str_eq(header, x.a)) return (str){0}; str attr = x.b; x = str_split_at(attr, '"'); if(str_eq(attr, x.a)) return (str){0}; return x.a; } static int http_parse_multipart_formdata(str body, multipart_fn cb, void *cbdata){ str const CR = kstr("\r"); str const LF = kstr("\n"); str const o = body; str_pair x = str_split_at(body, '\n'); if(str_eq(body, x.a)){ debug("boundary not found"); return 0; } str boundary = x.a; bool has_crlf = (boundary.len && str_ends_with(boundary, kstr("\r"))); if(has_crlf) boundary = str_drop_last(boundary, 1); debug("boundary [%.*s]\n", pstr(boundary)); int nparts=0; body = x.b; str const content_disposition_prefix = kstr("content-disposition: form-data;"); str const content_type_prefix = kstr("content-type: "); while(body.len){ x = str_split_at_str(body, boundary); if(str_eq(body, x.a)){ warn("multipart missing end boundary at offset:%ld\n", (body.data-o.data)); return 0; } str part = x.a; unless(str_ends_with(part, LF)){ warn("multipart part missing LF before boundary at offset:%ld\n", (body.data-o.data)); return 0; } part = str_drop_last(part, 1); if(str_ends_with(part, CR)) part = str_drop_last(part, 1); body = x.b; str name = {0}; str mime_type = {0}; str filename = {0}; str header = part; bool header_complete = false; while(header.len){ x = str_split_at(header, '\n'); if(str_eq(header, x.a)) return 0; str line = x.a; header = x.b; if(line.len && '\r'==line.data[line.len-1]) line=str_drop_last(line, 1); if(0==line.len){ header_complete=true; break; } if(str_starts_with_ci(line, content_disposition_prefix)){ str rest = str_drop(line, content_disposition_prefix.len); name = http_header_get_attr(rest, kstr(" name=\"")); filename = http_header_get_attr(rest, kstr(" filename=\"")); }else if(str_starts_with_ci(line, content_type_prefix)){ mime_type = str_drop(line, content_type_prefix.len); } } unless(header_complete){ warn("multipart header not terminated\n"); return 0; } part = header; cb(cbdata, nparts, name, mime_type, filename, part); ++nparts; bool const final_boundary = str_starts_with(body, kstr("--")); if(final_boundary) return nparts; if(body.len && '\r'==body.data[0]) body=str_drop(body, 1); unless(body.len && '\n'==body.data[0]){ warn("multipart boundary missing newline\n"); return 0; } body = str_drop(body, 1); } warn("http_parse_multipart_formdata failed to find final boundary\n"); return 0; }