View Javadoc
1   /*
2    * Copyright (c) 2002-2026 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit;
16  
17  import java.io.IOException;
18  import java.io.ObjectInputStream;
19  import java.io.ObjectOutputStream;
20  import java.io.Serializable;
21  import java.net.IDN;
22  import java.net.MalformedURLException;
23  import java.net.URL;
24  import java.nio.charset.Charset;
25  import java.nio.charset.StandardCharsets;
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.EnumSet;
29  import java.util.HashMap;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Set;
33  import java.util.regex.Pattern;
34  
35  import org.apache.http.auth.Credentials;
36  import org.htmlunit.http.HttpUtils;
37  import org.htmlunit.httpclient.HtmlUnitUsernamePasswordCredentials;
38  import org.htmlunit.util.NameValuePair;
39  import org.htmlunit.util.UrlUtils;
40  
41  /**
42   * Parameter object for making web requests.
43   *
44   * @author Brad Clarke
45   * @author Hans Donner
46   * @author Ahmed Ashour
47   * @author Marc Guillemot
48   * @author Rodney Gitzel
49   * @author Ronald Brill
50   * @author Adam Afeltowicz
51   * @author Joerg Werner
52   * @author Michael Lueck
53   * @author Lai Quang Duong
54   * @author Kristof Neirynck
55   */
56  @SuppressWarnings("PMD.TooManyFields")
57  public class WebRequest implements Serializable {
58  
59      /**
60       * Enum to configure request creation.
61       */
62      public enum HttpHint {
63          /** Force to include the charset. */
64          IncludeCharsetInContentTypeHeader,
65  
66          /** Disable sending of stored cookies and receiving of new cookies. */
67          BlockCookies
68      }
69  
70      private static final Pattern DOT_PATTERN = Pattern.compile("/\\./");
71      private static final Pattern DOT_DOT_PATTERN = Pattern.compile("/(?!\\.\\.)[^/]*/\\.\\./");
72      private static final Pattern REMOVE_DOTS_PATTERN = Pattern.compile("^/(\\.\\.?/)*");
73  
74      private String url_; // String instead of java.net.URL because "about:blank" URLs don't serialize correctly
75      private String proxyHost_;
76      private int proxyPort_;
77      private String proxyScheme_;
78      private boolean isSocksProxy_;
79      private HttpMethod httpMethod_ = HttpMethod.GET;
80      private FormEncodingType encodingType_ = FormEncodingType.URL_ENCODED;
81      private Map<String, String> additionalHeaders_ = new HashMap<>();
82      private Credentials urlCredentials_;
83      private Credentials credentials_;
84      private int timeout_;
85      private transient Set<HttpHint> httpHints_;
86  
87      private transient Charset charset_ = StandardCharsets.ISO_8859_1;
88      // https://datatracker.ietf.org/doc/html/rfc6838#section-4.2.1
89      // private transient Charset defaultResponseContentCharset_ = StandardCharsets.UTF_8;
90      private transient Charset defaultResponseContentCharset_ = StandardCharsets.ISO_8859_1;
91  
92      /*
93       * These two are mutually exclusive; additionally, requestBody_ should only be
94       * set for POST requests.
95       */
96      private List<NameValuePair> requestParameters_ = Collections.emptyList();
97      private String requestBody_;
98  
99      /**
100      * Creates or updates this object..
101      *
102      * Instantiates a {@link WebRequest} for the specified URL.
103      *
104      * @param url                  the target URL
105      * @param acceptHeader         the accept header to use
106      * @param acceptEncodingHeader the accept encoding header to use
107      */
108     public WebRequest(final URL url, final String acceptHeader, final String acceptEncodingHeader) {
109         setUrl(url);
110         if (acceptHeader != null) {
111             setAdditionalHeader(HttpHeader.ACCEPT, acceptHeader);
112         }
113         if (acceptEncodingHeader != null) {
114             setAdditionalHeader(HttpHeader.ACCEPT_ENCODING, acceptEncodingHeader);
115         }
116         timeout_ = -1;
117     }
118 
119     /**
120      * Creates or updates this object..
121      *
122      * Instantiates a {@link WebRequest} for the specified URL.
123      *
124      * @param url        the target URL
125      * @param charset    the charset to use
126      * @param refererUrl the url be used by the referer header
127      */
128     public WebRequest(final URL url, final Charset charset, final URL refererUrl) {
129         setUrl(url);
130         setCharset(charset);
131         setRefererHeader(refererUrl);
132     }
133 
134     /**
135      * Returns a new request for about:blank.
136      *
137      * @return a new request for about:blank
138      */
139     public static WebRequest newAboutBlankRequest() {
140         return new WebRequest(UrlUtils.URL_ABOUT_BLANK, "*/*", "gzip, deflate");
141     }
142 
143     /**
144      * Creates or updates this object..
145      *
146      * Instantiates a {@link WebRequest} for the specified URL.
147      *
148      * @param url the target URL
149      */
150     public WebRequest(final URL url) {
151         this(url, "*/*", "gzip, deflate");
152     }
153 
154     /**
155      * Creates or updates this object..
156      *
157      * Instantiates a {@link WebRequest} for the specified URL using the specified
158      * HTTP submit method.
159      *
160      * @param url          the target URL
161      * @param submitMethod the HTTP submit method to use
162      */
163     public WebRequest(final URL url, final HttpMethod submitMethod) {
164         this(url);
165         setHttpMethod(submitMethod);
166     }
167 
168     /**
169      * Returns the target URL.
170      *
171      * Returns the target URL.
172      *
173      * @return the target URL
174      */
175     public URL getUrl() {
176         return UrlUtils.toUrlSafe(url_);
177     }
178 
179     /**
180      * Creates or updates this object.
181      *
182      * Sets the target URL. The URL may be simplified if needed (for instance
183      * eliminating irrelevant path portions like "/./").
184      *
185      * @param url the target URL
186      */
187     public void setUrl(URL url) {
188         if (url == null) {
189             url_ = null;
190             return;
191         }
192 
193         final String path = url.getPath();
194         if (path.isEmpty()) {
195             if (!url.getFile().isEmpty() || url.getProtocol().startsWith("http")) {
196                 url = buildUrlWithNewPath(url, "/");
197             }
198         }
199         else if (path.contains("/.")) {
200             url = buildUrlWithNewPath(url, removeDots(path));
201         }
202 
203         try {
204             final String idn = IDN.toASCII(url.getHost());
205             if (!idn.equals(url.getHost())) {
206                 url = UrlUtils.getUrlWithNewHost(url, idn);
207             }
208         }
209         catch (final Exception e) {
210             throw new IllegalArgumentException(
211                     "Cannot convert the hostname of URL: '" + url.toExternalForm() + "' to ASCII.", e);
212         }
213 
214         try {
215             url_ = UrlUtils.removeRedundantPort(url).toExternalForm();
216         }
217         catch (final MalformedURLException e) {
218             throw new RuntimeException("Cannot strip default port of URL: " + url.toExternalForm(), e);
219         }
220 
221         // http://john.smith:secret@localhost
222         final String userInfo = url.getUserInfo();
223         if (userInfo != null) {
224             final int splitPos = userInfo.indexOf(':');
225             if (splitPos == -1) {
226                 urlCredentials_ = new HtmlUnitUsernamePasswordCredentials(userInfo, new char[0]);
227             }
228             else {
229                 final String username = userInfo.substring(0, splitPos);
230                 final String password = userInfo.substring(splitPos + 1);
231                 urlCredentials_ = new HtmlUnitUsernamePasswordCredentials(username, password.toCharArray());
232             }
233         }
234     }
235 
236     /*
237      * Strip a URL string of "/./" and "/../" occurrences. <p> One trick here is to
238      * repeatedly create new matchers on a given pattern, so that we can see whether
239      * it needs to be re-applied; unfortunately .replaceAll() doesn't re-process its
240      * own output, so if we create a new match with a replacement, it is missed.
241      */
242     private static String removeDots(final String path) {
243         String newPath = path;
244 
245         // remove occurrences at the beginning
246         newPath = REMOVE_DOTS_PATTERN.matcher(newPath).replaceAll("/");
247         if ("/..".equals(newPath)) {
248             newPath = "/";
249         }
250 
251         // single dots have no effect, so just remove them
252         while (DOT_PATTERN.matcher(newPath).find()) {
253             newPath = DOT_PATTERN.matcher(newPath).replaceAll("/");
254         }
255 
256         // mid-path double dots should be removed WITH the previous subdirectory and replaced
257         //  with "/" BUT ONLY IF that subdirectory's not also ".." (a regex lookahead helps with this)
258         while (DOT_DOT_PATTERN.matcher(newPath).find()) {
259             newPath = DOT_DOT_PATTERN.matcher(newPath).replaceAll("/");
260         }
261 
262         return newPath;
263     }
264 
265     private static URL buildUrlWithNewPath(URL url, final String newPath) {
266         try {
267             url = UrlUtils.getUrlWithNewPath(url, newPath);
268         }
269         catch (final Exception e) {
270             throw new RuntimeException("Cannot change path of URL: " + url.toExternalForm(), e);
271         }
272         return url;
273     }
274 
275     /**
276      * Returns the proxy host to use.
277      *
278      * Returns the proxy host to use.
279      *
280      * @return the proxy host to use
281      */
282     public String getProxyHost() {
283         return proxyHost_;
284     }
285 
286     /**
287      * Creates or updates this object.
288      *
289      * Sets the proxy host to use.
290      *
291      * @param proxyHost the proxy host to use
292      */
293     public void setProxyHost(final String proxyHost) {
294         proxyHost_ = proxyHost;
295     }
296 
297     /**
298      * Returns the proxy port to use.
299      *
300      * Returns the proxy port to use.
301      *
302      * @return the proxy port to use
303      */
304     public int getProxyPort() {
305         return proxyPort_;
306     }
307 
308     /**
309      * Creates or updates this object.
310      *
311      * Sets the proxy port to use.
312      *
313      * @param proxyPort the proxy port to use
314      */
315     public void setProxyPort(final int proxyPort) {
316         proxyPort_ = proxyPort;
317     }
318 
319     /**
320      * Returns the proxy scheme to use.
321      *
322      * Returns the proxy scheme to use.
323      *
324      * @return the proxy scheme to use
325      */
326     public String getProxyScheme() {
327         return proxyScheme_;
328     }
329 
330     /**
331      * Creates or updates this object..
332      *
333      * Sets the proxy scheme to use.
334      *
335      * @param proxyScheme the proxy scheme to use
336      *
337      */
338     public void setProxyScheme(final String proxyScheme) {
339         proxyScheme_ = proxyScheme;
340     }
341 
342     /**
343      * Returns whether SOCKS proxy or not.
344      *
345      * Returns whether SOCKS proxy or not.
346      *
347      * @return whether SOCKS proxy or not
348      *
349      */
350     public boolean isSocksProxy() {
351         return isSocksProxy_;
352     }
353 
354     /**
355      * Creates or updates this object..
356      *
357      * Sets whether SOCKS proxy or not.
358      *
359      * @param isSocksProxy whether SOCKS proxy or not
360      *
361      */
362     public void setSocksProxy(final boolean isSocksProxy) {
363         isSocksProxy_ = isSocksProxy;
364     }
365 
366     /**
367      * Returns the timeout to use.
368      *
369      * @return the timeout to use
370      *
371      */
372     public int getTimeout() {
373         return timeout_;
374     }
375 
376     /**
377      * Creates or updates this object..
378      *
379      * Sets the timeout to use.
380      *
381      * @param timeout the timeout to use
382      *
383      */
384     public void setTimeout(final int timeout) {
385         timeout_ = timeout;
386     }
387 
388     /**
389      * Returns the form encoding type to use.
390      *
391      * Returns the form encoding type to use.
392      *
393      * @return the form encoding type to use
394      *
395      */
396     public FormEncodingType getEncodingType() {
397         return encodingType_;
398     }
399 
400     /**
401      * Creates or updates this object..
402      *
403      * Sets the form encoding type to use.
404      *
405      * @param encodingType the form encoding type to use
406      *
407      */
408     public void setEncodingType(final FormEncodingType encodingType) {
409         encodingType_ = encodingType;
410     }
411 
412     /**
413      * Returns the request parameters to use.
414      *
415      * <p>
416      * Retrieves the request parameters used. Similar to the servlet api function
417      * getParameterMap() this works depending on the request type and collects the
418      * url parameters and the body stuff.<br>
419      * The value is also normalized - null is converted to an empty string.</p>
420      * <p>In contrast to the servlet api this creates a separate KeyValuePair for every
421      * parameter. This means that pairs with the same name can be part of the list. The
422      * servlet api will return a string[] as value for the key in this case.<br>
423      * Additionally this method includes also the uploaded files for multipart post
424      * requests.</p>
425      *
426      * @return the request parameters to use
427      */
428     public List<NameValuePair> getParameters() {
429         // developer note:
430         // this has to be in sync with org.htmlunit.HttpWebConnection.makeHttpMethod(WebRequest, HttpClientBuilder)
431 
432         // developer note:
433         // the spring org.springframework.test.web.servlet.htmlunitHtmlUnitRequestBuilder uses
434         // this method and is sensitive to all the details of the current implementation.
435 
436         final List<NameValuePair> allParameters = new ArrayList<>(
437                 HttpUtils.parseUrlQuery(getUrl().getQuery(), getCharset()));
438 
439         // the servlet api ignores these parameters but to make spring happy we include them
440         final HttpMethod httpMethod = getHttpMethod();
441         if (httpMethod == HttpMethod.POST
442             || httpMethod == HttpMethod.PUT
443             || httpMethod == HttpMethod.PATCH
444             || httpMethod == HttpMethod.DELETE
445             || httpMethod == HttpMethod.OPTIONS) {
446             if (FormEncodingType.URL_ENCODED == getEncodingType()
447                 && httpMethod != HttpMethod.OPTIONS) {
448                 // spring ignores URL_ENCODED parameters for OPTIONS requests
449                 // getRequestParameters and getRequestBody are mutually exclusive
450                 if (getRequestBody() == null) {
451                     allParameters.addAll(getRequestParameters());
452                 }
453                 else {
454                     allParameters.addAll(HttpUtils.parseUrlQuery(getRequestBody(), getCharset()));
455                 }
456             }
457             else if (FormEncodingType.MULTIPART == getEncodingType()) {
458                 if (httpMethod == HttpMethod.POST) {
459                     allParameters.addAll(getRequestParameters());
460                 }
461                 else {
462                     // for PUT, PATCH, DELETE and OPTIONS spring moves the parameters up to the query
463                     // it doesn't replace the query
464                     allParameters.addAll(0, getRequestParameters());
465                 }
466             }
467         }
468 
469         return normalize(allParameters);
470     }
471 
472     private static List<NameValuePair> normalize(final List<NameValuePair> pairs) {
473         if (pairs == null || pairs.isEmpty()) {
474             return pairs;
475         }
476 
477         final List<NameValuePair> resultingPairs = new ArrayList<>();
478         for (final NameValuePair pair : pairs) {
479             resultingPairs.add(pair.normalized());
480         }
481 
482         return resultingPairs;
483     }
484 
485     /**
486      * Returns the request parameters to use.
487      *
488      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT
489      * YOUR OWN RISK.</span><br>
490      *
491      * Retrieves the request parameters to use. If set, these request parameters
492      * will overwrite any request parameters which may be present in the
493      * {@link #getUrl() URL}. Should not be used in combination with the
494      * {@link #setRequestBody(String) request body}.
495      *
496      * @return the request parameters to use
497      *
498      */
499     public List<NameValuePair> getRequestParameters() {
500         return requestParameters_;
501     }
502 
503     /**
504      * Creates or updates this object..
505      *
506      * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT
507      * YOUR OWN RISK.</span><br>
508      *
509      * Sets the request parameters to use. If set, these request parameters will
510      * overwrite any request parameters which may be present in the {@link #getUrl()
511      * URL}. Should not be used in combination with the
512      * {@link #setRequestBody(String) request body}.
513      *
514      * @param requestParameters the request parameters to use
515      * @throws RuntimeException if the request body has already been set
516      *
517      */
518     public void setRequestParameters(final List<NameValuePair> requestParameters) throws RuntimeException {
519         if (requestBody_ != null) {
520             final String msg = "Trying to set the request parameters, but the request body has already been specified;"
521                     + "the two are mutually exclusive!";
522             throw new RuntimeException(msg);
523         }
524         requestParameters_ = requestParameters;
525     }
526 
527     /**
528      * Returns the body content to be submitted if this is a <code>POST</code>
529      * request.
530      *
531      * Returns the body content to be submitted if this is a <code>POST</code>
532      * request. Ignored for all other request types. Should not be used in
533      * combination with {@link #setRequestParameters(List) request parameters}.
534      *
535      * @return the body content to be submitted if this is a <code>POST</code>
536      *         request
537      *
538      */
539     public String getRequestBody() {
540         return requestBody_;
541     }
542 
543     /**
544      * Creates or updates this object..
545      *
546      * Sets the body content to be submitted if this is a {@code POST}, {@code PUT}
547      * or {@code PATCH} request. Other request types result in
548      * {@link RuntimeException}. Should not be used in combination with
549      * {@link #setRequestParameters(List) request parameters}.
550      *
551      * @param requestBody the body content to be submitted if this is a
552      *                    {@code POST}, {@code PUT} or {@code PATCH} request
553      * @throws RuntimeException if the request parameters have already been set or
554      *                          this is not a {@code POST}, {@code PUT} or
555      *                          {@code PATCH} request.
556      *
557      */
558     public void setRequestBody(final String requestBody) throws RuntimeException {
559         if (requestParameters_ != null && !requestParameters_.isEmpty()) {
560             final String msg = "Trying to set the request body, but the request parameters have already been specified;"
561                        + "the two are mutually exclusive!";
562             throw new RuntimeException(msg);
563         }
564         if (httpMethod_ != HttpMethod.POST
565                 && httpMethod_ != HttpMethod.PUT
566                 && httpMethod_ != HttpMethod.PATCH
567                 && httpMethod_ != HttpMethod.DELETE
568                 && httpMethod_ != HttpMethod.OPTIONS) {
569             final String msg = "The request body may only be set for POST, PUT, PATCH, DELETE or OPTIONS requests!";
570             throw new RuntimeException(msg);
571         }
572         requestBody_ = requestBody;
573     }
574 
575     /**
576      * Returns the HTTP submit method to use.
577      *
578      * Returns the HTTP submit method to use.
579      *
580      * @return the HTTP submit method to use
581      *
582      */
583     public HttpMethod getHttpMethod() {
584         return httpMethod_;
585     }
586 
587     /**
588      * Creates or updates this object..
589      *
590      * Sets the HTTP submit method to use.
591      *
592      * @param submitMethod the HTTP submit method to use
593      *
594      */
595     public void setHttpMethod(final HttpMethod submitMethod) {
596         httpMethod_ = submitMethod;
597     }
598 
599     /**
600      * Returns the additional HTTP headers to use.
601      *
602      * Returns the additional HTTP headers to use.
603      *
604      * @return the additional HTTP headers to use
605      *
606      */
607     public Map<String, String> getAdditionalHeaders() {
608         return additionalHeaders_;
609     }
610 
611     /**
612      * Creates or updates this object..
613      *
614      * Sets the additional HTTP headers to use.
615      *
616      * @param additionalHeaders the additional HTTP headers to use
617      *
618      */
619     public void setAdditionalHeaders(final Map<String, String> additionalHeaders) {
620         additionalHeaders_ = additionalHeaders;
621     }
622 
623     /**
624      * Creates or updates this object..
625      *
626      * Returns whether the specified header name is already included in the
627      * additional HTTP headers.
628      *
629      * @param name the name of the additional HTTP header
630      * @return true if the specified header name is included in the additional HTTP
631      *         headers
632      *
633      */
634     public boolean isAdditionalHeader(final String name) {
635         for (final String key : additionalHeaders_.keySet()) {
636             if (name.equalsIgnoreCase(key)) {
637                 return true;
638             }
639         }
640         return false;
641     }
642 
643     /**
644      * Creates or updates this object..
645      *
646      * Returns the header value associated with this name.
647      *
648      * @param name the name of the additional HTTP header
649      * @return the value or null
650      *
651      */
652     public String getAdditionalHeader(final String name) {
653         String newKey = name;
654         for (final String key : additionalHeaders_.keySet()) {
655             if (name.equalsIgnoreCase(key)) {
656                 newKey = key;
657                 break;
658             }
659         }
660         return additionalHeaders_.get(newKey);
661     }
662 
663     /**
664      * Creates or updates this object..
665      *
666      * Sets the referer HTTP header - only if the provided url is valid.
667      *
668      * @param url the url for the referer HTTP header
669      *
670      */
671     public void setRefererHeader(final URL url) {
672         if (url == null || !url.getProtocol().startsWith("http")) {
673             return;
674         }
675 
676         try {
677             setAdditionalHeader(HttpHeader.REFERER, UrlUtils.getUrlWithoutRef(url).toExternalForm());
678         }
679         catch (final MalformedURLException ignored) {
680             // bad luck us the whole url from the pager
681         }
682     }
683 
684     /**
685      * Creates or updates this object..
686      *
687      * Sets the specified name/value pair in the additional HTTP headers.
688      *
689      * @param name  the name of the additional HTTP header
690      * @param value the value of the additional HTTP header
691      *
692      */
693     public void setAdditionalHeader(final String name, final String value) {
694         String newKey = name;
695         for (final String key : additionalHeaders_.keySet()) {
696             if (name.equalsIgnoreCase(key)) {
697                 newKey = key;
698                 break;
699             }
700         }
701         additionalHeaders_.put(newKey, value);
702     }
703 
704     /**
705      * Creates or updates this object..
706      *
707      * Removed the specified name/value pair from the additional HTTP headers.
708      *
709      * @param name the name of the additional HTTP header
710      *
711      */
712     public void removeAdditionalHeader(String name) {
713         for (final String key : additionalHeaders_.keySet()) {
714             if (name.equalsIgnoreCase(key)) {
715                 name = key;
716                 break;
717             }
718         }
719         additionalHeaders_.remove(name);
720     }
721 
722     /**
723      * Returns the credentials if set as part of the url.
724      *
725      * Returns the credentials to use.
726      *
727      * @return the credentials if set as part of the url
728      *
729      */
730     public Credentials getUrlCredentials() {
731         return urlCredentials_;
732     }
733 
734     /**
735      * Returns the credentials if set from the external builder.
736      *
737      * Returns the credentials to use.
738      *
739      * @return the credentials if set from the external builder
740      *
741      */
742     public Credentials getCredentials() {
743         return credentials_;
744     }
745 
746     /**
747      * Creates or updates this object..
748      *
749      * Sets the credentials to use.
750      *
751      * @param credentials the credentials to use
752      *
753      */
754     public void setCredentials(final Credentials credentials) {
755         credentials_ = credentials;
756     }
757 
758     /**
759      * Returns the character set to use to perform the request.
760      *
761      * Returns the character set to use to perform the request.
762      *
763      * @return the character set to use to perform the request
764      *
765      */
766     public Charset getCharset() {
767         return charset_;
768     }
769 
770     /**
771      * Creates or updates this object.
772      *
773      * Sets the character set to use to perform the request. The default value is
774      * {@link java.nio.charset.StandardCharsets#ISO_8859_1}.
775      *
776      * @param charset the character set to use to perform the request
777      *
778      */
779     public void setCharset(final Charset charset) {
780         charset_ = charset;
781     }
782 
783     /**
784      * Returns the default character set to use for the response when it does not
785      * specify one.
786      *
787      * @return the default character set to use for the response when it does not
788      *         specify one.
789      *
790      */
791     public Charset getDefaultResponseContentCharset() {
792         return defaultResponseContentCharset_;
793     }
794 
795     /**
796      * Creates or updates this object.
797      *
798      * Sets the default character set to use for the response when it does not
799      * specify one.
800      * <p>
801      * Unless set, the default is {@link java.nio.charset.StandardCharsets#UTF_8}.
802      * </p>
803      *
804      * @param defaultResponseContentCharset the default character set of the
805      *                                      response
806      *
807      */
808     public void setDefaultResponseContentCharset(final Charset defaultResponseContentCharset) {
809         WebAssert.notNull("defaultResponseContentCharset", defaultResponseContentCharset);
810         defaultResponseContentCharset_ = defaultResponseContentCharset;
811     }
812 
813     /**
814      * Creates or updates this object..
815      *
816      * @param hint the hint to check for
817      * @return true if the hint is enabled
818      *
819      */
820     public boolean hasHint(final HttpHint hint) {
821         if (httpHints_ == null) {
822             return false;
823         }
824         return httpHints_.contains(hint);
825     }
826 
827     /**
828      * Creates or updates this object..
829      *
830      * Enables the hint.
831      *
832      * @param hint the hint to add
833      *
834      */
835     public void addHint(final HttpHint hint) {
836         if (httpHints_ == null) {
837             httpHints_ = EnumSet.noneOf(HttpHint.class);
838         }
839         httpHints_.add(hint);
840     }
841 
842     /**
843      * Returns a string representation of this object.
844      *
845      * Returns a string representation of this object.
846      *
847      * @return a string representation of this object
848      *
849      */
850     @Override
851     public String toString() {
852         final StringBuilder builder = new StringBuilder(100)
853                 .append(getClass().getSimpleName())
854                 .append("[<url=\"")
855                 .append(url_)
856                 .append("\", ").append(httpMethod_)
857                 .append(", ").append(encodingType_)
858                 .append(", ").append(requestParameters_)
859                 .append(", ").append(additionalHeaders_)
860                 .append(", ").append(credentials_)
861                 .append(">]");
862         return builder.toString();
863     }
864 
865     private void writeObject(final ObjectOutputStream oos) throws IOException {
866         oos.defaultWriteObject();
867         oos.writeObject(charset_ == null ? null : charset_.name());
868         oos.writeObject(defaultResponseContentCharset_ == null ? null : defaultResponseContentCharset_.name());
869     }
870 
871     private void readObject(final ObjectInputStream ois) throws ClassNotFoundException, IOException {
872         ois.defaultReadObject();
873         final String charsetName = (String) ois.readObject();
874         if (charsetName != null) {
875             charset_ = Charset.forName(charsetName);
876         }
877         final String defaultResponseContentCharset = (String) ois.readObject();
878         if (defaultResponseContentCharset != null) {
879             defaultResponseContentCharset_ = Charset.forName(defaultResponseContentCharset);
880         }
881     }
882 }