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 or PUT.
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         *         and PUT 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()) || Method.PUT.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 or PUT.
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 and PUT requests the body. {@code null} if not
530         *              specified.
531         */
532        @Deprecated
533        public void setQuery(final String query) {
534
535                if (Method.POST.equals(getMethod()) || Method.PUT.equals(getMethod())) {
536                        setBody(query);
537                } else {
538                        appendQueryString(query);
539                }
540        }
541
542
543        /**
544         * Ensures this HTTP response has a specified query string or entity
545         * body.
546         *
547         * @throws ParseException If the query string or entity body is missing
548         *                        or empty.
549         */
550        private void ensureQuery()
551                throws ParseException {
552                
553                if (getQuery() == null || getQuery().trim().isEmpty())
554                        throw new ParseException("Missing or empty HTTP query string / entity body");
555        }
556        
557        
558        /**
559         * Gets the query string as a parameter map. The parameters are decoded
560         * according to {@code application/x-www-form-urlencoded}.
561         *
562         * @return The query string parameters to, decoded. If none the map
563         *         will be empty.
564         */
565        public Map<String,List<String>> getQueryStringParameters() {
566        
567                return URLUtils.parseParameters(url.getQuery());
568        }
569
570
571        /**
572         * Gets the request query as a parameter map. The parameters are
573         * decoded according to {@code application/x-www-form-urlencoded}.
574         *
575         * @deprecated Use {@link #getQueryStringParameters()}.
576         *
577         * @return The request query parameters, decoded. If none the map will
578         *         be empty.
579         */
580        @Deprecated
581        public Map<String,List<String>> getQueryParameters() {
582
583                return URLUtils.parseParameters(getQuery());
584        }
585
586
587        /**
588         * Gets the request query or entity body as a JSON Object.
589         *
590         * @deprecated Use {@link #getBodyAsJSONObject()}.
591         *
592         * @return The request query or entity body as a JSON object.
593         *
594         * @throws ParseException If the Content-Type header isn't 
595         *                        {@code application/json}, the request query
596         *                        or entity body is {@code null}, empty or 
597         *                        couldn't be parsed to a valid JSON object.
598         */
599        @Deprecated
600        public JSONObject getQueryAsJSONObject()
601                throws ParseException {
602
603                ensureEntityContentType(ContentType.APPLICATION_JSON);
604
605                ensureQuery();
606
607                return JSONObjectUtils.parse(getQuery());
608        }
609
610
611        /**
612         * Gets the raw (encoded) fragment of the URL.
613         *
614         * @deprecated Use {@link #getURL()}.
615         *
616         * @return The fragment, {@code null} if not specified.
617         */
618        @Deprecated
619        public String getFragment() {
620
621                return url.getRef();
622        }
623
624
625        /**
626         * Sets the raw (encoded) fragment of the URL.
627         *
628         * @param fragment The fragment, {@code null} if not specified.
629         */
630        public void setFragment(final String fragment) {
631
632                url = URLUtils.setEncodedFragment(url, fragment);
633        }
634
635
636        @Override
637        public int getConnectTimeout() {
638
639                return connectTimeout;
640        }
641
642
643        /**
644         * Sets the HTTP connect timeout.
645         *
646         * @param connectTimeout The HTTP connect timeout, in milliseconds.
647         *                       Zero implies no timeout. Must not be negative.
648         */
649        public void setConnectTimeout(final int connectTimeout) {
650
651                if (connectTimeout < 0) {
652                        throw new IllegalArgumentException("The HTTP connect timeout must be zero or positive");
653                }
654
655                this.connectTimeout = connectTimeout;
656        }
657
658
659        @Override
660        public int getReadTimeout() {
661
662                return readTimeout;
663        }
664
665
666        /**
667         * Sets the HTTP response read timeout.
668         *
669         * @param readTimeout The HTTP response read timeout, in milliseconds.
670         *                    Zero implies no timeout. Must not be negative.
671         */
672        public void setReadTimeout(final int readTimeout) {
673
674                if (readTimeout < 0) {
675                        throw new IllegalArgumentException("The HTTP response read timeout must be zero or positive");
676                }
677
678                this.readTimeout = readTimeout;
679        }
680
681        /**
682         * Returns the proxy to use for this HTTP request.
683         *
684         * @return The connection specific proxy for this request, {@code null}
685         *         for the default proxy strategy.
686         */
687        public Proxy getProxy() {
688                
689                return this.proxy;
690        }
691        
692
693        /**
694         * Tunnels this HTTP request via the specified {@link Proxy} by
695         * directly configuring the proxy on the {@link java.net.URLConnection}.
696         * The proxy is only used for this instance and bypasses any other
697         * proxy settings (such as set via System properties or
698         * {@link java.net.ProxySelector}). Supplying {@code null} (the
699         * default) reverts to the default proxy strategy of
700         * {@link java.net.URLConnection}. If the goal is to avoid using a
701         * proxy at all supply {@link Proxy#NO_PROXY}.
702         *
703         * @param proxy The connection specific proxy to use, {@code null} to
704         *              use the default proxy strategy.
705         *
706         * @see URL#openConnection(Proxy)
707         */
708        public void setProxy(final Proxy proxy) {
709                
710                this.proxy = proxy;
711        }
712        
713
714        /**
715         * Gets the boolean setting whether HTTP redirects (requests with
716         * response code 3xx) should be automatically followed.
717         *
718         * @return {@code true} if HTTP redirects are automatically followed,
719         *         else {@code false}.
720         */
721        public boolean getFollowRedirects() {
722
723                return followRedirects;
724        }
725
726
727        /**
728         * Sets whether HTTP redirects (requests with response code 3xx) should
729         * be automatically followed.
730         *
731         * @param follow {@code true} if HTTP redirects are automatically
732         *               followed, else {@code false}.
733         */
734        public void setFollowRedirects(final boolean follow) {
735
736                followRedirects = follow;
737        }
738        
739        
740        /**
741         * Gets the received validated client X.509 certificate for a received
742         * HTTPS request.
743         *
744         * @return The client X.509 certificate, {@code null} if not specified.
745         */
746        public X509Certificate getClientX509Certificate() {
747                
748                return clientX509Certificate;
749        }
750        
751        
752        /**
753         * Sets the received validated client X.509 certificate for a received
754         * HTTPS request.
755         *
756         * @param clientX509Certificate The client X.509 certificate,
757         *                              {@code null} if not specified.
758         */
759        public void setClientX509Certificate(final X509Certificate clientX509Certificate) {
760                
761                this.clientX509Certificate = clientX509Certificate;
762        }
763        
764        
765        /**
766         * Gets the subject DN of a received validated client X.509 certificate
767         * for a received HTTPS request.
768         *
769         * @return The subject DN, {@code null} if not specified.
770         */
771        public String getClientX509CertificateSubjectDN() {
772                
773                return clientX509CertificateSubjectDN;
774        }
775        
776        
777        /**
778         * Sets the subject DN of a received validated client X.509 certificate
779         * for a received HTTPS request.
780         *
781         * @param subjectDN The subject DN, {@code null} if not specified.
782         */
783        public void setClientX509CertificateSubjectDN(final String subjectDN) {
784                
785                this.clientX509CertificateSubjectDN = subjectDN;
786        }
787        
788        
789        /**
790         * Gets the root issuer DN of a received validated client X.509
791         * certificate for a received HTTPS request.
792         *
793         * @return The root DN, {@code null} if not specified.
794         */
795        public String getClientX509CertificateRootDN() {
796                
797                return clientX509CertificateRootDN;
798        }
799        
800        
801        /**
802         * Sets the root issuer DN of a received validated client X.509
803         * certificate for a received HTTPS request.
804         *
805         * @param rootDN The root DN, {@code null} if not specified.
806         */
807        public void setClientX509CertificateRootDN(final String rootDN) {
808                
809                this.clientX509CertificateRootDN = rootDN;
810        }
811        
812        
813        /**
814         * Gets the hostname verifier for outgoing HTTPS requests.
815         *
816         * @return The hostname verifier, {@code null} implies use of the
817         *         {@link #getDefaultHostnameVerifier() default one}.
818         */
819        public HostnameVerifier getHostnameVerifier() {
820                
821                return hostnameVerifier;
822        }
823        
824        
825        /**
826         * Sets the hostname verifier for outgoing HTTPS requests.
827         *
828         * @param hostnameVerifier The hostname verifier, {@code null} implies
829         *                         use of the
830         *                         {@link #getDefaultHostnameVerifier() default
831         *                         one}.
832         */
833        public void setHostnameVerifier(final HostnameVerifier hostnameVerifier) {
834                
835                this.hostnameVerifier = hostnameVerifier;
836        }
837        
838        
839        /**
840         * Gets the SSL factory for outgoing HTTPS requests.
841         *
842         * @return The SSL factory, {@code null} implies of the default one.
843         */
844        public SSLSocketFactory getSSLSocketFactory() {
845                
846                return sslSocketFactory;
847        }
848        
849        
850        /**
851         * Sets the SSL factory for outgoing HTTPS requests. Use the
852         * {@link com.nimbusds.oauth2.sdk.util.tls.TLSUtils TLS utility} to
853         * set a custom trust store for server and CA certificates and / or a
854         * custom key store for client private keys and certificates, also to
855         * select a specific TLS protocol version.
856         *
857         * @param sslSocketFactory The SSL factory, {@code null} implies use of
858         *                         the default one.
859         */
860        public void setSSLSocketFactory(final SSLSocketFactory sslSocketFactory) {
861                
862                this.sslSocketFactory = sslSocketFactory;
863        }
864        
865        
866        /**
867         * Returns the default hostname verifier for all outgoing HTTPS
868         * requests.
869         *
870         * @return The hostname verifier.
871         */
872        public static HostnameVerifier getDefaultHostnameVerifier() {
873
874                return defaultHostnameVerifier;
875        }
876
877
878        /**
879         * Sets the default hostname verifier for all outgoing HTTPS requests.
880         * Can be overridden on a individual request basis.
881         *
882         * @param defaultHostnameVerifier The hostname verifier. Must not be
883         *                                {@code null}.
884         */
885        public static void setDefaultHostnameVerifier(final HostnameVerifier defaultHostnameVerifier) {
886
887                if (defaultHostnameVerifier == null) {
888                        throw new IllegalArgumentException("The hostname verifier must not be null");
889                }
890
891                HTTPRequest.defaultHostnameVerifier = defaultHostnameVerifier;
892        }
893
894
895        /**
896         * Returns the default SSL socket factory for all outgoing HTTPS
897         * requests.
898         *
899         * @return The SSL socket factory.
900         */
901        public static SSLSocketFactory getDefaultSSLSocketFactory() {
902
903                return defaultSSLSocketFactory;
904        }
905
906
907        /**
908         * Sets the default SSL socket factory for all outgoing HTTPS requests.
909         * Can be overridden on a individual request basis. Use the
910         * {@link com.nimbusds.oauth2.sdk.util.tls.TLSUtils TLS utility} to
911         * set a custom trust store for server and CA certificates and / or a
912         * custom key store for client private keys and certificates, also to
913         * select a specific TLS protocol version.
914         *
915         * @param sslSocketFactory The SSL socket factory. Must not be
916         *                         {@code null}.
917         */
918        public static void setDefaultSSLSocketFactory(final SSLSocketFactory sslSocketFactory) {
919
920                if (sslSocketFactory == null) {
921                        throw new IllegalArgumentException("The SSL socket factory must not be null");
922                }
923
924                HTTPRequest.defaultSSLSocketFactory = sslSocketFactory;
925        }
926
927
928        /**
929         * Returns an established HTTP URL connection for this HTTP request.
930         * Deprecated as of v5.31, use {@link #toHttpURLConnection()} with
931         * {@link #setHostnameVerifier} and {@link #setSSLSocketFactory}
932         * instead.
933         *
934         * @param hostnameVerifier The hostname verifier for outgoing HTTPS
935         *                         requests, {@code null} implies use of the
936         *                         {@link #getDefaultHostnameVerifier() default
937         *                         one}.
938         * @param sslSocketFactory The SSL socket factory for HTTPS requests,
939         *                         {@code null} implies use of the
940         *                         {@link #getDefaultSSLSocketFactory() default
941         *                         one}.
942         *
943         * @return The HTTP URL connection, with the request sent and ready to
944         *         read the response.
945         *
946         * @throws IOException If the HTTP request couldn't be made, due to a
947         *                     network or other error.
948         */
949        @Deprecated
950        public HttpURLConnection toHttpURLConnection(final HostnameVerifier hostnameVerifier,
951                                                     final SSLSocketFactory sslSocketFactory)
952                throws IOException {
953                
954                HostnameVerifier savedHostnameVerifier = getHostnameVerifier();
955                SSLSocketFactory savedSSLFactory = getSSLSocketFactory();
956                
957                try {
958                        // Set for this HTTP URL connection only
959                        setHostnameVerifier(hostnameVerifier);
960                        setSSLSocketFactory(sslSocketFactory);
961                        
962                        return toHttpURLConnection();
963                        
964                } finally {
965                        setHostnameVerifier(savedHostnameVerifier);
966                        setSSLSocketFactory(savedSSLFactory);
967                }
968        }
969
970
971        /**
972         * Returns an established HTTP URL connection for this HTTP request.
973         *
974         * @return The HTTP URL connection, with the request sent and ready to
975         *         read the response.
976         *
977         * @throws IOException If the HTTP request couldn't be made, due to a
978         *                     network or other error.
979         */
980        public HttpURLConnection toHttpURLConnection()
981                throws IOException {
982
983                final URL finalURL = getURL();
984
985                HttpURLConnection conn = (HttpURLConnection) (proxy == null ? finalURL.openConnection() : finalURL.openConnection(proxy));
986
987                if (conn instanceof HttpsURLConnection) {
988                        HttpsURLConnection sslConn = (HttpsURLConnection)conn;
989                        sslConn.setHostnameVerifier(hostnameVerifier != null ? hostnameVerifier : getDefaultHostnameVerifier());
990                        sslConn.setSSLSocketFactory(sslSocketFactory != null ? sslSocketFactory : getDefaultSSLSocketFactory());
991                }
992
993                for (Map.Entry<String,List<String>> header: getHeaderMap().entrySet()) {
994                        for (String headerValue: header.getValue()) {
995                                conn.addRequestProperty(header.getKey(), headerValue);
996                        }
997                }
998
999                conn.setRequestMethod(method.name());
1000                conn.setConnectTimeout(connectTimeout);
1001                conn.setReadTimeout(readTimeout);
1002                conn.setInstanceFollowRedirects(followRedirects);
1003
1004                if (method.equals(HTTPRequest.Method.POST) || method.equals(Method.PUT)) {
1005
1006                        conn.setDoOutput(true);
1007
1008                        if (getEntityContentType() != null)
1009                                conn.setRequestProperty("Content-Type", getEntityContentType().toString());
1010
1011                        if (getBody() != null) {
1012                                OutputStream outputStream = null;
1013                                try {
1014                                        outputStream = conn.getOutputStream();
1015                                        OutputStreamWriter writer = new OutputStreamWriter(outputStream);
1016                                        writer.write(getBody());
1017                                        writer.close();
1018                                } catch (IOException e) {
1019                                        closeStreams(conn.getInputStream(), outputStream, conn.getErrorStream(), debugCloseStreams);
1020                                        throw e; // Rethrow
1021                                }
1022                        }
1023                }
1024
1025                return conn;
1026        }
1027
1028
1029        /**
1030         * Sends this HTTP request to the request URL and retrieves the
1031         * resulting HTTP response. Deprecated as of v5.31, use
1032         * {@link #toHttpURLConnection()} with {@link #setHostnameVerifier} and
1033         * {@link #setSSLSocketFactory} instead.
1034         *
1035         * @param hostnameVerifier The hostname verifier for outgoing HTTPS
1036         *                         requests, {@code null} implies use of the
1037         *                         {@link #getDefaultHostnameVerifier() default
1038         *                         one}.
1039         * @param sslSocketFactory The SSL socket factory for HTTPS requests,
1040         *                         {@code null} implies use of the
1041         *                         {@link #getDefaultSSLSocketFactory() default
1042         *                         one}.
1043         *
1044         * @return The resulting HTTP response.
1045         *
1046         * @throws IOException If the HTTP request couldn't be made, due to a
1047         *                     network or other error.
1048         */
1049        @Deprecated
1050        public HTTPResponse send(final HostnameVerifier hostnameVerifier,
1051                                 final SSLSocketFactory sslSocketFactory)
1052                throws IOException {
1053                
1054                HostnameVerifier savedHostnameVerifier = getHostnameVerifier();
1055                SSLSocketFactory savedSSLFactory = getSSLSocketFactory();
1056                
1057                try {
1058                        // Set for this HTTP URL connection only
1059                        setHostnameVerifier(hostnameVerifier);
1060                        setSSLSocketFactory(sslSocketFactory);
1061                        
1062                        return send();
1063                        
1064                } finally {
1065                        setHostnameVerifier(savedHostnameVerifier);
1066                        setSSLSocketFactory(savedSSLFactory);
1067                }
1068        }
1069
1070
1071        /**
1072         * Sends this HTTP request to the {@link #getURL() URL} and retrieves
1073         * the resulting HTTP response.
1074         *
1075         * @return The resulting HTTP response.
1076         *
1077         * @throws IOException If the HTTP request couldn't be sent, due to a
1078         *                     network or another error.
1079         */
1080        public HTTPResponse send()
1081                throws IOException {
1082
1083                HttpURLConnection conn = toHttpURLConnection();
1084
1085                int statusCode;
1086
1087                BufferedReader reader;
1088
1089                InputStream inputStream = null;
1090                InputStream errStream = null;
1091                OutputStream outputStream = null;
1092                try {
1093                        // getOutputStream() can only be retrieved before calling getInputStream()
1094                        if (conn.getDoOutput()) {
1095                                outputStream = conn.getOutputStream();
1096                        }
1097                        // Open a connection, then send method and headers
1098                        inputStream = conn.getInputStream();
1099                        reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
1100
1101                        // The next step is to get the status
1102                        statusCode = conn.getResponseCode();
1103
1104                } catch (IOException e) {
1105
1106                        // HttpUrlConnection will throw an IOException if any
1107                        // 4XX response is sent. If we request the status
1108                        // again, this time the internal status will be
1109                        // properly set, and we'll be able to retrieve it.
1110                        statusCode = conn.getResponseCode();
1111
1112                        if (statusCode == -1) {
1113                                throw e; // Rethrow IO exception
1114                        } else {
1115                                // HTTP status code indicates the response got
1116                                // through, read the content but using error stream
1117                                errStream = conn.getErrorStream();
1118
1119                                if (errStream != null) {
1120                                        // We have useful HTTP error body
1121                                        reader = new BufferedReader(new InputStreamReader(errStream, StandardCharsets.UTF_8));
1122                                } else {
1123                                        // No content, set to empty string
1124                                        reader = new BufferedReader(new StringReader(""));
1125                                }
1126                        }
1127                }
1128
1129                StringBuilder body = new StringBuilder();
1130                String line;
1131                while ((line = reader.readLine()) != null) {
1132                        body.append(line);
1133                        body.append(System.getProperty("line.separator"));
1134                }
1135                reader.close();
1136
1137
1138                HTTPResponse response = new HTTPResponse(statusCode);
1139                
1140                response.setStatusMessage(conn.getResponseMessage());
1141
1142                // Set headers
1143                for (Map.Entry<String,List<String>> responseHeader: conn.getHeaderFields().entrySet()) {
1144
1145                        if (responseHeader.getKey() == null) {
1146                                continue; // skip header
1147                        }
1148
1149                        List<String> values = responseHeader.getValue();
1150                        if (values == null || values.isEmpty() || values.get(0) == null) {
1151                                continue; // skip header
1152                        }
1153
1154                        response.setHeader(responseHeader.getKey(), values.toArray(new String[]{}));
1155                }
1156
1157                closeStreams(inputStream, outputStream, errStream, debugCloseStreams);
1158
1159                final String bodyContent = body.toString();
1160                if (! bodyContent.isEmpty())
1161                        response.setBody(bodyContent);
1162
1163                return response;
1164        }
1165
1166
1167        /**
1168         * Sends this HTTP request to the {@link #getURL() URL} and retrieves
1169         * the resulting HTTP response.
1170         *
1171         * @param httpRequestSender The HTTP request sender. Must not be
1172         *                          {@code null}.
1173         *
1174         * @return The resulting HTTP response.
1175         *
1176         * @throws IOException If the HTTP request couldn't be sent, due to a
1177         *                     network or another error.
1178         */
1179        public HTTPResponse send(final HTTPRequestSender httpRequestSender)
1180                throws IOException {
1181
1182                ReadOnlyHTTPResponse roResponse = httpRequestSender.send(this);
1183
1184                HTTPResponse response = new HTTPResponse(roResponse.getStatusCode());
1185                response.setStatusMessage(roResponse.getStatusMessage());
1186                for (Map.Entry<String, List<String>> en: roResponse.getHeaderMap().entrySet()) {
1187                        if (en.getKey() != null && en.getValue() != null && ! en.getValue().isEmpty()) {
1188                                response.setHeader(en.getKey(), en.getValue().toArray(new String[0]));
1189                        }
1190                }
1191                response.setBody(roResponse.getBody());
1192                return response;
1193        }
1194
1195
1196        /**
1197         * Closes the input, output and error streams of the specified HTTP URL
1198         * connection. No attempt is made to close the underlying socket with
1199         * {@code conn.disconnect} so it may be cached (HTTP 1.1 keep live).
1200         * See http://techblog.bozho.net/caveats-of-httpurlconnection/
1201         */
1202        private static void closeStreams(final InputStream inputStream,
1203                                         final OutputStream outputStream,
1204                                         final InputStream errStream,
1205                                         final boolean debugCloseStreams)
1206                throws IOException {
1207
1208                try {
1209                        if (inputStream != null) {
1210                                inputStream.close();
1211                        }
1212                } catch (IOException e) {
1213                        if (debugCloseStreams) {
1214                                throw e;
1215                        }
1216                } catch (Exception e) {
1217                        // ignore
1218                }
1219
1220                try {
1221                        if (outputStream != null) {
1222                                outputStream.close();
1223                        }
1224                } catch (IOException e) {
1225                        if (debugCloseStreams) {
1226                                throw e;
1227                        }
1228                } catch (Exception e) {
1229                        // ignore
1230                }
1231
1232                try {
1233                        if (errStream != null) {
1234                                errStream.close();
1235                        }
1236                } catch (IOException e) {
1237                        if (debugCloseStreams) {
1238                                throw e;
1239                        }
1240                } catch (Exception e) {
1241                        // ignore
1242                }
1243        }
1244}