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