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}