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.net.URLConnection; 027import java.util.List; 028import java.util.Map; 029import javax.net.ssl.HttpsURLConnection; 030import javax.net.ssl.SSLSocketFactory; 031 032import net.jcip.annotations.ThreadSafe; 033 034 035/** 036 * The default retriever of resources specified by HTTP(S) or file based URL. 037 * Provides setting of a HTTP proxy, HTTP connect and read timeouts as well as 038 * a size limit of the retrieved entity. Caching header directives are not 039 * honoured. 040 * 041 * @author Vladimir Dzhuvinov 042 * @author Artun Subasi 043 * @author Imre Paladji 044 * @version 2022-04-07 045 */ 046@ThreadSafe 047public class DefaultResourceRetriever extends AbstractRestrictedResourceRetriever implements RestrictedResourceRetriever { 048 049 050 /** 051 * If {@code true} the disconnect method of the underlying 052 * HttpURLConnection is called after a successful or failed retrieval. 053 */ 054 private boolean disconnectAfterUse; 055 056 057 /** 058 * For establishing the TLS connections, {@code null} to use the 059 * default one. 060 */ 061 private final SSLSocketFactory sslSocketFactory; 062 063 064 /** 065 * The proxy to use when opening the HttpURLConnection. Can be 066 * {@code null}. 067 */ 068 private Proxy proxy; 069 070 071 /** 072 * Creates a new resource retriever. The HTTP timeouts and entity size 073 * limit are set to zero (infinite). 074 */ 075 public DefaultResourceRetriever() { 076 077 this(0, 0); 078 } 079 080 081 /** 082 * Creates a new resource retriever. The HTTP entity size limit is set 083 * to zero (infinite). 084 * 085 * @param connectTimeout The HTTP connects timeout, in milliseconds, 086 * zero for infinite. Must not be negative. 087 * @param readTimeout The HTTP read timeout, in milliseconds, zero 088 * for infinite. Must not be negative. 089 */ 090 public DefaultResourceRetriever(final int connectTimeout, final int readTimeout) { 091 092 this(connectTimeout, readTimeout, 0); 093 } 094 095 096 /** 097 * Creates a new resource retriever. 098 * 099 * @param connectTimeout The HTTP connects timeout, in milliseconds, 100 * zero for infinite. Must not be negative. 101 * @param readTimeout The HTTP read timeout, in milliseconds, zero 102 * for infinite. Must not be negative. 103 * @param sizeLimit The HTTP entity size limit, in bytes, zero for 104 * infinite. Must not be negative. 105 */ 106 public DefaultResourceRetriever(final int connectTimeout, final int readTimeout, final int sizeLimit) { 107 108 this(connectTimeout, readTimeout, sizeLimit, true); 109 } 110 111 112 /** 113 * Creates a new resource retriever. 114 * 115 * @param connectTimeout The HTTP connects timeout, in 116 * milliseconds, zero for infinite. Must not 117 * be negative. 118 * @param readTimeout The HTTP read timeout, in milliseconds, 119 * zero for infinite. Must not be negative. 120 * @param sizeLimit The HTTP entity size limit, in bytes, zero 121 * for infinite. Must not be negative. 122 * @param disconnectAfterUse If {@code true} the disconnect method of 123 * the underlying {@link HttpURLConnection} 124 * will be called after trying to retrieve 125 * the resource. Whether the TCP socket is 126 * actually closed or reused depends on the 127 * underlying HTTP implementation and the 128 * setting of the {@code keep.alive} system 129 * property. 130 */ 131 public DefaultResourceRetriever(final int connectTimeout, 132 final int readTimeout, 133 final int sizeLimit, 134 final boolean disconnectAfterUse) { 135 136 this(connectTimeout, readTimeout, sizeLimit, disconnectAfterUse, null); 137 } 138 139 140 /** 141 * Creates a new resource retriever. 142 * 143 * @param connectTimeout The HTTP connects timeout, in 144 * milliseconds, zero for infinite. Must not 145 * be negative. 146 * @param readTimeout The HTTP read timeout, in milliseconds, 147 * zero for infinite. Must not be negative. 148 * @param sizeLimit The HTTP entity size limit, in bytes, zero 149 * for infinite. Must not be negative. 150 * @param disconnectAfterUse If {@code true} the disconnect method of 151 * the underlying {@link HttpURLConnection} 152 * will be called after trying to retrieve 153 * the resource. Whether the TCP socket is 154 * actually closed or reused depends on the 155 * underlying HTTP implementation and the 156 * setting of the {@code keep.alive} system 157 * property. 158 * @param sslSocketFactory An SSLSocketFactory for establishing the 159 * TLS connections, {@code null} to use the 160 * default one. 161 */ 162 public DefaultResourceRetriever(final int connectTimeout, 163 final int readTimeout, 164 final int sizeLimit, 165 final boolean disconnectAfterUse, 166 final SSLSocketFactory sslSocketFactory) { 167 super(connectTimeout, readTimeout, sizeLimit); 168 this.disconnectAfterUse = disconnectAfterUse; 169 this.sslSocketFactory = sslSocketFactory; 170 } 171 172 173 /** 174 * Returns {@code true} if the disconnect method of the underlying 175 * {@link HttpURLConnection} will be called after trying to retrieve 176 * the resource. Whether the TCP socket is actually closed or reused 177 * depends on the underlying HTTP implementation and the setting of the 178 * {@code keep.alive} system property. 179 * 180 * @return If {@code true} the disconnect method of the underlying 181 * {@link HttpURLConnection} will be called after trying to 182 * retrieve the resource. 183 */ 184 public boolean disconnectsAfterUse() { 185 186 return disconnectAfterUse; 187 } 188 189 190 /** 191 * Controls calling of the disconnect method the underlying 192 * {@link HttpURLConnection} after trying to retrieve the resource. 193 * Whether the TCP socket is actually closed or reused depends on the 194 * underlying HTTP implementation and the setting of the 195 * {@code keep.alive} system property. 196 * 197 * If {@code true} the disconnect method of the underlying 198 * {@link HttpURLConnection} will be called after trying to 199 * retrieve the resource. 200 */ 201 public void setDisconnectsAfterUse(final boolean disconnectAfterUse) { 202 203 this.disconnectAfterUse = disconnectAfterUse; 204 } 205 206 /** 207 * Returns the HTTP proxy to use when opening the HttpURLConnection to 208 * retrieve the resource. Note that the JVM may have a system wide 209 * proxy configured via the {@code https.proxyHost} Java system 210 * property. 211 * 212 * @return The proxy to use or {@code null} if no proxy should be used. 213 */ 214 public Proxy getProxy() { 215 216 return proxy; 217 } 218 219 /** 220 * Sets the HTTP proxy to use when opening the HttpURLConnection to 221 * retrieve the resource. Note that the JVM may have a system wide 222 * proxy configured via the {@code https.proxyHost} Java system 223 * property. 224 * 225 * @param proxy The proxy to use or {@code null} if no proxy should be 226 * used. 227 */ 228 public void setProxy(final Proxy proxy) { 229 230 this.proxy = proxy; 231 } 232 233 234 @Override 235 public Resource retrieveResource(final URL url) 236 throws IOException { 237 238 URLConnection con = null; 239 try { 240 if ("file".equals(url.getProtocol())) { 241 con = openFileConnection(url); 242 } else { 243 // TODO Switch to openHTTPConnected when the 244 // deprecated openConnection method is removed 245 con = openConnection(url); 246 } 247 248 con.setConnectTimeout(getConnectTimeout()); 249 con.setReadTimeout(getReadTimeout()); 250 251 if (con instanceof HttpsURLConnection && sslSocketFactory != null) { 252 ((HttpsURLConnection)con).setSSLSocketFactory(sslSocketFactory); 253 } 254 255 if (con instanceof HttpURLConnection && getHeaders() != null && !getHeaders().isEmpty()) { 256 for (Map.Entry<String, List<String>> entry : getHeaders().entrySet()) { 257 for (String value: entry.getValue()) { 258 con.addRequestProperty(entry.getKey(), value); 259 } 260 } 261 } 262 263 final String content; 264 try (InputStream inputStream = getInputStream(con, getSizeLimit())) { 265 content = IOUtils.readInputStreamToString(inputStream, StandardCharset.UTF_8); 266 } 267 268 if (con instanceof HttpURLConnection) { 269 // Check HTTP code + message 270 HttpURLConnection httpCon = (HttpURLConnection) con; 271 final int statusCode = httpCon.getResponseCode(); 272 final String statusMessage = httpCon.getResponseMessage(); 273 274 // Ensure 2xx status code 275 if (statusCode > 299 || statusCode < 200) { 276 throw new IOException("HTTP " + statusCode + ": " + statusMessage); 277 } 278 } 279 280 String contentType = con instanceof HttpURLConnection ? con.getContentType() : null; 281 282 return new Resource(content, contentType); 283 284 } catch (Exception e) { 285 286 if (e instanceof IOException) { 287 throw e; 288 } 289 290 throw new IOException("Couldn't open URL connection: " + e.getMessage(), e); 291 292 } finally { 293 if (disconnectAfterUse && con instanceof HttpURLConnection) { 294 ((HttpURLConnection) con).disconnect(); 295 } 296 } 297 } 298 299 300 /** 301 * Opens a connection the specified HTTP(S) URL. Uses the configured 302 * {@link Proxy} if available. 303 * 304 * @see #openHTTPConnection 305 * 306 * @param url The URL of the resource. Its scheme must be HTTP or 307 * HTTPS. Must not be {@code null}. 308 * 309 * @return The opened HTTP(S) connection 310 * 311 * @throws IOException If the HTTP(S) connection to the specified URL 312 * failed. 313 */ 314 @Deprecated 315 protected HttpURLConnection openConnection(final URL url) throws IOException { 316 return openHTTPConnection(url); 317 } 318 319 320 /** 321 * Opens a connection the specified HTTP(S) URL. Uses the configured 322 * {@link Proxy} if available. 323 * 324 * @param url The URL of the resource. Its scheme must be "http" or 325 * "https". Must not be {@code null}. 326 * 327 * @return The opened HTTP(S) connection 328 * 329 * @throws IOException If the HTTP(S) connection to the specified URL 330 * failed. 331 */ 332 protected HttpURLConnection openHTTPConnection(final URL url) throws IOException { 333 if (proxy != null) { 334 return (HttpURLConnection)url.openConnection(proxy); 335 } else { 336 return (HttpURLConnection)url.openConnection(); 337 } 338 } 339 340 341 /** 342 * Opens a connection the specified file URL. 343 * 344 * @param url The URL of the resource. Its scheme must be "file". Must 345 * not be {@code null}. 346 * 347 * @return The opened file connection. 348 * 349 * @throws IOException If the file connection to the specified URL 350 * failed. 351 */ 352 protected URLConnection openFileConnection(final URL url) throws IOException { 353 354 return url.openConnection(); 355 } 356 357 358 private InputStream getInputStream(final URLConnection con, final int sizeLimit) 359 throws IOException { 360 361 InputStream inputStream = con.getInputStream(); 362 363 return sizeLimit > 0 ? new BoundedInputStream(inputStream, getSizeLimit()) : inputStream; 364 } 365}