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