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