001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.oauth2.sdk.http;
019
020
021import com.nimbusds.common.contenttype.ContentType;
022import com.nimbusds.jwt.SignedJWT;
023import com.nimbusds.oauth2.sdk.ParseException;
024import com.nimbusds.oauth2.sdk.SerializeException;
025import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
026import com.nimbusds.oauth2.sdk.util.MapUtils;
027import com.nimbusds.oauth2.sdk.util.StringUtils;
028import com.nimbusds.oauth2.sdk.util.URLUtils;
029import net.jcip.annotations.ThreadSafe;
030import net.minidev.json.JSONObject;
031
032import javax.net.ssl.HostnameVerifier;
033import javax.net.ssl.HttpsURLConnection;
034import javax.net.ssl.SSLSocketFactory;
035import java.io.*;
036import java.net.*;
037import java.nio.charset.StandardCharsets;
038import java.security.cert.X509Certificate;
039import java.util.List;
040import java.util.Map;
041
042
043/**
044 * HTTP request with support for the parameters required to construct an 
045 * {@link com.nimbusds.oauth2.sdk.Request OAuth 2.0 request message}.
046 *
047 * <p>Supported HTTP methods:
048 *
049 * <ul>
050 *     <li>{@link Method#GET HTTP GET}
051 *     <li>{@link Method#POST HTTP POST}
052 *     <li>{@link Method#POST HTTP PUT}
053 *     <li>{@link Method#POST HTTP DELETE}
054 * </ul>
055 *
056 * <p>Supported request headers:
057 *
058 * <ul>
059 *     <li>Content-Type
060 *     <li>Authorization
061 *     <li>Accept
062 *     <li>Etc.
063 * </ul>
064 *
065 * <p>Supported timeouts:
066 *
067 * <ul>
068 *     <li>On HTTP connect
069 *     <li>On HTTP response read
070 * </ul>
071 *
072 * <p>HTTP 3xx redirection: follow (default) / don't follow
073 */
074@ThreadSafe
075public class HTTPRequest extends HTTPMessage implements ReadOnlyHTTPRequest {
076
077
078        /**
079         * Enumeration of the HTTP methods used in OAuth 2.0 requests.
080         */
081        public enum Method {
082        
083                /**
084                 * HTTP GET.
085                 */
086                GET,
087                
088                
089                /**
090                 * HTTP POST.
091                 */
092                POST,
093                
094                
095                /**
096                 * HTTP PUT.
097                 */
098                PUT,
099                
100                
101                /**
102                 * HTTP DELETE.
103                 */
104                DELETE
105        }
106        
107        
108        /**
109         * The request method.
110         */
111        private final Method method;
112
113
114        /**
115         * The request URL (mutable).
116         */
117        private URL url;
118
119
120        /**
121         * The HTTP connect timeout, in milliseconds. Zero implies none.
122         */
123        private int connectTimeout = 0;
124
125
126        /**
127         * The HTTP response read timeout, in milliseconds. Zero implies none.
128
129         */
130        private int readTimeout = 0;
131
132        
133        /**
134         * Do not use a connection specific proxy by default.
135         */
136        private Proxy proxy = null;
137
138        /**
139         * Controls HTTP 3xx redirections.
140         */
141        private boolean followRedirects = true;
142        
143        
144        /**
145         * The received validated client X.509 certificate for a received HTTPS
146         * request, {@code null} if not specified.
147         */
148        private X509Certificate clientX509Certificate = null;
149        
150        
151        /**
152         * The subject DN of a received client X.509 certificate for a received
153         * HTTPS request, {@code null} if not specified.
154         */
155        private String clientX509CertificateSubjectDN = null;
156        
157        
158        /**
159         * The root issuer DN of a received client X.509 certificate for a
160         * received HTTPS request, {@code null} if not specified.
161         */
162        private String clientX509CertificateRootDN = null;
163        
164        
165        /**
166         * The hostname verifier to use for outgoing HTTPS requests,
167         * {@code null} implies the default one.
168         */
169        private HostnameVerifier hostnameVerifier = null;
170        
171        
172        /**
173         * The SSL socket factory to use for outgoing HTTPS requests,
174         * {@code null} implies the default one.
175         */
176        private SSLSocketFactory sslSocketFactory = null;
177
178
179        /**
180         * The default hostname verifier for all outgoing HTTPS requests.
181         */
182        private static HostnameVerifier defaultHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
183
184
185        /**
186         * The default socket factory for all outgoing HTTPS requests.
187         */
188        private static SSLSocketFactory defaultSSLSocketFactory = (SSLSocketFactory)SSLSocketFactory.getDefault();
189
190
191        /**
192         * If {@code true} disables swallowing of {@link IOException}s when the
193         * HTTP connection streams are closed.
194         */
195        private boolean debugCloseStreams = false;
196
197
198        /**
199         * Creates a new minimally specified HTTP request.
200         *
201         * @param method The HTTP request method. Must not be {@code null}.
202         * @param url    The HTTP request URL. Must not be {@code null}.
203         */
204        public HTTPRequest(final Method method, final URL url) {
205        
206                if (method == null)
207                        throw new IllegalArgumentException("The HTTP method must not be null");
208                
209                this.method = method;
210
211
212                if (url == null)
213                        throw new IllegalArgumentException("The HTTP URL must not be null");
214
215                this.url = url;
216        }
217        
218        
219        /**
220         * Creates a new minimally specified HTTP request.
221         *
222         * @param method The HTTP request method. Must not be {@code null}.
223         * @param uri    The HTTP request URI. Must be a URL and not
224         *               {@code null}.
225         */
226        public HTTPRequest(final Method method, final URI uri) {
227                this(method, toURLWithUncheckedException(uri));
228        }
229        
230        
231        private static URL toURLWithUncheckedException(final URI uri) {
232                try {
233                        return uri.toURL();
234                } catch (MalformedURLException | IllegalArgumentException e) {
235                        throw new SerializeException(e.getMessage(), e);
236                }
237        }
238        
239        
240        /**
241         * Gets the request method.
242         *
243         * @return The request method.
244         */
245        @Override
246        public Method getMethod() {
247        
248                return method;
249        }
250
251
252        /**
253         * Gets the request URL.
254         *
255         * @return The request URL.
256         */
257        @Override
258        public URL getURL() {
259
260                return url;
261        }
262
263
264        /**
265         * Gets the request URL as URI.
266         *
267         * @return The request URL as URI.
268         */
269        @Override
270        public URI getURI() {
271                
272                try {
273                        return url.toURI();
274                } catch (URISyntaxException e) {
275                        // Should never happen
276                        throw new IllegalStateException(e.getMessage(), e);
277                }
278        }
279        
280        
281        /**
282         * Ensures this HTTP request has the specified method.
283         *
284         * @param expectedMethod The expected method. Must not be {@code null}.
285         *
286         * @throws ParseException If the method doesn't match the expected.
287         */
288        public void ensureMethod(final Method expectedMethod)
289                throws ParseException {
290                
291                if (method != expectedMethod)
292                        throw new ParseException("The HTTP request method must be " + expectedMethod);
293        }
294        
295        
296        /**
297         * Gets the {@code Authorization} header value.
298         *
299         * @return The {@code Authorization} header value, {@code null} if not 
300         *         specified.
301         */
302        public String getAuthorization() {
303        
304                return getHeaderValue("Authorization");
305        }
306        
307        
308        /**
309         * Sets the {@code Authorization} header value.
310         *
311         * @param authz The {@code Authorization} header value, {@code null} if 
312         *              not specified.
313         */
314        public void setAuthorization(final String authz) {
315        
316                setHeader("Authorization", authz);
317        }
318        
319        
320        /**
321         * Gets the {@code DPoP} header value.
322         *
323         * @return The {@code DPoP} header value, {@code null} if not specified
324         *         or parsing failed.
325         */
326        public SignedJWT getDPoP() {
327        
328                try {
329                        return getPoPWithException();
330                } catch (ParseException e) {
331                        return null;
332                }
333        }
334        
335        
336        /**
337         * Gets the {@code DPoP} header value.
338         *
339         * @return The {@code DPoP} header value, {@code null} if not
340         *         specified.
341         *
342         * @throws ParseException If JWT parsing failed.
343         */
344        public SignedJWT getPoPWithException()
345                throws ParseException {
346                
347                String dPoP = getHeaderValue("DPoP");
348                if (dPoP == null) {
349                        return null;
350                }
351                
352                try {
353                        return SignedJWT.parse(dPoP);
354                } catch (java.text.ParseException e) {
355                        throw new ParseException(e.getMessage(), e);
356                }
357        }
358        
359        
360        /**
361         * Sets the {@code DPoP} header value.
362         *
363         * @param dPoPJWT The {@code DPoP} header value, {@code null} if not
364         *                specified.
365         */
366        public void setDPoP(final SignedJWT dPoPJWT) {
367        
368                if (dPoPJWT != null) {
369                        setHeader("DPoP", dPoPJWT.serialize());
370                } else {
371                        setHeader("DPoP", (String[]) null);
372                }
373        }
374
375
376        /**
377         * Gets the {@code Accept} header value.
378         *
379         * @return The {@code Accept} header value, {@code null} if not
380         *         specified.
381         */
382        public String getAccept() {
383
384                return getHeaderValue("Accept");
385        }
386
387
388        /**
389         * Sets the {@code Accept} header value.
390         *
391         * @param accept The {@code Accept} header value, {@code null} if not
392         *               specified.
393         */
394        public void setAccept(final String accept) {
395
396                setHeader("Accept", accept);
397        }
398
399
400        /**
401         * Enables debugging of the closing of the HTTP connection streams.
402         *
403         * @param debugCloseStreams If {@code true} disables swallowing of
404         *                          {@link IOException}s when the HTTP
405         *                          connection streams are closed.
406         */
407        void setDebugCloseStreams(final boolean debugCloseStreams) {
408
409                this.debugCloseStreams = debugCloseStreams;
410        }
411
412
413        /**
414         * Appends the specified query parameters to the current HTTP request
415         * {@link #getURL() URL} query.
416         *
417         * <p>If the current URL has a query string the new query is appended
418         * with `&amp;` in front.
419         *
420         * @param queryParams The query parameters to append, empty or
421         *                    {@code null} if nothing to append.
422         *
423         * @throws IllegalArgumentException If the URL composition failed.
424         */
425        public void appendQueryParameters(final Map<String,List<String>> queryParams) {
426
427                if (MapUtils.isEmpty(queryParams)) {
428                        // Nothing to append
429                        return;
430                }
431
432                appendQueryString(URLUtils.serializeParameters(queryParams));
433        }
434        
435        
436        /**
437         * Appends the specified raw (encoded) query string to the current HTTP
438         * request {@link #getURL() URL} query.
439         *
440         * <p>If the current URL has a query string the new query is appended
441         * with `&amp;` in front.
442         *
443         * <p>The '?' character preceding the query string must not be
444         * included.
445         *
446         * <p>Example query string to append:
447         *
448         * <pre>
449         * client_id=123&amp;logout_hint=eepaeph8siot&amp;state=shah2key
450         * </pre>
451         *
452         * @param queryString The query string to append, blank or {@code null}
453         *                    if nothing to append.
454         *
455         * @throws IllegalArgumentException If the URL composition failed.
456         */
457        public void appendQueryString(final String queryString) {
458
459                if (StringUtils.isBlank(queryString)) {
460                        // Nothing to append
461                        return;
462                }
463
464                if (StringUtils.isNotBlank(queryString) && queryString.startsWith("?")) {
465                        throw new IllegalArgumentException("The query string must not start with ?");
466                }
467
468                // Append query string to the URL
469                StringBuilder sb = new StringBuilder();
470
471                if (StringUtils.isNotBlank(url.getQuery())) {
472                        sb.append(url.getQuery());
473                        sb.append('&');
474                }
475                sb.append(queryString);
476
477                url = URLUtils.setEncodedQuery(url, sb.toString());
478        }
479
480
481        /**
482         * Gets the raw (encoded) query string if the request is HTTP GET or
483         * the entity body if the request is HTTP POST.
484         *
485         * <p>Note that the '?' character preceding the query string in GET
486         * requests is not included in the returned string.
487         *
488         * <p>Example query string (line breaks for clarity):
489         *
490         * <pre>
491         * response_type=code
492         * &amp;client_id=s6BhdRkqt3
493         * &amp;state=xyz
494         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
495         * </pre>
496         *
497         * @deprecated Use {@link #getURL()}.
498         *
499         * @return For HTTP GET requests the URL query string, for HTTP POST
500         *         requests the body. {@code null} if not specified.
501         */
502        @Deprecated
503        public String getQuery() {
504
505                // Heuristics for deprecated API
506                return Method.POST.equals(getMethod()) ? getBody() : getURL().getQuery();
507        }
508
509
510        /**
511         * Sets the raw (encoded) query string if the request is HTTP GET or
512         * the entity body if the request is HTTP POST.
513         *
514         * <p>Note that the '?' character preceding the query string in GET
515         * requests must not be included.
516         *
517         * <p>Example query string (line breaks for clarity):
518         *
519         * <pre>
520         * response_type=code
521         * &amp;client_id=s6BhdRkqt3
522         * &amp;state=xyz
523         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
524         * </pre>
525         *
526         * @deprecated Use {@link #appendQueryString(String)}.
527         *
528         * @param query For HTTP GET requests the URL query string, for HTTP
529         *              POST requests the body. {@code null} if not specified.
530         */
531        @Deprecated
532        public void setQuery(final String query) {
533
534                if (Method.POST.equals(getMethod())) {
535                        setBody(query);
536                } else {
537                        appendQueryString(query);
538                }
539        }
540
541
542        /**
543         * Ensures this HTTP response has a specified query string or entity
544         * body.
545         *
546         * @throws ParseException If the query string or entity body is missing
547         *                        or empty.
548         */
549        private void ensureQuery()
550                throws ParseException {
551                
552                if (getQuery() == null || getQuery().trim().isEmpty())
553                        throw new ParseException("Missing or empty HTTP query string / entity body");
554        }
555        
556        
557        /**
558         * Gets the query string as a parameter map. The parameters are decoded
559         * according to {@code application/x-www-form-urlencoded}.
560         *
561         * @return The query string parameters to, decoded. If none the map
562         *         will be empty.
563         */
564        public Map<String,List<String>> getQueryStringParameters() {
565        
566                return URLUtils.parseParameters(url.getQuery());
567        }
568
569
570        /**
571         * Gets the request query as a parameter map. The parameters are
572         * decoded according to {@code application/x-www-form-urlencoded}.
573         *
574         * @deprecated Use {@link #getQueryStringParameters()}.
575         *
576         * @return The request query parameters, decoded. If none the map will
577         *         be empty.
578         */
579        @Deprecated
580        public Map<String,List<String>> getQueryParameters() {
581
582                return URLUtils.parseParameters(getQuery());
583        }
584
585
586        /**
587         * Gets the request query or entity body as a JSON Object.
588         *
589         * @deprecated Use {@link #getBodyAsJSONObject()}.
590         *
591         * @return The request query or entity body as a JSON object.
592         *
593         * @throws ParseException If the Content-Type header isn't 
594         *                        {@code application/json}, the request query
595         *                        or entity body is {@code null}, empty or 
596         *                        couldn't be parsed to a valid JSON object.
597         */
598        @Deprecated
599        public JSONObject getQueryAsJSONObject()
600                throws ParseException {
601
602                ensureEntityContentType(ContentType.APPLICATION_JSON);
603
604                ensureQuery();
605
606                return JSONObjectUtils.parse(getQuery());
607        }
608
609
610        /**
611         * Gets the raw (encoded) fragment of the URL.
612         *
613         * @deprecated Use {@link #getURL()}.
614         *
615         * @return The fragment, {@code null} if not specified.
616         */
617        @Deprecated
618        public String getFragment() {
619
620                return url.getRef();
621        }
622
623
624        /**
625         * Sets the raw (encoded) fragment of the URL.
626         *
627         * @param fragment The fragment, {@code null} if not specified.
628         */
629        public void setFragment(final String fragment) {
630
631                url = URLUtils.setEncodedFragment(url, fragment);
632        }
633
634
635        @Override
636        public int getConnectTimeout() {
637
638                return connectTimeout;
639        }
640
641
642        /**
643         * Sets the HTTP connect timeout.
644         *
645         * @param connectTimeout The HTTP connect timeout, in milliseconds.
646         *                       Zero implies no timeout. Must not be negative.
647         */
648        public void setConnectTimeout(final int connectTimeout) {
649
650                if (connectTimeout < 0) {
651                        throw new IllegalArgumentException("The HTTP connect timeout must be zero or positive");
652                }
653
654                this.connectTimeout = connectTimeout;
655        }
656
657
658        @Override
659        public int getReadTimeout() {
660
661                return readTimeout;
662        }
663
664
665        /**
666         * Sets the HTTP response read timeout.
667         *
668         * @param readTimeout The HTTP response read timeout, in milliseconds.
669         *                    Zero implies no timeout. Must not be negative.
670         */
671        public void setReadTimeout(final int readTimeout) {
672
673                if (readTimeout < 0) {
674                        throw new IllegalArgumentException("The HTTP response read timeout must be zero or positive");
675                }
676
677                this.readTimeout = readTimeout;
678        }
679
680        /**
681         * Returns the proxy to use for this HTTP request.
682         *
683         * @return The connection specific proxy for this request, {@code null}
684         *         for the default proxy strategy.
685         */
686        public Proxy getProxy() {
687                
688                return this.proxy;
689        }
690        
691
692        /**
693         * Tunnels this HTTP request via the specified {@link Proxy} by
694         * directly configuring the proxy on the {@link java.net.URLConnection}.
695         * The proxy is only used for this instance and bypasses any other
696         * proxy settings (such as set via System properties or
697         * {@link java.net.ProxySelector}). Supplying {@code null} (the
698         * default) reverts to the default proxy strategy of
699         * {@link java.net.URLConnection}. If the goal is to avoid using a
700         * proxy at all supply {@link Proxy#NO_PROXY}.
701         *
702         * @param proxy The connection specific proxy to use, {@code null} to
703         *              use the default proxy strategy.
704         *
705         * @see URL#openConnection(Proxy)
706         */
707        public void setProxy(final Proxy proxy) {
708                
709                this.proxy = proxy;
710        }
711        
712
713        /**
714         * Gets the boolean setting whether HTTP redirects (requests with
715         * response code 3xx) should be automatically followed.
716         *
717         * @return {@code true} if HTTP redirects are automatically followed,
718         *         else {@code false}.
719         */
720        public boolean getFollowRedirects() {
721
722                return followRedirects;
723        }
724
725
726        /**
727         * Sets whether HTTP redirects (requests with response code 3xx) should
728         * be automatically followed.
729         *
730         * @param follow {@code true} if HTTP redirects are automatically
731         *               followed, else {@code false}.
732         */
733        public void setFollowRedirects(final boolean follow) {
734
735                followRedirects = follow;
736        }
737        
738        
739        /**
740         * Gets the received validated client X.509 certificate for a received
741         * HTTPS request.
742         *
743         * @return The client X.509 certificate, {@code null} if not specified.
744         */
745        public X509Certificate getClientX509Certificate() {
746                
747                return clientX509Certificate;
748        }
749        
750        
751        /**
752         * Sets the received validated client X.509 certificate for a received
753         * HTTPS request.
754         *
755         * @param clientX509Certificate The client X.509 certificate,
756         *                              {@code null} if not specified.
757         */
758        public void setClientX509Certificate(final X509Certificate clientX509Certificate) {
759                
760                this.clientX509Certificate = clientX509Certificate;
761        }
762        
763        
764        /**
765         * Gets the subject DN of a received validated client X.509 certificate
766         * for a received HTTPS request.
767         *
768         * @return The subject DN, {@code null} if not specified.
769         */
770        public String getClientX509CertificateSubjectDN() {
771                
772                return clientX509CertificateSubjectDN;
773        }
774        
775        
776        /**
777         * Sets the subject DN of a received validated client X.509 certificate
778         * for a received HTTPS request.
779         *
780         * @param subjectDN The subject DN, {@code null} if not specified.
781         */
782        public void setClientX509CertificateSubjectDN(final String subjectDN) {
783                
784                this.clientX509CertificateSubjectDN = subjectDN;
785        }
786        
787        
788        /**
789         * Gets the root issuer DN of a received validated client X.509
790         * certificate for a received HTTPS request.
791         *
792         * @return The root DN, {@code null} if not specified.
793         */
794        public String getClientX509CertificateRootDN() {
795                
796                return clientX509CertificateRootDN;
797        }
798        
799        
800        /**
801         * Sets the root issuer DN of a received validated client X.509
802         * certificate for a received HTTPS request.
803         *
804         * @param rootDN The root DN, {@code null} if not specified.
805         */
806        public void setClientX509CertificateRootDN(final String rootDN) {
807                
808                this.clientX509CertificateRootDN = rootDN;
809        }
810        
811        
812        /**
813         * Gets the hostname verifier for outgoing HTTPS requests.
814         *
815         * @return The hostname verifier, {@code null} implies use of the
816         *         {@link #getDefaultHostnameVerifier() default one}.
817         */
818        public HostnameVerifier getHostnameVerifier() {
819                
820                return hostnameVerifier;
821        }
822        
823        
824        /**
825         * Sets the hostname verifier for outgoing HTTPS requests.
826         *
827         * @param hostnameVerifier The hostname verifier, {@code null} implies
828         *                         use of the
829         *                         {@link #getDefaultHostnameVerifier() default
830         *                         one}.
831         */
832        public void setHostnameVerifier(final HostnameVerifier hostnameVerifier) {
833                
834                this.hostnameVerifier = hostnameVerifier;
835        }
836        
837        
838        /**
839         * Gets the SSL factory for outgoing HTTPS requests.
840         *
841         * @return The SSL factory, {@code null} implies of the default one.
842         */
843        public SSLSocketFactory getSSLSocketFactory() {
844                
845                return sslSocketFactory;
846        }
847        
848        
849        /**
850         * Sets the SSL factory for outgoing HTTPS requests. Use the
851         * {@link com.nimbusds.oauth2.sdk.util.tls.TLSUtils TLS utility} to
852         * set a custom trust store for server and CA certificates and / or a
853         * custom key store for client private keys and certificates, also to
854         * select a specific TLS protocol version.
855         *
856         * @param sslSocketFactory The SSL factory, {@code null} implies use of
857         *                         the default one.
858         */
859        public void setSSLSocketFactory(final SSLSocketFactory sslSocketFactory) {
860                
861                this.sslSocketFactory = sslSocketFactory;
862        }
863        
864        
865        /**
866         * Returns the default hostname verifier for all outgoing HTTPS
867         * requests.
868         *
869         * @return The hostname verifier.
870         */
871        public static HostnameVerifier getDefaultHostnameVerifier() {
872
873                return defaultHostnameVerifier;
874        }
875
876
877        /**
878         * Sets the default hostname verifier for all outgoing HTTPS requests.
879         * Can be overridden on a individual request basis.
880         *
881         * @param defaultHostnameVerifier The hostname verifier. Must not be
882         *                                {@code null}.
883         */
884        public static void setDefaultHostnameVerifier(final HostnameVerifier defaultHostnameVerifier) {
885
886                if (defaultHostnameVerifier == null) {
887                        throw new IllegalArgumentException("The hostname verifier must not be null");
888                }
889
890                HTTPRequest.defaultHostnameVerifier = defaultHostnameVerifier;
891        }
892
893
894        /**
895         * Returns the default SSL socket factory for all outgoing HTTPS
896         * requests.
897         *
898         * @return The SSL socket factory.
899         */
900        public static SSLSocketFactory getDefaultSSLSocketFactory() {
901
902                return defaultSSLSocketFactory;
903        }
904
905
906        /**
907         * Sets the default SSL socket factory for all outgoing HTTPS requests.
908         * Can be overridden on a individual request basis. Use the
909         * {@link com.nimbusds.oauth2.sdk.util.tls.TLSUtils TLS utility} to
910         * set a custom trust store for server and CA certificates and / or a
911         * custom key store for client private keys and certificates, also to
912         * select a specific TLS protocol version.
913         *
914         * @param sslSocketFactory The SSL socket factory. Must not be
915         *                         {@code null}.
916         */
917        public static void setDefaultSSLSocketFactory(final SSLSocketFactory sslSocketFactory) {
918
919                if (sslSocketFactory == null) {
920                        throw new IllegalArgumentException("The SSL socket factory must not be null");
921                }
922
923                HTTPRequest.defaultSSLSocketFactory = sslSocketFactory;
924        }
925
926
927        /**
928         * Returns an established HTTP URL connection for this HTTP request.
929         * Deprecated as of v5.31, use {@link #toHttpURLConnection()} with
930         * {@link #setHostnameVerifier} and {@link #setSSLSocketFactory}
931         * instead.
932         *
933         * @param hostnameVerifier The hostname verifier for outgoing HTTPS
934         *                         requests, {@code null} implies use of the
935         *                         {@link #getDefaultHostnameVerifier() default
936         *                         one}.
937         * @param sslSocketFactory The SSL socket factory for HTTPS requests,
938         *                         {@code null} implies use of the
939         *                         {@link #getDefaultSSLSocketFactory() default
940         *                         one}.
941         *
942         * @return The HTTP URL connection, with the request sent and ready to
943         *         read the response.
944         *
945         * @throws IOException If the HTTP request couldn't be made, due to a
946         *                     network or other error.
947         */
948        @Deprecated
949        public HttpURLConnection toHttpURLConnection(final HostnameVerifier hostnameVerifier,
950                                                     final SSLSocketFactory sslSocketFactory)
951                throws IOException {
952                
953                HostnameVerifier savedHostnameVerifier = getHostnameVerifier();
954                SSLSocketFactory savedSSLFactory = getSSLSocketFactory();
955                
956                try {
957                        // Set for this HTTP URL connection only
958                        setHostnameVerifier(hostnameVerifier);
959                        setSSLSocketFactory(sslSocketFactory);
960                        
961                        return toHttpURLConnection();
962                        
963                } finally {
964                        setHostnameVerifier(savedHostnameVerifier);
965                        setSSLSocketFactory(savedSSLFactory);
966                }
967        }
968
969
970        /**
971         * Returns an established HTTP URL connection for this HTTP request.
972         *
973         * @return The HTTP URL connection, with the request sent and ready to
974         *         read the response.
975         *
976         * @throws IOException If the HTTP request couldn't be made, due to a
977         *                     network or other error.
978         */
979        public HttpURLConnection toHttpURLConnection()
980                throws IOException {
981
982                final URL finalURL = getURL();
983
984                HttpURLConnection conn = (HttpURLConnection) (proxy == null ? finalURL.openConnection() : finalURL.openConnection(proxy));
985
986                if (conn instanceof HttpsURLConnection) {
987                        HttpsURLConnection sslConn = (HttpsURLConnection)conn;
988                        sslConn.setHostnameVerifier(hostnameVerifier != null ? hostnameVerifier : getDefaultHostnameVerifier());
989                        sslConn.setSSLSocketFactory(sslSocketFactory != null ? sslSocketFactory : getDefaultSSLSocketFactory());
990                }
991
992                for (Map.Entry<String,List<String>> header: getHeaderMap().entrySet()) {
993                        for (String headerValue: header.getValue()) {
994                                conn.addRequestProperty(header.getKey(), headerValue);
995                        }
996                }
997
998                conn.setRequestMethod(method.name());
999                conn.setConnectTimeout(connectTimeout);
1000                conn.setReadTimeout(readTimeout);
1001                conn.setInstanceFollowRedirects(followRedirects);
1002
1003                if (method.equals(HTTPRequest.Method.POST) || method.equals(Method.PUT)) {
1004
1005                        conn.setDoOutput(true);
1006
1007                        if (getEntityContentType() != null)
1008                                conn.setRequestProperty("Content-Type", getEntityContentType().toString());
1009
1010                        if (getBody() != null) {
1011                                OutputStream outputStream = null;
1012                                try {
1013                                        outputStream = conn.getOutputStream();
1014                                        OutputStreamWriter writer = new OutputStreamWriter(outputStream);
1015                                        writer.write(getBody());
1016                                        writer.close();
1017                                } catch (IOException e) {
1018                                        closeStreams(conn.getInputStream(), outputStream, conn.getErrorStream(), debugCloseStreams);
1019                                        throw e; // Rethrow
1020                                }
1021                        }
1022                }
1023
1024                return conn;
1025        }
1026
1027
1028        /**
1029         * Sends this HTTP request to the request URL and retrieves the
1030         * resulting HTTP response. Deprecated as of v5.31, use
1031         * {@link #toHttpURLConnection()} with {@link #setHostnameVerifier} and
1032         * {@link #setSSLSocketFactory} instead.
1033         *
1034         * @param hostnameVerifier The hostname verifier for outgoing HTTPS
1035         *                         requests, {@code null} implies use of the
1036         *                         {@link #getDefaultHostnameVerifier() default
1037         *                         one}.
1038         * @param sslSocketFactory The SSL socket factory for HTTPS requests,
1039         *                         {@code null} implies use of the
1040         *                         {@link #getDefaultSSLSocketFactory() default
1041         *                         one}.
1042         *
1043         * @return The resulting HTTP response.
1044         *
1045         * @throws IOException If the HTTP request couldn't be made, due to a
1046         *                     network or other error.
1047         */
1048        @Deprecated
1049        public HTTPResponse send(final HostnameVerifier hostnameVerifier,
1050                                 final SSLSocketFactory sslSocketFactory)
1051                throws IOException {
1052                
1053                HostnameVerifier savedHostnameVerifier = getHostnameVerifier();
1054                SSLSocketFactory savedSSLFactory = getSSLSocketFactory();
1055                
1056                try {
1057                        // Set for this HTTP URL connection only
1058                        setHostnameVerifier(hostnameVerifier);
1059                        setSSLSocketFactory(sslSocketFactory);
1060                        
1061                        return send();
1062                        
1063                } finally {
1064                        setHostnameVerifier(savedHostnameVerifier);
1065                        setSSLSocketFactory(savedSSLFactory);
1066                }
1067        }
1068
1069
1070        /**
1071         * Sends this HTTP request to the {@link #getURL() URL} and retrieves
1072         * the resulting HTTP response.
1073         *
1074         * @return The resulting HTTP response.
1075         *
1076         * @throws IOException If the HTTP request couldn't be sent, due to a
1077         *                     network or another error.
1078         */
1079        public HTTPResponse send()
1080                throws IOException {
1081
1082                HttpURLConnection conn = toHttpURLConnection();
1083
1084                int statusCode;
1085
1086                BufferedReader reader;
1087
1088                InputStream inputStream = null;
1089                InputStream errStream = null;
1090                OutputStream outputStream = null;
1091                try {
1092                        // getOutputStream() can only be retrieved before calling getInputStream()
1093                        if (conn.getDoOutput()) {
1094                                outputStream = conn.getOutputStream();
1095                        }
1096                        // Open a connection, then send method and headers
1097                        inputStream = conn.getInputStream();
1098                        reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
1099
1100                        // The next step is to get the status
1101                        statusCode = conn.getResponseCode();
1102
1103                } catch (IOException e) {
1104
1105                        // HttpUrlConnection will throw an IOException if any
1106                        // 4XX response is sent. If we request the status
1107                        // again, this time the internal status will be
1108                        // properly set, and we'll be able to retrieve it.
1109                        statusCode = conn.getResponseCode();
1110
1111                        if (statusCode == -1) {
1112                                throw e; // Rethrow IO exception
1113                        } else {
1114                                // HTTP status code indicates the response got
1115                                // through, read the content but using error stream
1116                                errStream = conn.getErrorStream();
1117
1118                                if (errStream != null) {
1119                                        // We have useful HTTP error body
1120                                        reader = new BufferedReader(new InputStreamReader(errStream, StandardCharsets.UTF_8));
1121                                } else {
1122                                        // No content, set to empty string
1123                                        reader = new BufferedReader(new StringReader(""));
1124                                }
1125                        }
1126                }
1127
1128                StringBuilder body = new StringBuilder();
1129                String line;
1130                while ((line = reader.readLine()) != null) {
1131                        body.append(line);
1132                        body.append(System.getProperty("line.separator"));
1133                }
1134                reader.close();
1135
1136
1137                HTTPResponse response = new HTTPResponse(statusCode);
1138                
1139                response.setStatusMessage(conn.getResponseMessage());
1140
1141                // Set headers
1142                for (Map.Entry<String,List<String>> responseHeader: conn.getHeaderFields().entrySet()) {
1143
1144                        if (responseHeader.getKey() == null) {
1145                                continue; // skip header
1146                        }
1147
1148                        List<String> values = responseHeader.getValue();
1149                        if (values == null || values.isEmpty() || values.get(0) == null) {
1150                                continue; // skip header
1151                        }
1152
1153                        response.setHeader(responseHeader.getKey(), values.toArray(new String[]{}));
1154                }
1155
1156                closeStreams(inputStream, outputStream, errStream, debugCloseStreams);
1157
1158                final String bodyContent = body.toString();
1159                if (! bodyContent.isEmpty())
1160                        response.setBody(bodyContent);
1161
1162                return response;
1163        }
1164
1165
1166        /**
1167         * Sends this HTTP request to the {@link #getURL() URL} and retrieves
1168         * the resulting HTTP response.
1169         *
1170         * @param httpRequestSender The HTTP request sender. Must not be
1171         *                          {@code null}.
1172         *
1173         * @return The resulting HTTP response.
1174         *
1175         * @throws IOException If the HTTP request couldn't be sent, due to a
1176         *                     network or another error.
1177         */
1178        public HTTPResponse send(final HTTPRequestSender httpRequestSender)
1179                throws IOException {
1180
1181                ReadOnlyHTTPResponse roResponse = httpRequestSender.send(this);
1182
1183                HTTPResponse response = new HTTPResponse(roResponse.getStatusCode());
1184                response.setStatusMessage(roResponse.getStatusMessage());
1185                for (Map.Entry<String, List<String>> en: roResponse.getHeaderMap().entrySet()) {
1186                        if (en.getKey() != null && en.getValue() != null && ! en.getValue().isEmpty()) {
1187                                response.setHeader(en.getKey(), en.getValue().toArray(new String[0]));
1188                        }
1189                }
1190                response.setBody(roResponse.getBody());
1191                return response;
1192        }
1193
1194
1195        /**
1196         * Closes the input, output and error streams of the specified HTTP URL
1197         * connection. No attempt is made to close the underlying socket with
1198         * {@code conn.disconnect} so it may be cached (HTTP 1.1 keep live).
1199         * See http://techblog.bozho.net/caveats-of-httpurlconnection/
1200         */
1201        private static void closeStreams(final InputStream inputStream,
1202                                         final OutputStream outputStream,
1203                                         final InputStream errStream,
1204                                         final boolean debugCloseStreams)
1205                throws IOException {
1206
1207                try {
1208                        if (inputStream != null) {
1209                                inputStream.close();
1210                        }
1211                } catch (IOException e) {
1212                        if (debugCloseStreams) {
1213                                throw e;
1214                        }
1215                } catch (Exception e) {
1216                        // ignore
1217                }
1218
1219                try {
1220                        if (outputStream != null) {
1221                                outputStream.close();
1222                        }
1223                } catch (IOException e) {
1224                        if (debugCloseStreams) {
1225                                throw e;
1226                        }
1227                } catch (Exception e) {
1228                        // ignore
1229                }
1230
1231                try {
1232                        if (errStream != null) {
1233                                errStream.close();
1234                        }
1235                } catch (IOException e) {
1236                        if (debugCloseStreams) {
1237                                throw e;
1238                        }
1239                } catch (Exception e) {
1240                        // ignore
1241                }
1242        }
1243}