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.URL;
025import java.nio.charset.Charset;
026
027import net.jcip.annotations.ThreadSafe;
028
029
030/**
031 * The default retriever of resources specified by URL. Provides setting of
032 * HTTP connect and read timeouts as well as a size limit of the retrieved
033 * entity. Caching header directives are not honoured.
034 *
035 * @author Vladimir Dzhuvinov
036 * @version 2018-01-04
037 */
038@ThreadSafe
039public class DefaultResourceRetriever extends AbstractRestrictedResourceRetriever implements RestrictedResourceRetriever {
040        
041        
042        /**
043         * If {@code true} the disconnect method of the underlying
044         * HttpURLConnection is called after a successful or failed retrieval.
045         */
046        private boolean disconnectAfterUse;
047        
048        
049        /**
050         * Creates a new resource retriever. The HTTP timeouts and entity size
051         * limit are set to zero (infinite).
052         */
053        public DefaultResourceRetriever() {
054        
055                this(0, 0);     
056        }
057        
058        
059        /**
060         * Creates a new resource retriever. The HTTP entity size limit is set
061         * to zero (infinite).
062         *
063         * @param connectTimeout The HTTP connects timeout, in milliseconds, 
064         *                       zero for infinite. Must not be negative.
065         * @param readTimeout    The HTTP read timeout, in milliseconds, zero 
066         *                       for infinite. Must not be negative.
067         */
068        public DefaultResourceRetriever(final int connectTimeout, final int readTimeout) {
069
070                this(connectTimeout, readTimeout, 0);
071        }
072
073
074        /**
075         * Creates a new resource retriever.
076         *
077         * @param connectTimeout The HTTP connects timeout, in milliseconds,
078         *                       zero for infinite. Must not be negative.
079         * @param readTimeout    The HTTP read timeout, in milliseconds, zero
080         *                       for infinite. Must not be negative.
081         * @param sizeLimit      The HTTP entity size limit, in bytes, zero for
082         *                       infinite. Must not be negative.
083         */
084        public DefaultResourceRetriever(final int connectTimeout, final int readTimeout, final int sizeLimit) {
085        
086                this(connectTimeout, readTimeout, sizeLimit, true);
087        }
088
089
090        /**
091         * Creates a new resource retriever.
092         *
093         * @param connectTimeout     The HTTP connects timeout, in
094         *                           milliseconds, zero for infinite. Must not
095         *                           be negative.
096         * @param readTimeout        The HTTP read timeout, in milliseconds,
097         *                           zero for infinite. Must not be negative.
098         * @param sizeLimit          The HTTP entity size limit, in bytes, zero
099         *                           for infinite. Must not be negative.
100         * @param disconnectAfterUse If {@code true} the disconnect method of
101         *                           the underlying {@link HttpURLConnection}
102         *                           will be called after trying to retrieve
103         *                           the resource. Whether the TCP socket is
104         *                           actually closed or reused depends on the
105         *                           underlying HTTP implementation and the
106         *                           setting of the {@code keep.alive} system
107         *                           property.
108         */
109        public DefaultResourceRetriever(final int connectTimeout,
110                                        final int readTimeout,
111                                        final int sizeLimit,
112                                        final boolean disconnectAfterUse) {
113        
114                super(connectTimeout, readTimeout, sizeLimit);
115                this.disconnectAfterUse = disconnectAfterUse;
116        }
117        
118        
119        /**
120         * Returns {@code true} if the disconnect method of the underlying
121         * {@link HttpURLConnection} will be called after trying to retrieve
122         * the resource. Whether the TCP socket is actually closed or reused
123         * depends on the underlying HTTP implementation and the setting of the
124         * {@code keep.alive} system property.
125         *
126         * @return If {@code true} the disconnect method of the underlying
127         *         {@link HttpURLConnection} will be called after trying to
128         *         retrieve the resource.
129         */
130        public boolean disconnectsAfterUse() {
131                
132                return disconnectAfterUse;
133        }
134        
135        
136        /**
137         * Controls calling of the disconnect method the underlying
138         * {@link HttpURLConnection} after trying to retrieve the resource.
139         * Whether the TCP socket is actually closed or reused depends on the
140         * underlying HTTP implementation and the setting of the
141         * {@code keep.alive} system property.
142         *
143         * @return If {@code true} the disconnect method of the underlying
144         *         {@link HttpURLConnection} will be called after trying to
145         *         retrieve the resource.
146         */
147        public void setDisconnectsAfterUse(final boolean disconnectAfterUse) {
148                
149                this.disconnectAfterUse = disconnectAfterUse;
150        }
151
152
153        @Override
154        public Resource retrieveResource(final URL url)
155                throws IOException {
156                
157                HttpURLConnection con = null;
158                try {
159                        con = (HttpURLConnection)url.openConnection();
160                        
161                        con.setConnectTimeout(getConnectTimeout());
162                        con.setReadTimeout(getReadTimeout());
163                        
164                        final String content;
165                        
166                        InputStream inputStream = con.getInputStream();
167                        try {
168                                if (getSizeLimit() > 0) {
169                                        inputStream = new BoundedInputStream(inputStream, getSizeLimit());
170                                }
171                                
172                                content = IOUtils.readInputStreamToString(inputStream, Charset.forName("UTF-8"));
173                                
174                        } finally {
175                                inputStream.close();
176                        }
177        
178                        // Check HTTP code + message
179                        final int statusCode = con.getResponseCode();
180                        final String statusMessage = con.getResponseMessage();
181        
182                        // Ensure 2xx status code
183                        if (statusCode > 299 || statusCode < 200) {
184                                throw new IOException("HTTP " + statusCode + ": " + statusMessage);
185                        }
186        
187                        return new Resource(content, con.getContentType());
188                
189                } catch (ClassCastException e) {
190                        throw new IOException("Couldn't open HTTP(S) connection: " + e.getMessage(), e);
191                } finally {
192                        if (disconnectAfterUse && con != null) {
193                                con.disconnect();
194                        }
195                }
196        }
197}