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