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