001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2016, Connect2id Ltd.
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.jose.util;
019
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.net.HttpURLConnection;
024import java.net.Proxy;
025import java.net.URL;
026import java.nio.charset.StandardCharsets;
027
028import net.jcip.annotations.ThreadSafe;
029
030
031/**
032 * The default retriever of resources specified by URL. Provides setting of a
033 * HTTP proxy, HTTP connect and read timeouts as well as a size limit of the
034 * retrieved entity. Caching header directives are not honoured.
035 *
036 * @author Vladimir Dzhuvinov
037 * @author Artun Subasi
038 * @version 2019-08-23
039 */
040@ThreadSafe
041public class DefaultResourceRetriever extends AbstractRestrictedResourceRetriever implements RestrictedResourceRetriever {
042        
043        
044        /**
045         * If {@code true} the disconnect method of the underlying
046         * HttpURLConnection is called after a successful or failed retrieval.
047         */
048        private boolean disconnectAfterUse;
049
050        
051        /**
052         * The proxy to use when opening the HttpURLConnection. Can be
053         * {@code null}.
054         */
055        private Proxy proxy;
056        
057        
058        /**
059         * Creates a new resource retriever. The HTTP timeouts and entity size
060         * limit are set to zero (infinite).
061         */
062        public DefaultResourceRetriever() {
063        
064                this(0, 0);     
065        }
066        
067        
068        /**
069         * Creates a new resource retriever. The HTTP entity size limit is set
070         * to zero (infinite).
071         *
072         * @param connectTimeout The HTTP connects timeout, in milliseconds, 
073         *                       zero for infinite. Must not be negative.
074         * @param readTimeout    The HTTP read timeout, in milliseconds, zero 
075         *                       for infinite. Must not be negative.
076         */
077        public DefaultResourceRetriever(final int connectTimeout, final int readTimeout) {
078
079                this(connectTimeout, readTimeout, 0);
080        }
081
082
083        /**
084         * Creates a new resource retriever.
085         *
086         * @param connectTimeout The HTTP connects timeout, in milliseconds,
087         *                       zero for infinite. Must not be negative.
088         * @param readTimeout    The HTTP read timeout, in milliseconds, zero
089         *                       for infinite. Must not be negative.
090         * @param sizeLimit      The HTTP entity size limit, in bytes, zero for
091         *                       infinite. Must not be negative.
092         */
093        public DefaultResourceRetriever(final int connectTimeout, final int readTimeout, final int sizeLimit) {
094        
095                this(connectTimeout, readTimeout, sizeLimit, true);
096        }
097
098
099        /**
100         * Creates a new resource retriever.
101         *
102         * @param connectTimeout     The HTTP connects timeout, in
103         *                           milliseconds, zero for infinite. Must not
104         *                           be negative.
105         * @param readTimeout        The HTTP read timeout, in milliseconds,
106         *                           zero for infinite. Must not be negative.
107         * @param sizeLimit          The HTTP entity size limit, in bytes, zero
108         *                           for infinite. Must not be negative.
109         * @param disconnectAfterUse If {@code true} the disconnect method of
110         *                           the underlying {@link HttpURLConnection}
111         *                           will be called after trying to retrieve
112         *                           the resource. Whether the TCP socket is
113         *                           actually closed or reused depends on the
114         *                           underlying HTTP implementation and the
115         *                           setting of the {@code keep.alive} system
116         *                           property.
117         */
118        public DefaultResourceRetriever(final int connectTimeout,
119                                        final int readTimeout,
120                                        final int sizeLimit,
121                                        final boolean disconnectAfterUse) {
122        
123                super(connectTimeout, readTimeout, sizeLimit);
124                this.disconnectAfterUse = disconnectAfterUse;
125        }
126        
127        
128        /**
129         * Returns {@code true} if the disconnect method of the underlying
130         * {@link HttpURLConnection} will be called after trying to retrieve
131         * the resource. Whether the TCP socket is actually closed or reused
132         * depends on the underlying HTTP implementation and the setting of the
133         * {@code keep.alive} system property.
134         *
135         * @return If {@code true} the disconnect method of the underlying
136         *         {@link HttpURLConnection} will be called after trying to
137         *         retrieve the resource.
138         */
139        public boolean disconnectsAfterUse() {
140                
141                return disconnectAfterUse;
142        }
143        
144        
145        /**
146         * Controls calling of the disconnect method the underlying
147         * {@link HttpURLConnection} after trying to retrieve the resource.
148         * Whether the TCP socket is actually closed or reused depends on the
149         * underlying HTTP implementation and the setting of the
150         * {@code keep.alive} system property.
151         *
152         * If {@code true} the disconnect method of the underlying
153         * {@link HttpURLConnection} will be called after trying to
154         * retrieve the resource.
155         */
156        public void setDisconnectsAfterUse(final boolean disconnectAfterUse) {
157                
158                this.disconnectAfterUse = disconnectAfterUse;
159        }
160
161        /**
162         * Returns the HTTP proxy to use when opening the HttpURLConnection to
163         * retrieve the resource. Note that the JVM may have a system wide
164         * proxy configured via the {@code https.proxyHost} Java system
165         * property.
166         *
167         * @return The proxy to use or {@code null} if no proxy should be used.
168         */
169        public Proxy getProxy() {
170                
171                return proxy;
172        }
173
174        /**
175         * Sets the HTTP proxy to use when opening the HttpURLConnection to
176         * retrieve the resource. Note that the JVM may have a system wide
177         * proxy configured via the {@code https.proxyHost} Java system
178         * property.
179         *
180         * @param proxy The proxy to use or {@code null} if no proxy should be
181         *              used.
182         */
183        public void setProxy(final Proxy proxy) {
184                
185                this.proxy = proxy;
186        }
187
188        
189        @Override
190        public Resource retrieveResource(final URL url)
191                throws IOException {
192                
193                HttpURLConnection con = null;
194                try {
195                        con = openConnection(url);
196                        
197                        con.setConnectTimeout(getConnectTimeout());
198                        con.setReadTimeout(getReadTimeout());
199                        
200                        final String content;
201                        try (InputStream inputStream = getInputStream(con, getSizeLimit())) {
202                                content = IOUtils.readInputStreamToString(inputStream, StandardCharsets.UTF_8);
203                        }
204        
205                        // Check HTTP code + message
206                        final int statusCode = con.getResponseCode();
207                        final String statusMessage = con.getResponseMessage();
208        
209                        // Ensure 2xx status code
210                        if (statusCode > 299 || statusCode < 200) {
211                                throw new IOException("HTTP " + statusCode + ": " + statusMessage);
212                        }
213        
214                        return new Resource(content, con.getContentType());
215                
216                } catch (ClassCastException e) {
217                        throw new IOException("Couldn't open HTTP(S) connection: " + e.getMessage(), e);
218                } finally {
219                        if (disconnectAfterUse && con != null) {
220                                con.disconnect();
221                        }
222                }
223        }
224
225        /**
226         * Opens a connection the specified HTTP(S) URL. Uses the configured
227         * {@link Proxy} if available.
228         *
229         * @param url The URL of the resource. Its scheme must be HTTP or
230         *            HTTPS. Must not be {@code null}.
231         *
232         * @return The opened HTTP(S) connection
233         *
234         * @throws IOException If the HTTP(S) connection to the specified URL
235         *                     failed.
236         */
237        protected HttpURLConnection openConnection(final URL url) throws IOException {
238                if (proxy != null) {
239                        return (HttpURLConnection)url.openConnection(proxy);
240                } else {
241                        return (HttpURLConnection)url.openConnection();
242                }
243        }
244
245        
246        private InputStream getInputStream(final HttpURLConnection con, final int sizeLimit)
247                throws IOException {
248                
249                InputStream inputStream = con.getInputStream();
250                
251                return sizeLimit > 0 ? new BoundedInputStream(inputStream, getSizeLimit()) : inputStream;
252        }
253}