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