|
1 | 1 | package org.zaproxy.zap.extension.scriptgen;
|
2 | 2 |
|
3 | 3 | import com.h3xstream.scriptgen.model.HttpRequestInfo;
|
| 4 | +import com.h3xstream.scriptgen.model.MultiPartParameter; |
| 5 | +import org.apache.commons.fileupload.MultipartStream; |
4 | 6 | import org.apache.commons.httpclient.URI;
|
5 | 7 | import org.parosproxy.paros.network.HtmlParameter;
|
6 | 8 | import org.parosproxy.paros.network.HttpMessage;
|
7 | 9 |
|
8 | 10 | import java.io.BufferedReader;
|
9 | 11 | import java.io.ByteArrayInputStream;
|
| 12 | +import java.io.ByteArrayOutputStream; |
10 | 13 | import java.io.IOException;
|
11 | 14 | import java.io.InputStreamReader;
|
| 15 | +import java.io.OutputStream; |
12 | 16 | import java.net.URLDecoder;
|
| 17 | +import java.util.ArrayList; |
13 | 18 | import java.util.HashMap;
|
| 19 | +import java.util.List; |
14 | 20 | import java.util.Map;
|
| 21 | +import java.util.regex.Matcher; |
| 22 | +import java.util.regex.Pattern; |
15 | 23 |
|
16 | 24 | public class ZapHttpRequestMapper {
|
17 | 25 |
|
| 26 | + |
| 27 | + //Sample : multipart/form-data; boundary=---------------------------297592997116592 |
| 28 | + private static final Pattern PATTERN_MULTIPART_FORM_BOUNDARY = Pattern.compile("multipart/form-data; boundary=(.*)"); |
| 29 | + //Content-Disposition: form-data; name="numitems" |
| 30 | + private static final Pattern PATTERN_MULTIPART_PARAM_NAME = Pattern.compile("[^e]name=\"([^\"]+)\""); |
| 31 | + //Content-Disposition: form-data; name="uploadname2"; filename="" |
| 32 | + private static final Pattern PATTERN_MULTIPART_PARAM_FILENAME = Pattern.compile("filename=\"([^\"]+)\""); |
| 33 | + |
| 34 | + private static final Pattern PATTERN_MULTIPART_PARAM_CONTENT_TYPE = Pattern.compile("[Cc]ontent-[tT]ype: ([^\n^\r]+)"); |
| 35 | + |
| 36 | + |
| 37 | + |
18 | 38 | public static HttpRequestInfo buildRequestInfo(HttpMessage httpMessage) throws IOException {
|
19 | 39 | String method = httpMessage.getRequestHeader().getMethod();
|
20 | 40 | URI url = httpMessage.getRequestHeader().getURI();
|
@@ -44,17 +64,77 @@ public static HttpRequestInfo buildRequestInfo(HttpMessage httpMessage) throws I
|
44 | 64 | }
|
45 | 65 | }
|
46 | 66 |
|
| 67 | + String multiPartBoundary = null; |
| 68 | + |
47 | 69 | Map<String,String> headers = new HashMap<String, String>();
|
48 | 70 | String headerString = httpMessage.getRequestHeader().getHeadersAsString();
|
49 | 71 | BufferedReader br = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(headerString.getBytes())));
|
50 | 72 | String headerLine = null;
|
51 | 73 | while((headerLine = br.readLine()) != null) {
|
52 | 74 | String[] headerParts = headerLine.split(":", 2);
|
53 | 75 | if (headerParts.length == 2) {
|
54 |
| - headers.put(headerParts[0].trim(), URLDecoder.decode(headerParts[1].trim(),"UTF-8")); |
| 76 | + |
| 77 | + String name = headerParts[0].trim(); |
| 78 | + String value = URLDecoder.decode(headerParts[1].trim(),"UTF-8"); //Decode for cookies |
| 79 | + headers.put(name, value); |
| 80 | + |
| 81 | + if (name.toLowerCase().equals("content-type") && value.startsWith("multipart/form-data")) { |
| 82 | + Matcher m = PATTERN_MULTIPART_FORM_BOUNDARY.matcher(value); |
| 83 | + if(m.find()) { |
| 84 | + multiPartBoundary = m.group(1); |
| 85 | + } |
| 86 | + } |
55 | 87 | }
|
56 | 88 | }
|
57 | 89 |
|
58 |
| - return new HttpRequestInfo(method,urlWithoutQuery,paramsGet,paramsPost,postData,headers,null); |
| 90 | + List<MultiPartParameter> multiPartParameters = new ArrayList<MultiPartParameter>(); |
| 91 | + if(multiPartBoundary != null) { //Boundary was intercept during the previous step (header parsing) |
| 92 | + parseMultiPartParameter(httpMessage.getRequestBody().getBytes(),multiPartBoundary.getBytes(), multiPartParameters, paramsPost); |
| 93 | + } |
| 94 | + |
| 95 | + return new HttpRequestInfo(method,urlWithoutQuery,paramsGet,paramsPost,postData,headers,multiPartParameters); |
| 96 | + } |
| 97 | + |
| 98 | + /** |
| 99 | + * |
| 100 | + * Use the low-level api of Commons-FileUpload to parse the different parameters. |
| 101 | + * |
| 102 | + * @param input |
| 103 | + * @param boundary |
| 104 | + * @param multiPartParameters |
| 105 | + * @param paramsPost |
| 106 | + */ |
| 107 | + private static void parseMultiPartParameter(byte[] input,byte[] boundary,List<MultiPartParameter> multiPartParameters,Map<String,String> paramsPost) { |
| 108 | + |
| 109 | + try { |
| 110 | + MultipartStream multipartStream = new MultipartStream(new ByteArrayInputStream(input), boundary); |
| 111 | + boolean nextPart = multipartStream.skipPreamble(); |
| 112 | + |
| 113 | + while(nextPart) { |
| 114 | + String header = multipartStream.readHeaders(); |
| 115 | + Matcher mN = PATTERN_MULTIPART_PARAM_NAME.matcher(header); |
| 116 | + Matcher mF = PATTERN_MULTIPART_PARAM_FILENAME.matcher(header); |
| 117 | + Matcher mCT = PATTERN_MULTIPART_PARAM_CONTENT_TYPE.matcher(header); |
| 118 | + |
| 119 | + String name = mN.find() ? mN.group(1) : null; |
| 120 | + String filename = mF.find() ? mF.group(1) : null; |
| 121 | + String contentType = mCT.find() ? mCT.group(1) : "application/octet-stream"; |
| 122 | + |
| 123 | + ByteArrayOutputStream output = new ByteArrayOutputStream(); |
| 124 | + multipartStream.readBodyData(output); |
| 125 | + |
| 126 | + if(filename != null) { //Multi-part file |
| 127 | + multiPartParameters.add(new MultiPartParameter(name, output.toString(), contentType, filename)); |
| 128 | + } |
| 129 | + else { //Multi-part regular parameter |
| 130 | + paramsPost.put(name,output.toString()); |
| 131 | + } |
| 132 | + nextPart = multipartStream.readBoundary(); |
| 133 | + } |
| 134 | + } catch(MultipartStream.MalformedStreamException e) { |
| 135 | + //Oops |
| 136 | + } catch(IOException e) { |
| 137 | + //Oops |
| 138 | + } |
59 | 139 | }
|
60 | 140 | }
|
0 commit comments