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                setHeader("DPoP", dPoPJWT.serialize());
351        }
352
353
354        /**
355         * Gets the {@code Accept} header value.
356         *
357         * @return The {@code Accept} header value, {@code null} if not
358         *         specified.
359         */
360        public String getAccept() {
361
362                return getHeaderValue("Accept");
363        }
364
365
366        /**
367         * Sets the {@code Accept} header value.
368         *
369         * @param accept The {@code Accept} header value, {@code null} if not
370         *               specified.
371         */
372        public void setAccept(final String accept) {
373
374                setHeader("Accept", accept);
375        }
376        
377        
378        /**
379         * Gets the raw (undecoded) query string if the request is HTTP GET or
380         * the entity body if the request is HTTP POST.
381         *
382         * <p>Note that the '?' character preceding the query string in GET
383         * requests is not included in the returned string.
384         *
385         * <p>Example query string (line breaks for clarity):
386         *
387         * <pre>
388         * response_type=code
389         * &amp;client_id=s6BhdRkqt3
390         * &amp;state=xyz
391         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
392         * </pre>
393         *
394         * @return For HTTP GET requests the URL query string, for HTTP POST 
395         *         requests the body. {@code null} if not specified.
396         */
397        public String getQuery() {
398        
399                return query;
400        }
401        
402        
403        /**
404         * Sets the raw (undecoded) query string if the request is HTTP GET or
405         * the entity body if the request is HTTP POST.
406         *
407         * <p>Note that the '?' character preceding the query string in GET
408         * requests must not be included.
409         *
410         * <p>Example query string (line breaks for clarity):
411         *
412         * <pre>
413         * response_type=code
414         * &amp;client_id=s6BhdRkqt3
415         * &amp;state=xyz
416         * &amp;redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
417         * </pre>
418         *
419         * @param query For HTTP GET requests the URL query string, for HTTP 
420         *              POST requests the body. {@code null} if not specified.
421         */
422        public void setQuery(final String query) {
423        
424                this.query = query;
425        }
426
427
428        /**
429         * Ensures this HTTP response has a specified query string or entity
430         * body.
431         *
432         * @throws ParseException If the query string or entity body is missing
433         *                        or empty.
434         */
435        private void ensureQuery()
436                throws ParseException {
437                
438                if (query == null || query.trim().isEmpty())
439                        throw new ParseException("Missing or empty HTTP query string / entity body");
440        }
441        
442        
443        /**
444         * Gets the request query as a parameter map. The parameters are 
445         * decoded according to {@code application/x-www-form-urlencoded}.
446         *
447         * @return The request query parameters, decoded. If none the map will
448         *         be empty.
449         */
450        public Map<String,List<String>> getQueryParameters() {
451        
452                return URLUtils.parseParameters(query);
453        }
454
455
456        /**
457         * Gets the request query or entity body as a JSON Object.
458         *
459         * @return The request query or entity body as a JSON object.
460         *
461         * @throws ParseException If the Content-Type header isn't 
462         *                        {@code application/json}, the request query
463         *                        or entity body is {@code null}, empty or 
464         *                        couldn't be parsed to a valid JSON object.
465         */
466        public JSONObject getQueryAsJSONObject()
467                throws ParseException {
468
469                ensureEntityContentType(ContentType.APPLICATION_JSON);
470
471                ensureQuery();
472
473                return JSONObjectUtils.parse(query);
474        }
475
476
477        /**
478         * Gets the raw (undecoded) request fragment.
479         *
480         * @return The request fragment, {@code null} if not specified.
481         */
482        public String getFragment() {
483
484                return fragment;
485        }
486
487
488        /**
489         * Sets the raw (undecoded) request fragment.
490         *
491         * @param fragment The request fragment, {@code null} if not specified.
492         */
493        public void setFragment(final String fragment) {
494
495                this.fragment = fragment;
496        }
497
498
499        /**
500         * Gets the HTTP connect timeout.
501         *
502         * @return The HTTP connect timeout, in milliseconds. Zero implies no
503         *         timeout.
504         */
505        public int getConnectTimeout() {
506
507                return connectTimeout;
508        }
509
510
511        /**
512         * Sets the HTTP connect timeout.
513         *
514         * @param connectTimeout The HTTP connect timeout, in milliseconds.
515         *                       Zero implies no timeout. Must not be negative.
516         */
517        public void setConnectTimeout(final int connectTimeout) {
518
519                if (connectTimeout < 0) {
520                        throw new IllegalArgumentException("The HTTP connect timeout must be zero or positive");
521                }
522
523                this.connectTimeout = connectTimeout;
524        }
525
526
527        /**
528         * Gets the HTTP response read timeout.
529         *
530         * @return The HTTP response read timeout, in milliseconds. Zero
531         *         implies no timeout.
532         */
533        public int getReadTimeout() {
534
535                return readTimeout;
536        }
537
538
539        /**
540         * Sets the HTTP response read timeout.
541         *
542         * @param readTimeout The HTTP response read timeout, in milliseconds.
543         *                    Zero implies no timeout. Must not be negative.
544         */
545        public void setReadTimeout(final int readTimeout) {
546
547                if (readTimeout < 0) {
548                        throw new IllegalArgumentException("The HTTP response read timeout must be zero or positive");
549                }
550
551                this.readTimeout = readTimeout;
552        }
553
554        /**
555         * Returns the proxy to use for this HTTP request.
556         *
557         * @return The connection specific proxy for this request, {@code null}
558         *         for the default proxy strategy.
559         */
560        public Proxy getProxy() {
561                
562                return this.proxy;
563        }
564        
565
566        /**
567         * Tunnels this HTTP request via the specified {@link Proxy} by
568         * directly configuring the proxy on the {@link java.net.URLConnection}.
569         * The proxy is only used for this instance and bypasses any other
570         * proxy settings (such as set via System properties or
571         * {@link java.net.ProxySelector}). Supplying {@code null} (the
572         * default) reverts to the default proxy strategy of
573         * {@link java.net.URLConnection}. If the goal is to avoid using a
574         * proxy at all supply {@link Proxy#NO_PROXY}.
575         *
576         * @param proxy The connection specific proxy to use, {@code null} to
577         *              use the default proxy strategy.
578         *
579         * @see URL#openConnection(Proxy)
580         */
581        public void setProxy(final Proxy proxy) {
582                
583                this.proxy = proxy;
584        }
585        
586
587        /**
588         * Gets the boolean setting whether HTTP redirects (requests with
589         * response code 3xx) should be automatically followed.
590         *
591         * @return {@code true} if HTTP redirects are automatically followed,
592         *         else {@code false}.
593         */
594        public boolean getFollowRedirects() {
595
596                return followRedirects;
597        }
598
599
600        /**
601         * Sets whether HTTP redirects (requests with response code 3xx) should
602         * be automatically followed.
603         *
604         * @param follow Whether or not to follow HTTP redirects.
605         */
606        public void setFollowRedirects(final boolean follow) {
607
608                followRedirects = follow;
609        }
610        
611        
612        /**
613         * Gets the received validated client X.509 certificate for a received
614         * HTTPS request.
615         *
616         * @return The client X.509 certificate, {@code null} if not specified.
617         */
618        public X509Certificate getClientX509Certificate() {
619                
620                return clientX509Certificate;
621        }
622        
623        
624        /**
625         * Sets the received validated client X.509 certificate for a received
626         * HTTPS request.
627         *
628         * @param clientX509Certificate The client X.509 certificate,
629         *                              {@code null} if not specified.
630         */
631        public void setClientX509Certificate(final X509Certificate clientX509Certificate) {
632                
633                this.clientX509Certificate = clientX509Certificate;
634        }
635        
636        
637        /**
638         * Gets the subject DN of a received validated client X.509 certificate
639         * for a received HTTPS request.
640         *
641         * @return The subject DN, {@code null} if not specified.
642         */
643        public String getClientX509CertificateSubjectDN() {
644                
645                return clientX509CertificateSubjectDN;
646        }
647        
648        
649        /**
650         * Sets the subject DN of a received validated client X.509 certificate
651         * for a received HTTPS request.
652         *
653         * @param subjectDN The subject DN, {@code null} if not specified.
654         */
655        public void setClientX509CertificateSubjectDN(final String subjectDN) {
656                
657                this.clientX509CertificateSubjectDN = subjectDN;
658        }
659        
660        
661        /**
662         * Gets the root issuer DN of a received validated client X.509
663         * certificate for a received HTTPS request.
664         *
665         * @return The root DN, {@code null} if not specified.
666         */
667        public String getClientX509CertificateRootDN() {
668                
669                return clientX509CertificateRootDN;
670        }
671        
672        
673        /**
674         * Sets the root issuer DN of a received validated client X.509
675         * certificate for a received HTTPS request.
676         *
677         * @param rootDN The root DN, {@code null} if not specified.
678         */
679        public void setClientX509CertificateRootDN(final String rootDN) {
680                
681                this.clientX509CertificateRootDN = rootDN;
682        }
683        
684        
685        /**
686         * Gets the hostname verifier for outgoing HTTPS requests.
687         *
688         * @return The hostname verifier, {@code null} implies use of the
689         *         {@link #getDefaultHostnameVerifier() default one}.
690         */
691        public HostnameVerifier getHostnameVerifier() {
692                
693                return hostnameVerifier;
694        }
695        
696        
697        /**
698         * Sets the hostname verifier for outgoing HTTPS requests.
699         *
700         * @param hostnameVerifier The hostname verifier, {@code null} implies
701         *                         use of the
702         *                         {@link #getDefaultHostnameVerifier() default
703         *                         one}.
704         */
705        public void setHostnameVerifier(final HostnameVerifier hostnameVerifier) {
706                
707                this.hostnameVerifier = hostnameVerifier;
708        }
709        
710        
711        /**
712         * Gets the SSL factory for outgoing HTTPS requests.
713         *
714         * @return The SSL factory, {@code null} implies of the default one.
715         */
716        public SSLSocketFactory getSSLSocketFactory() {
717                
718                return sslSocketFactory;
719        }
720        
721        
722        /**
723         * Sets the SSL factory for outgoing HTTPS requests. Use the
724         * {@link com.nimbusds.oauth2.sdk.util.tls.TLSUtils TLS utility} to
725         * set a custom trust store for server and CA certificates and / or a
726         * custom key store for client private keys and certificates, also to
727         * select a specific TLS protocol version.
728         *
729         * @param sslSocketFactory The SSL factory, {@code null} implies use of
730         *                         the default one.
731         */
732        public void setSSLSocketFactory(final SSLSocketFactory sslSocketFactory) {
733                
734                this.sslSocketFactory = sslSocketFactory;
735        }
736        
737        
738        /**
739         * Returns the default hostname verifier for all outgoing HTTPS
740         * requests.
741         *
742         * @return The hostname verifier.
743         */
744        public static HostnameVerifier getDefaultHostnameVerifier() {
745
746                return defaultHostnameVerifier;
747        }
748
749
750        /**
751         * Sets the default hostname verifier for all outgoing HTTPS requests.
752         * Can be overridden on a individual request basis.
753         *
754         * @param defaultHostnameVerifier The hostname verifier. Must not be
755         *                                {@code null}.
756         */
757        public static void setDefaultHostnameVerifier(final HostnameVerifier defaultHostnameVerifier) {
758
759                if (defaultHostnameVerifier == null) {
760                        throw new IllegalArgumentException("The hostname verifier must not be null");
761                }
762
763                HTTPRequest.defaultHostnameVerifier = defaultHostnameVerifier;
764        }
765
766
767        /**
768         * Returns the default SSL socket factory for all outgoing HTTPS
769         * requests.
770         *
771         * @return The SSL socket factory.
772         */
773        public static SSLSocketFactory getDefaultSSLSocketFactory() {
774
775                return defaultSSLSocketFactory;
776        }
777
778
779        /**
780         * Sets the default SSL socket factory for all outgoing HTTPS requests.
781         * Can be overridden on a individual request basis. Use the
782         * {@link com.nimbusds.oauth2.sdk.util.tls.TLSUtils TLS utility} to
783         * set a custom trust store for server and CA certificates and / or a
784         * custom key store for client private keys and certificates, also to
785         * select a specific TLS protocol version.
786         *
787         * @param sslSocketFactory The SSL socket factory. Must not be
788         *                         {@code null}.
789         */
790        public static void setDefaultSSLSocketFactory(final SSLSocketFactory sslSocketFactory) {
791
792                if (sslSocketFactory == null) {
793                        throw new IllegalArgumentException("The SSL socket factory must not be null");
794                }
795
796                HTTPRequest.defaultSSLSocketFactory = sslSocketFactory;
797        }
798
799
800        /**
801         * Returns an established HTTP URL connection for this HTTP request.
802         * Deprecated as of v5.31, use {@link #toHttpURLConnection()} with
803         * {@link #setHostnameVerifier} and {@link #setSSLSocketFactory}
804         * instead.
805         *
806         * @param hostnameVerifier The hostname verifier for outgoing HTTPS
807         *                         requests, {@code null} implies use of the
808         *                         {@link #getDefaultHostnameVerifier() default
809         *                         one}.
810         * @param sslSocketFactory The SSL socket factory for HTTPS requests,
811         *                         {@code null} implies use of the
812         *                         {@link #getDefaultSSLSocketFactory() default
813         *                         one}.
814         *
815         * @return The HTTP URL connection, with the request sent and ready to
816         *         read the response.
817         *
818         * @throws IOException If the HTTP request couldn't be made, due to a
819         *                     network or other error.
820         */
821        @Deprecated
822        public HttpURLConnection toHttpURLConnection(final HostnameVerifier hostnameVerifier,
823                                                     final SSLSocketFactory sslSocketFactory)
824                throws IOException {
825                
826                HostnameVerifier savedHostnameVerifier = getHostnameVerifier();
827                SSLSocketFactory savedSSLFactory = getSSLSocketFactory();
828                
829                try {
830                        // Set for this HTTP URL connection only
831                        setHostnameVerifier(hostnameVerifier);
832                        setSSLSocketFactory(sslSocketFactory);
833                        
834                        return toHttpURLConnection();
835                        
836                } finally {
837                        setHostnameVerifier(savedHostnameVerifier);
838                        setSSLSocketFactory(savedSSLFactory);
839                }
840        }
841
842
843        /**
844         * Returns an established HTTP URL connection for this HTTP request.
845         *
846         * @return The HTTP URL connection, with the request sent and ready to
847         *         read the response.
848         *
849         * @throws IOException If the HTTP request couldn't be made, due to a
850         *                     network or other error.
851         */
852        public HttpURLConnection toHttpURLConnection()
853                throws IOException {
854
855                URL finalURL = url;
856
857                if (query != null && (method.equals(HTTPRequest.Method.GET) || method.equals(Method.DELETE))) {
858
859                        // Append query string
860                        StringBuilder sb = new StringBuilder(url.toString());
861                        sb.append('?');
862                        sb.append(query);
863
864                        try {
865                                finalURL = new URL(sb.toString());
866
867                        } catch (MalformedURLException e) {
868
869                                throw new IOException("Couldn't append query string: " + e.getMessage(), e);
870                        }
871                }
872
873                if (fragment != null) {
874
875                        // Append raw fragment
876                        StringBuilder sb = new StringBuilder(finalURL.toString());
877                        sb.append('#');
878                        sb.append(fragment);
879
880                        try {
881                                finalURL = new URL(sb.toString());
882
883                        } catch (MalformedURLException e) {
884
885                                throw new IOException("Couldn't append raw fragment: " + e.getMessage(), e);
886                        }
887                }
888
889                HttpURLConnection conn = (HttpURLConnection) (proxy == null ? finalURL.openConnection() : finalURL.openConnection(proxy));
890
891                if (conn instanceof HttpsURLConnection) {
892                        HttpsURLConnection sslConn = (HttpsURLConnection)conn;
893                        sslConn.setHostnameVerifier(hostnameVerifier != null ? hostnameVerifier : getDefaultHostnameVerifier());
894                        sslConn.setSSLSocketFactory(sslSocketFactory != null ? sslSocketFactory : getDefaultSSLSocketFactory());
895                }
896
897                for (Map.Entry<String,List<String>> header: getHeaderMap().entrySet()) {
898                        for (String headerValue: header.getValue()) {
899                                conn.addRequestProperty(header.getKey(), headerValue);
900                        }
901                }
902
903                conn.setRequestMethod(method.name());
904                conn.setConnectTimeout(connectTimeout);
905                conn.setReadTimeout(readTimeout);
906                conn.setInstanceFollowRedirects(followRedirects);
907
908                if (method.equals(HTTPRequest.Method.POST) || method.equals(Method.PUT)) {
909
910                        conn.setDoOutput(true);
911
912                        if (getEntityContentType() != null)
913                                conn.setRequestProperty("Content-Type", getEntityContentType().toString());
914
915                        if (query != null) {
916                                try {
917                                        OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream());
918                                        writer.write(query);
919                                        writer.close();
920                                } catch (IOException e) {
921                                        closeStreams(conn);
922                                        throw e; // Rethrow
923                                }
924                        }
925                }
926
927                return conn;
928        }
929
930
931        /**
932         * Sends this HTTP request to the request URL and retrieves the
933         * resulting HTTP response. Deprecated as of v5.31, use
934         * {@link #toHttpURLConnection()} with {@link #setHostnameVerifier} and
935         * {@link #setSSLSocketFactory} instead.
936         *
937         * @param hostnameVerifier The hostname verifier for outgoing HTTPS
938         *                         requests, {@code null} implies use of the
939         *                         {@link #getDefaultHostnameVerifier() default
940         *                         one}.
941         * @param sslSocketFactory The SSL socket factory for HTTPS requests,
942         *                         {@code null} implies use of the
943         *                         {@link #getDefaultSSLSocketFactory() default
944         *                         one}.
945         *
946         * @return The resulting HTTP response.
947         *
948         * @throws IOException If the HTTP request couldn't be made, due to a
949         *                     network or other error.
950         */
951        @Deprecated
952        public HTTPResponse send(final HostnameVerifier hostnameVerifier,
953                                 final SSLSocketFactory sslSocketFactory)
954                throws IOException {
955                
956                HostnameVerifier savedHostnameVerifier = getHostnameVerifier();
957                SSLSocketFactory savedSSLFactory = getSSLSocketFactory();
958                
959                try {
960                        // Set for this HTTP URL connection only
961                        setHostnameVerifier(hostnameVerifier);
962                        setSSLSocketFactory(sslSocketFactory);
963                        
964                        return send();
965                        
966                } finally {
967                        setHostnameVerifier(savedHostnameVerifier);
968                        setSSLSocketFactory(savedSSLFactory);
969                }
970        }
971
972
973        /**
974         * Sends this HTTP request to the request URL and retrieves the
975         * resulting HTTP response.
976         *
977         * @return The resulting HTTP response.
978         *
979         * @throws IOException If the HTTP request couldn't be made, due to a
980         *                     network or other error.
981         */
982        public HTTPResponse send()
983                throws IOException {
984
985                HttpURLConnection conn = toHttpURLConnection();
986
987                int statusCode;
988
989                BufferedReader reader;
990
991                try {
992                        // Open a connection, then send method and headers
993                        reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
994
995                        // The next step is to get the status
996                        statusCode = conn.getResponseCode();
997
998                } catch (IOException e) {
999
1000                        // HttpUrlConnection will throw an IOException if any
1001                        // 4XX response is sent. If we request the status
1002                        // again, this time the internal status will be
1003                        // properly set, and we'll be able to retrieve it.
1004                        statusCode = conn.getResponseCode();
1005
1006                        if (statusCode == -1) {
1007                                throw e; // Rethrow IO exception
1008                        } else {
1009                                // HTTP status code indicates the response got
1010                                // through, read the content but using error stream
1011                                InputStream errStream = conn.getErrorStream();
1012
1013                                if (errStream != null) {
1014                                        // We have useful HTTP error body
1015                                        reader = new BufferedReader(new InputStreamReader(errStream));
1016                                } else {
1017                                        // No content, set to empty string
1018                                        reader = new BufferedReader(new StringReader(""));
1019                                }
1020                        }
1021                }
1022
1023                StringBuilder body = new StringBuilder();
1024                String line;
1025                while ((line = reader.readLine()) != null) {
1026                        body.append(line);
1027                        body.append(System.getProperty("line.separator"));
1028                }
1029                reader.close();
1030
1031
1032                HTTPResponse response = new HTTPResponse(statusCode);
1033                
1034                response.setStatusMessage(conn.getResponseMessage());
1035
1036                // Set headers
1037                for (Map.Entry<String,List<String>> responseHeader: conn.getHeaderFields().entrySet()) {
1038
1039                        if (responseHeader.getKey() == null) {
1040                                continue; // skip header
1041                        }
1042
1043                        List<String> values = responseHeader.getValue();
1044                        if (values == null || values.isEmpty() || values.get(0) == null) {
1045                                continue; // skip header
1046                        }
1047
1048                        response.setHeader(responseHeader.getKey(), values.toArray(new String[]{}));
1049                }
1050
1051                closeStreams(conn);
1052
1053                final String bodyContent = body.toString();
1054                if (! bodyContent.isEmpty())
1055                        response.setContent(bodyContent);
1056
1057                return response;
1058        }
1059
1060
1061        /**
1062         * Closes the input, output and error streams of the specified HTTP URL
1063         * connection. No attempt is made to close the underlying socket with
1064         * {@code conn.disconnect} so it may be cached (HTTP 1.1 keep live).
1065         * See http://techblog.bozho.net/caveats-of-httpurlconnection/
1066         *
1067         * @param conn The HTTP URL connection. May be {@code null}.
1068         */
1069        private static void closeStreams(final HttpURLConnection conn) {
1070
1071                if (conn == null) {
1072                        return;
1073                }
1074
1075                try {
1076                        if (conn.getInputStream() != null) {
1077                                conn.getInputStream().close();
1078                        }
1079                } catch (Exception e) {
1080                        // ignore
1081                }
1082
1083                try {
1084                        if (conn.getOutputStream() != null) {
1085                                conn.getOutputStream().close();
1086                        }
1087                } catch (Exception e) {
1088                        // ignore
1089                }
1090
1091                try {
1092                        if (conn.getErrorStream() != null) {
1093                                conn.getOutputStream().close();
1094                        }
1095                } catch (Exception e) {
1096                        // ignore
1097                }
1098        }
1099}