1 /*
2  * Created on Jun 14, 2004
3  *
4  * Paros and its related class files.
5  *
6  * Paros is an HTTP/HTTPS proxy for assessing web application security.
7  * Copyright (C) 2003-2004 Chinotec Technologies Company
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the Clarified Artistic License
11  * as published by the Free Software Foundation.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * Clarified Artistic License for more details.
17  *
18  * You should have received a copy of the Clarified Artistic License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
21  */
22 // ZAP: 2012:02/01 Changed getHostPort() to return proper port number even if it
23 // is not explicitly specified in URI
24 // ZAP: 2011/08/04 Changed to support Logging
25 // ZAP: 2011/10/29 Log errors
26 // ZAP: 2011/11/03 Changed isImage() to prevent a NullPointerException when the path doesn't exist
27 // ZAP: 2011/12/09 Changed HttpRequestHeader(String method, URI uri, String version) to add
28 //      the Cache-Control header field when the HTTP version is 1.1 and changed a if condition to
29 //      validate the variable version instead of the variable method.
30 // ZAP: 2012/03/15 Changed to use the class StringBuilder instead of StringBuffer. Reworked some
31 // methods.
32 // ZAP: 2012/04/23 Added @Override annotation to all appropriate methods.
33 // ZAP: 2012/06/24 Added method to add Cookies of type java.net.HttpCookie to request header
34 // ZAP: 2012/06/24 Added new method of getting cookies from the request header.
35 // ZAP: 2012/10/08 Issue 361: getHostPort on HttpRequestHeader for HTTPS CONNECT
36 // requests returns the wrong port
37 // ZAP: 2013/01/23 Clean up of exception handling/logging.
38 // ZAP: 2013/03/08 Improved parse error reporting
39 // ZAP: 2013/04/14 Issue 596: Rename the method HttpRequestHeader.getSecure to isSecure
40 // ZAP: 2013/05/02 Re-arranged all modifiers into Java coding standard order
41 // ZAP: 2013/12/09 Set Content-type only in case of POST or PUT HTTP methods
42 // ZAP: 2015/08/07 Issue 1768: Update to use a more recent default user agent
43 // ZAP: 2016/06/17 Remove redundant initialisations of instance variables
44 // ZAP: 2016/09/26 JavaDoc tweaks
45 // ZAP: 2017/02/23 Issue 3227: Limit API access to permitted IP addresses
46 // ZAP: 2017/04/24 Added more HTTP methods
47 // ZAP: 2017/10/19 Skip parsing of empty Cookie headers.
48 // ZAP: 2017/11/22 Address a NPE in isImage().
49 // ZAP: 2018/01/10 Tweak how cookie header is reconstructed from HtmlParameter(s).
50 // ZAP: 2018/02/06 Make the upper case changes locale independent (Issue 4327).
51 // ZAP: 2018/08/10 Allow to set the user agent used by default request headers (Issue 4846).
52 // ZAP: 2018/11/16 Add Accept header.
53 // ZAP: 2019/01/25 Add Origin header.
54 // ZAP: 2019/03/06 Log or include the malformed data in the exception message.
55 // ZAP: 2019/03/19 Changed the parse method to only parse the authority on CONNECT requests
56 // ZAP: 2019/06/01 Normalise line endings.
57 // ZAP: 2019/06/05 Normalise format/style.
58 // ZAP: 2019/12/09 Address deprecation of getHeaders(String) Vector method.
59 // ZAP: 2020/11/10 Add convenience method isCss(), refactor isImage() to use new private method
60 // isSpecificType(Pattern).
61 // ZAP: 2020/11/26 Use Log4j 2 classes for logging.
62 // ZAP: 2021/05/10 Use authority for CONNECT requests.
63 // ZAP: 2021/07/16 Issue 6691: Do not add zero Content-Length by default in GET requests
64 // ZAP: 2021/07/19 Include SVG in isImage().
65 package org.parosproxy.paros.network;
66 
67 import java.io.UnsupportedEncodingException;
68 import java.net.HttpCookie;
69 import java.net.InetAddress;
70 import java.net.URLEncoder;
71 import java.util.Iterator;
72 import java.util.LinkedList;
73 import java.util.List;
74 import java.util.Locale;
75 import java.util.TreeSet;
76 import java.util.regex.Matcher;
77 import java.util.regex.Pattern;
78 import org.apache.commons.httpclient.URI;
79 import org.apache.commons.httpclient.URIException;
80 import org.apache.logging.log4j.LogManager;
81 import org.apache.logging.log4j.Logger;
82 
83 public class HttpRequestHeader extends HttpHeader {
84 
85     /**
86      * The {@code Accept} request header.
87      *
88      * @since 2.8.0
89      */
90     public static final String ACCEPT = "Accept";
91 
92     /**
93      * The {@code Origin} request header.
94      *
95      * @since 2.8.0
96      */
97     public static final String ORIGIN = "Origin";
98 
99     private static final long serialVersionUID = 4156598327921777493L;
100     private static final Logger log = LogManager.getLogger(HttpRequestHeader.class);
101 
102     // method list
103     public static final String CONNECT = "CONNECT";
104     public static final String DELETE = "DELETE";
105     public static final String GET = "GET";
106     public static final String HEAD = "HEAD";
107     public static final String OPTIONS = "OPTIONS";
108     public static final String PATCH = "PATCH";
109     public static final String POST = "POST";
110     public static final String PUT = "PUT";
111     public static final String TRACE = "TRACE";
112     public static final String TRACK = "TRACK";
113 
114     // ZAP: Added method array
115     public static final String[] METHODS = {
116         CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE, TRACK
117     };
118     public static final String HOST = "Host";
119     private static final Pattern patternRequestLine =
120             Pattern.compile(p_METHOD + p_SP + p_URI + p_SP + p_VERSION, Pattern.CASE_INSENSITIVE);
121     // private static final Pattern patternHostHeader
122     //	= Pattern.compile("([^:]+)\\s*?:?\\s*?(\\d*?)");
123     private static final Pattern patternImage =
124             Pattern.compile(
125                     "\\.(bmp|ico|jpg|jpeg|gif|tiff|tif|png|svg)\\z", Pattern.CASE_INSENSITIVE);
126     private static final Pattern patternPartialRequestLine =
127             Pattern.compile(
128                     "\\A *(OPTIONS|GET|HEAD|POST|PUT|DELETE|TRACE|CONNECT)\\b",
129                     Pattern.CASE_INSENSITIVE);
130     private static final Pattern PATTERN_CSS =
131             Pattern.compile("\\.css\\z", Pattern.CASE_INSENSITIVE);
132 
133     /**
134      * The user agent used by {@link #HttpRequestHeader(String, URI, String) default request
135      * header}.
136      */
137     private static String defaultUserAgent;
138 
139     private String mMethod;
140     private URI mUri;
141     private String mHostName;
142     private InetAddress senderAddress;
143 
144     /**
145      * The host port number of this request message, a non-negative integer.
146      *
147      * <p>Default is {@code 80}.
148      *
149      * <p><strong>Note:</strong> All the modifications to the instance variable {@code mHostPort}
150      * must be done through the method {@code setHostPort(int)}, so a valid and correct value is set
151      * when no port number is defined (which is represented with the negative integer -1).
152      *
153      * @see #getHostPort()
154      * @see #setHostPort(int)
155      * @see URI#getPort()
156      */
157     private int mHostPort;
158 
159     private boolean mIsSecure;
160 
161     /** Constructor for an empty header. */
HttpRequestHeader()162     public HttpRequestHeader() {
163         super();
164         mMethod = "";
165         mHostName = "";
166         mHostPort = 80;
167     }
168 
169     /**
170      * Constructor of a request header with the string.
171      *
172      * @param data the request header
173      * @param isSecure {@code true} if the request should be secure, {@code false} otherwise
174      * @throws HttpMalformedHeaderException if the request being set is malformed
175      * @see #setSecure(boolean)
176      */
HttpRequestHeader(String data, boolean isSecure)177     public HttpRequestHeader(String data, boolean isSecure) throws HttpMalformedHeaderException {
178         setMessage(data, isSecure);
179     }
180 
181     /**
182      * Constructor of a request header with the string. Whether this is a secure header depends on
183      * the URL given.
184      *
185      * @param data the request header
186      * @throws HttpMalformedHeaderException if the request being set is malformed
187      */
HttpRequestHeader(String data)188     public HttpRequestHeader(String data) throws HttpMalformedHeaderException {
189         setMessage(data);
190     }
191 
192     @Override
clear()193     public void clear() {
194         super.clear();
195 
196         mMethod = "";
197         mUri = null;
198         mHostName = "";
199         setHostPort(-1);
200     }
201 
202     /**
203      * Constructs a {@code HttpRequestHeader} with the given method, URI, and version.
204      *
205      * <p>The following headers are automatically added:
206      *
207      * <ul>
208      *   <li>{@code Host}, with the domain and port from the given URI.
209      *   <li>{@code User-Agent}, using the {@link #getDefaultUserAgent()}.
210      *   <li>{@code Pragma: no-cache}
211      *   <li>{@code Cache-Control: no-cache}, if version is HTTP/1.1
212      *   <li>{@code Content-Type: application/x-www-form-urlencoded}, if the method is POST or PUT.
213      * </ul>
214      *
215      * @param method the request method.
216      * @param uri the request target.
217      * @param version the version, for example, {@code HTTP/1.1}.
218      * @throws HttpMalformedHeaderException if the resulting HTTP header is malformed.
219      */
HttpRequestHeader(String method, URI uri, String version)220     public HttpRequestHeader(String method, URI uri, String version)
221             throws HttpMalformedHeaderException {
222         this(method + " " + uri.toString() + " " + version.toUpperCase(Locale.ROOT) + CRLF + CRLF);
223 
224         try {
225             setHeader(
226                     HOST,
227                     uri.getHost()
228                             + (uri.getPort() > 0 ? ":" + Integer.toString(uri.getPort()) : ""));
229 
230         } catch (URIException e) {
231             log.error(e.getMessage(), e);
232         }
233 
234         setHeader(USER_AGENT, defaultUserAgent);
235         setHeader(PRAGMA, "no-cache");
236 
237         // ZAP: added the Cache-Control header field to comply with HTTP/1.1
238         if (version.equalsIgnoreCase(HTTP11)) {
239             setHeader(CACHE_CONTROL, "no-cache");
240         }
241 
242         // ZAP: set content type x-www-urlencoded only if it's a POST or a PUT
243         if (method.equalsIgnoreCase(POST) || method.equalsIgnoreCase(PUT)) {
244             setHeader(CONTENT_TYPE, "application/x-www-form-urlencoded");
245         }
246 
247         setHeader(ACCEPT_ENCODING, null);
248     }
249 
250     /**
251      * Constructs a {@code HttpRequestHeader} with the given method, URI, and version.
252      *
253      * <p>The following headers are automatically added:
254      *
255      * <ul>
256      *   <li>{@code Host}, with the domain and port from the given URI.
257      *   <li>{@code User-Agent}, using the {@link #getDefaultUserAgent()}.
258      *   <li>{@code Pragma: no-cache}
259      *   <li>{@code Cache-Control: no-cache}, if version is HTTP/1.1
260      *   <li>{@code Content-Type: application/x-www-form-urlencoded}, if the method is POST or PUT.
261      * </ul>
262      *
263      * @param method the request method.
264      * @param uri the request target.
265      * @param version the version, for example, {@code HTTP/1.1}.
266      * @param params unused.
267      * @throws HttpMalformedHeaderException if the resulting HTTP header is malformed.
268      * @deprecated (2.8.0) Use {@link #HttpRequestHeader(String, URI, String)} instead.
269      * @since 2.4.2
270      */
271     @Deprecated
HttpRequestHeader(String method, URI uri, String version, ConnectionParam params)272     public HttpRequestHeader(String method, URI uri, String version, ConnectionParam params)
273             throws HttpMalformedHeaderException {
274         this(method, uri, version);
275     }
276 
277     /**
278      * Set this request header with the given message.
279      *
280      * @param data the request header
281      * @param isSecure {@code true} if the request should be secure, {@code false} otherwise
282      * @throws HttpMalformedHeaderException if the request being set is malformed
283      * @see #setSecure(boolean)
284      */
setMessage(String data, boolean isSecure)285     public void setMessage(String data, boolean isSecure) throws HttpMalformedHeaderException {
286         super.setMessage(data);
287 
288         try {
289             parse(isSecure);
290 
291         } catch (HttpMalformedHeaderException e) {
292             mMalformedHeader = true;
293             if (log.isDebugEnabled()) {
294                 log.debug("Malformed header: " + data, e);
295             }
296 
297             throw e;
298 
299         } catch (Exception e) {
300             log.error("Failed to parse:\n" + data, e);
301             mMalformedHeader = true;
302             throw new HttpMalformedHeaderException(e.getMessage());
303         }
304     }
305 
306     /**
307      * Set this request header with the given message. Whether this is a secure header depends on
308      * the URL given.
309      */
310     @Override
setMessage(String data)311     public void setMessage(String data) throws HttpMalformedHeaderException {
312         this.setMessage(data, false);
313     }
314 
315     /**
316      * Get the HTTP method (GET, POST, ..., etc.).
317      *
318      * @return the request method
319      */
getMethod()320     public String getMethod() {
321         return mMethod;
322     }
323 
324     /**
325      * Set the HTTP method of this request header.
326      *
327      * @param method the new method, must not be {@code null}.
328      */
setMethod(String method)329     public void setMethod(String method) {
330         mMethod = method.toUpperCase(Locale.ROOT);
331     }
332 
333     /**
334      * Get the URI of this request header.
335      *
336      * @return the request URI
337      */
getURI()338     public URI getURI() {
339         return mUri;
340     }
341 
342     /**
343      * Sets the URI of this request header.
344      *
345      * @param uri the new request URI
346      * @throws URIException if an error occurred while setting the request URI
347      */
setURI(URI uri)348     public void setURI(URI uri) throws URIException {
349 
350         if (uri.getScheme() == null || uri.getScheme().equals("")) {
351             mUri = new URI(HTTP + "://" + getHeader(HOST) + "/" + mUri.toString(), true);
352 
353         } else {
354             mUri = uri;
355         }
356 
357         if (uri.getScheme().equalsIgnoreCase(HTTPS)) {
358             mIsSecure = true;
359 
360         } else {
361             mIsSecure = false;
362         }
363 
364         setHostPort(mUri.getPort());
365     }
366 
367     /**
368      * Get if this request header is under secure connection.
369      *
370      * @return {@code true} if the request is secure, {@code false} otherwise
371      * @deprecated Replaced by {@link #isSecure()}. It will be removed in a future release.
372      */
373     @Deprecated
getSecure()374     public boolean getSecure() {
375         return mIsSecure;
376     }
377 
378     /**
379      * Tells whether the request is secure, or not. A request is considered secure if it's using the
380      * HTTPS protocol.
381      *
382      * @return {@code true} if the request is secure, {@code false} otherwise.
383      */
isSecure()384     public boolean isSecure() {
385         return mIsSecure;
386     }
387 
388     /**
389      * Sets whether or not the request is done using a secure scheme, HTTPS.
390      *
391      * @param isSecure {@code true} if the request should be secure, {@code false} otherwise
392      * @throws URIException if an error occurred while rebuilding the request URI
393      */
setSecure(boolean isSecure)394     public void setSecure(boolean isSecure) throws URIException {
395         mIsSecure = isSecure;
396 
397         if (mUri == null) {
398             // mUri not yet set
399             return;
400         }
401 
402         URI newUri = mUri;
403 
404         // check if URI consistent
405         if (isSecure() && mUri.getScheme().equalsIgnoreCase(HTTP)) {
406             newUri = new URI(mUri.toString().replaceFirst(HTTP, HTTPS), true);
407 
408         } else if (!isSecure() && mUri.getScheme().equalsIgnoreCase(HTTPS)) {
409             newUri = new URI(mUri.toString().replaceFirst(HTTPS, HTTP), true);
410         }
411 
412         if (newUri != mUri) {
413             mUri = newUri;
414             setHostPort(mUri.getPort());
415         }
416     }
417 
418     /** Set the HTTP version of this request header. */
419     @Override
setVersion(String version)420     public void setVersion(String version) {
421         mVersion = version.toUpperCase(Locale.ROOT);
422     }
423 
424     /**
425      * Get the content length in this request header. If the content length is undetermined, 0 will
426      * be returned.
427      */
428     @Override
getContentLength()429     public int getContentLength() {
430         if (mContentLength == -1) {
431             return 0;
432         }
433 
434         return mContentLength;
435     }
436 
437     /**
438      * Parse this request header.
439      *
440      * @param isSecure {@code true} if the request is secure, {@code false} otherwise
441      * @throws URIException if failed to parse the URI
442      * @throws HttpMalformedHeaderException if the request being parsed is malformed
443      */
parse(boolean isSecure)444     private void parse(boolean isSecure) throws URIException, HttpMalformedHeaderException {
445 
446         mIsSecure = isSecure;
447         Matcher matcher = patternRequestLine.matcher(mStartLine);
448         if (!matcher.find()) {
449             mMalformedHeader = true;
450             throw new HttpMalformedHeaderException(
451                     "Failed to find pattern " + patternRequestLine + " in: " + mStartLine);
452         }
453 
454         mMethod = matcher.group(1);
455         String sUri = matcher.group(2);
456         mVersion = matcher.group(3);
457 
458         if (!mVersion.equalsIgnoreCase(HTTP09)
459                 && !mVersion.equalsIgnoreCase(HTTP10)
460                 && !mVersion.equalsIgnoreCase(HTTP11)) {
461             mMalformedHeader = true;
462             throw new HttpMalformedHeaderException("Unexpected version: " + mVersion);
463         }
464 
465         if (mMethod.equalsIgnoreCase(CONNECT)) {
466             parseHostName(sUri);
467             mUri = URI.fromAuthority(sUri);
468 
469         } else {
470             mUri = parseURI(sUri);
471 
472             if (mUri.getScheme() == null || mUri.getScheme().equals("")) {
473                 mUri = new URI(HTTP + "://" + getHeader(HOST) + mUri.toString(), true);
474             }
475 
476             if (isSecure() && mUri.getScheme().equalsIgnoreCase(HTTP)) {
477                 mUri = new URI(mUri.toString().replaceFirst(HTTP, HTTPS), true);
478             }
479 
480             if (mUri.getScheme().equalsIgnoreCase(HTTPS)) {
481                 setSecure(true);
482             }
483             mHostName = mUri.getHost();
484             setHostPort(mUri.getPort());
485         }
486     }
487 
parseHostName(String hostHeader)488     private void parseHostName(String hostHeader) {
489         // no host header given but a valid host name already exist.
490         if (hostHeader == null) {
491             return;
492         }
493 
494         int port = -1;
495         int pos;
496         if ((pos = hostHeader.indexOf(':', 2)) > -1) {
497             mHostName = hostHeader.substring(0, pos).trim();
498             try {
499                 port = Integer.parseInt(hostHeader.substring(pos + 1));
500             } catch (NumberFormatException e) {
501             }
502 
503         } else {
504             mHostName = hostHeader.trim();
505         }
506 
507         setHostPort(port);
508     }
509 
510     /**
511      * Get the host name in this request header.
512      *
513      * @return Host name.
514      */
getHostName()515     public String getHostName() {
516         String hostName = mHostName;
517         try {
518             // ZAP: fixed cases, where host name is null
519             hostName = ((mUri.getHost() != null) ? mUri.getHost() : mHostName);
520 
521         } catch (URIException e) {
522             if (log.isDebugEnabled()) {
523                 log.warn(e);
524             }
525         }
526 
527         return hostName;
528     }
529 
530     /**
531      * Gets the host port number of this request message, a non-negative integer.
532      *
533      * <p>If no port is defined the default port for the used scheme will be returned, either 80 for
534      * HTTP or 443 for HTTPS.
535      *
536      * @return the host port number, a non-negative integer
537      */
getHostPort()538     public int getHostPort() {
539         return mHostPort;
540     }
541 
542     /**
543      * Sets the host port number of this request message.
544      *
545      * <p>If the given {@code port} number is negative (usually -1 to represent that no port number
546      * is defined), the port number set will be the default port number for the used scheme known
547      * using the method {@code isSecure()}, either 80 for HTTP or 443 for HTTPS.
548      *
549      * @param port the new port number
550      * @see #mHostPort
551      * @see #isSecure()
552      * @see URI#getPort()
553      */
setHostPort(int port)554     private void setHostPort(int port) {
555         if (port > -1) {
556             mHostPort = port;
557 
558         } else if (this.isSecure()) {
559             mHostPort = 443;
560 
561         } else {
562             mHostPort = 80;
563         }
564     }
565 
566     /** Return if this request header is a image request basing on the path suffix. */
567     @Override
isImage()568     public boolean isImage() {
569         return isSpecificType(patternImage);
570     }
571 
isCss()572     public boolean isCss() {
573         return isSpecificType(PATTERN_CSS);
574     }
575 
isSpecificType(Pattern pattern)576     private boolean isSpecificType(Pattern pattern) {
577         if (getURI() == null) {
578             return false;
579         }
580 
581         try {
582             // ZAP: prevents a NullPointerException when no path exists
583             final String path = getURI().getPath();
584             if (path != null) {
585                 return (pattern.matcher(path).find());
586             }
587 
588         } catch (URIException e) {
589             log.error(e.getMessage(), e);
590         }
591 
592         return false;
593     }
594 
595     /**
596      * Return if the data given is a request header basing on the first start line.
597      *
598      * @param data the data to be checked
599      * @return {@code true} if the data contains a request line, {@code false} otherwise.
600      */
isRequestLine(String data)601     public static boolean isRequestLine(String data) {
602         return patternPartialRequestLine.matcher(data).find();
603     }
604 
605     /** Return the prime header (first line). */
606     @Override
getPrimeHeader()607     public String getPrimeHeader() {
608         return getMethod() + " " + getURI().toString() + " " + getVersion();
609     }
610     /*
611      * private static final char[] DELIM_UNWISE_CHAR = { '<', '>', '#', '"', '
612      * ', '{', '}', '|', '\\', '^', '[', ']', '`' };
613      */
614     private static final String DELIM = "<>#\"";
615     private static final String UNWISE = "{}|\\^[]`";
616     private static final String DELIM_UNWISE = DELIM + UNWISE;
617 
parseURI(String sUri)618     public static URI parseURI(String sUri) throws URIException {
619         URI uri;
620 
621         int len = sUri.length();
622         StringBuilder sb = new StringBuilder(len);
623         char[] charray = new char[1];
624         String s;
625 
626         for (int i = 0; i < len; i++) {
627             char ch = sUri.charAt(i);
628             // String ch = sUri.substring(i, i+1);
629             if (DELIM_UNWISE.indexOf(ch) >= 0) {
630                 // check if unwise or delim in RFC.  If so, encode it.
631                 charray[0] = ch;
632                 s = new String(charray);
633                 try {
634                     s = URLEncoder.encode(s, "UTF8");
635 
636                 } catch (UnsupportedEncodingException e1) {
637                 }
638 
639                 sb.append(s);
640 
641             } else if (ch == '%') {
642 
643                 // % is exception - no encoding to be done because some server may not handle
644                 // correctly when % is invalid.
645                 //
646 
647                 // sb.append(ch);
648 
649                 // if % followed by hex, no encode.
650 
651                 try {
652                     String hex = sUri.substring(i + 1, i + 3);
653                     Integer.parseInt(hex, 16);
654                     sb.append(ch);
655 
656                 } catch (Exception e) {
657                     charray[0] = ch;
658                     s = new String(charray);
659                     try {
660                         s = URLEncoder.encode(s, "UTF8");
661 
662                     } catch (UnsupportedEncodingException e1) {
663                     }
664                     sb.append(s);
665                 }
666 
667             } else if (ch == ' ') {
668                 // if URLencode, '+' will be appended.
669                 sb.append("%20");
670 
671             } else {
672                 sb.append(ch);
673             }
674         }
675 
676         uri = new URI(sb.toString(), true);
677         return uri;
678     }
679 
680     // Construct new GET url of request
681     // Based on getParams
setGetParams(TreeSet<HtmlParameter> getParams)682     public void setGetParams(TreeSet<HtmlParameter> getParams) {
683         if (mUri == null) {
684             return;
685         }
686 
687         if (getParams.isEmpty()) {
688             try {
689                 mUri.setQuery("");
690 
691             } catch (URIException e) {
692                 log.error(e.getMessage(), e);
693             }
694 
695             return;
696         }
697 
698         StringBuilder sbQuery = new StringBuilder();
699         for (HtmlParameter parameter : getParams) {
700             if (parameter.getType() != HtmlParameter.Type.url) {
701                 continue;
702             }
703 
704             sbQuery.append(parameter.getName());
705             sbQuery.append('=');
706             sbQuery.append(parameter.getValue());
707             sbQuery.append('&');
708         }
709 
710         if (sbQuery.length() <= 2) {
711             try {
712                 mUri.setQuery("");
713 
714             } catch (URIException e) {
715                 log.error(e.getMessage(), e);
716             }
717 
718             return;
719         }
720 
721         String query = sbQuery.substring(0, sbQuery.length() - 1);
722 
723         try {
724             // The previous behaviour was escaping the query,
725             // so it is maintained with the use of setQuery.
726             mUri.setQuery(query);
727 
728         } catch (URIException e) {
729             log.error(e.getMessage(), e);
730         }
731     }
732 
733     /**
734      * Construct new "Cookie:" line in request header based on HttpCookies.
735      *
736      * @param cookies the new cookies
737      */
setCookies(List<HttpCookie> cookies)738     public void setCookies(List<HttpCookie> cookies) {
739         if (cookies.isEmpty()) {
740             setHeader(HttpHeader.COOKIE, null);
741         }
742 
743         StringBuilder sbData = new StringBuilder();
744 
745         for (HttpCookie c : cookies) {
746             sbData.append(c.getName());
747             sbData.append('=');
748             sbData.append(c.getValue());
749             sbData.append("; ");
750         }
751 
752         if (sbData.length() <= 3) {
753             setHeader(HttpHeader.COOKIE, null);
754             return;
755         }
756 
757         final String data = sbData.substring(0, sbData.length() - 2);
758         setHeader(HttpHeader.COOKIE, data);
759     }
760 
761     // Construct new "Cookie:" line in request header,
762     // based on cookieParams
setCookieParams(TreeSet<HtmlParameter> cookieParams)763     public void setCookieParams(TreeSet<HtmlParameter> cookieParams) {
764         if (cookieParams.isEmpty()) {
765             setHeader(HttpHeader.COOKIE, null);
766         }
767 
768         StringBuilder sbData = new StringBuilder();
769 
770         for (HtmlParameter parameter : cookieParams) {
771             if (parameter.getType() != HtmlParameter.Type.cookie) {
772                 continue;
773             }
774 
775             String cookieName = parameter.getName();
776             if (!cookieName.isEmpty()) {
777                 sbData.append(cookieName);
778                 sbData.append('=');
779             }
780             sbData.append(parameter.getValue());
781             sbData.append("; ");
782         }
783 
784         if (sbData.length() <= 2) {
785             setHeader(HttpHeader.COOKIE, null);
786             return;
787         }
788 
789         final String data = sbData.substring(0, sbData.length() - 2);
790         setHeader(HttpHeader.COOKIE, data);
791     }
792 
getCookieParams()793     public TreeSet<HtmlParameter> getCookieParams() {
794         TreeSet<HtmlParameter> set = new TreeSet<>();
795 
796         for (String cookieLine : getHeaderValues(HttpHeader.COOKIE)) {
797             // watch out for the scenario where the first cookie name starts with "cookie"
798             // (uppercase or lowercase)
799             if (cookieLine.toUpperCase().startsWith(HttpHeader.COOKIE.toUpperCase() + ":")) {
800                 // HttpCookie wont parse lines starting with "Cookie:"
801                 cookieLine = cookieLine.substring(HttpHeader.COOKIE.length() + 1);
802             }
803 
804             if (cookieLine.isEmpty()) {
805                 // Nothing to parse.
806                 continue;
807             }
808 
809             // These can be comma separated type=value
810             String[] cookieArray = cookieLine.split(";");
811             for (String cookie : cookieArray) {
812                 set.add(new HtmlParameter(cookie));
813             }
814         }
815 
816         return set;
817     }
818 
819     // ZAP: Added method for working directly with HttpCookie
820     /**
821      * Gets a list of the http cookies from this request Header.
822      *
823      * @return the http cookies
824      * @throws IllegalArgumentException if a problem is encountered while processing the "Cookie: "
825      *     header line.
826      */
getHttpCookies()827     public List<HttpCookie> getHttpCookies() {
828         List<HttpCookie> cookies = new LinkedList<>();
829         // Use getCookieParams to reduce the places we parse cookies
830         TreeSet<HtmlParameter> ts = getCookieParams();
831         Iterator<HtmlParameter> it = ts.iterator();
832         while (it.hasNext()) {
833             HtmlParameter htmlParameter = it.next();
834             if (!htmlParameter.getName().isEmpty()) {
835                 try {
836                     cookies.add(new HttpCookie(htmlParameter.getName(), htmlParameter.getValue()));
837 
838                 } catch (IllegalArgumentException e) {
839                     // Occurs while scanning ;)
840                     log.debug(e.getMessage() + " " + htmlParameter.getName());
841                 }
842             }
843         }
844 
845         return cookies;
846     }
847 
848     /**
849      * Sets the senders IP address. Note that this is not persisted.
850      *
851      * @param inetAddress the senders IP address
852      * @since 2.6.0
853      */
setSenderAddress(InetAddress inetAddress)854     public void setSenderAddress(InetAddress inetAddress) {
855         this.senderAddress = inetAddress;
856     }
857 
858     /**
859      * Gets the senders IP address
860      *
861      * @return the senders IP address
862      * @since 2.6.0
863      */
getSenderAddress()864     public InetAddress getSenderAddress() {
865         return senderAddress;
866     }
867 
868     /**
869      * Sets the user agent used by {@link #HttpRequestHeader(String, URI, String) default request
870      * header}.
871      *
872      * <p>This is expected to be called only by core code, when the corresponding option is changed.
873      *
874      * @param defaultUserAgent the default user agent.
875      * @since 2.8.0
876      */
setDefaultUserAgent(String defaultUserAgent)877     public static void setDefaultUserAgent(String defaultUserAgent) {
878         HttpRequestHeader.defaultUserAgent = defaultUserAgent;
879     }
880 
881     /**
882      * Gets the user agent used by {@link #HttpRequestHeader(String, URI, String) default request
883      * header}.
884      *
885      * @return the default user agent.
886      * @since 2.8.0
887      */
getDefaultUserAgent()888     public static String getDefaultUserAgent() {
889         return defaultUserAgent;
890     }
891 }
892