001/* 002 * oauth2-oidc-sdk 003 * 004 * Copyright 2012-2016, Connect2id Ltd and contributors. 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.oauth2.sdk.http; 019 020 021import java.io.BufferedReader; 022import java.io.IOException; 023import java.io.PrintWriter; 024import java.net.MalformedURLException; 025import java.net.URL; 026import java.util.Enumeration; 027import java.util.Map; 028import javax.servlet.http.HttpServletRequest; 029import javax.servlet.http.HttpServletResponse; 030 031import com.nimbusds.oauth2.sdk.ParseException; 032import com.nimbusds.oauth2.sdk.util.URLUtils; 033import net.jcip.annotations.ThreadSafe; 034 035 036/** 037 * HTTP servlet utilities. 038 */ 039@ThreadSafe 040public class ServletUtils { 041 042 043 /** 044 * Reconstructs the request URL string for the specified servlet 045 * request. The host part is always the local IP address. The query 046 * string and fragment is always omitted. 047 * 048 * @param request The servlet request. Must not be {@code null}. 049 * 050 * @return The reconstructed request URL string. 051 */ 052 private static String reconstructRequestURLString(final HttpServletRequest request) { 053 054 StringBuilder sb = new StringBuilder("http"); 055 056 if (request.isSecure()) 057 sb.append('s'); 058 059 sb.append("://"); 060 061 String localAddress = request.getLocalAddr(); 062 063 if (localAddress.contains(".")) { 064 // IPv3 address 065 sb.append(localAddress); 066 } else if (localAddress.contains(":")) { 067 // IPv6 address, see RFC 2732 068 sb.append('['); 069 sb.append(localAddress); 070 sb.append(']'); 071 } else { 072 // Don't know what to do 073 } 074 075 if (! request.isSecure() && request.getLocalPort() != 80) { 076 // HTTP plain at port other than 80 077 sb.append(':'); 078 sb.append(request.getLocalPort()); 079 } 080 081 if (request.isSecure() && request.getLocalPort() != 443) { 082 // HTTPS at port other than 443 (default TLS) 083 sb.append(':'); 084 sb.append(request.getLocalPort()); 085 } 086 087 String path = request.getRequestURI(); 088 089 if (path != null) 090 sb.append(path); 091 092 return sb.toString(); 093 } 094 095 096 /** 097 * Creates a new HTTP request from the specified HTTP servlet request. 098 * 099 * <p><strong>Warning about servlet filters: </strong> Processing of 100 * HTTP POST and PUT requests requires the entity body to be available 101 * for reading from the {@link HttpServletRequest}. If you're getting 102 * unexpected exceptions, please ensure the entity body is not consumed 103 * or modified by an upstream servlet filter. 104 * 105 * @param sr The servlet request. Must not be {@code null}. 106 * 107 * @return The HTTP request. 108 * 109 * @throws IllegalArgumentException The the servlet request method is 110 * not GET, POST, PUT or DELETE or the 111 * content type header value couldn't 112 * be parsed. 113 * @throws IOException For a POST or PUT body that 114 * couldn't be read due to an I/O 115 * exception. 116 */ 117 public static HTTPRequest createHTTPRequest(final HttpServletRequest sr) 118 throws IOException { 119 120 return createHTTPRequest(sr, -1); 121 } 122 123 124 /** 125 * Creates a new HTTP request from the specified HTTP servlet request. 126 * 127 * <p><strong>Warning about servlet filters: </strong> Processing of 128 * HTTP POST and PUT requests requires the entity body to be available 129 * for reading from the {@link HttpServletRequest}. If you're getting 130 * unexpected exceptions, please ensure the entity body is not consumed 131 * or modified by an upstream servlet filter. 132 * 133 * @param sr The servlet request. Must not be 134 * {@code null}. 135 * @param maxEntityLength The maximum entity length to accept, -1 for 136 * no limit. 137 * 138 * @return The HTTP request. 139 * 140 * @throws IllegalArgumentException The the servlet request method is 141 * not GET, POST, PUT or DELETE or the 142 * content type header value couldn't 143 * be parsed. 144 * @throws IOException For a POST or PUT body that 145 * couldn't be read due to an I/O 146 * exception. 147 */ 148 public static HTTPRequest createHTTPRequest(final HttpServletRequest sr, final long maxEntityLength) 149 throws IOException { 150 151 HTTPRequest.Method method = HTTPRequest.Method.valueOf(sr.getMethod().toUpperCase()); 152 153 String urlString = reconstructRequestURLString(sr); 154 155 URL url; 156 157 try { 158 url = new URL(urlString); 159 160 } catch (MalformedURLException e) { 161 162 throw new IllegalArgumentException("Invalid request URL: " + e.getMessage() + ": " + urlString, e); 163 } 164 165 HTTPRequest request = new HTTPRequest(method, url); 166 167 try { 168 request.setContentType(sr.getContentType()); 169 170 } catch (ParseException e) { 171 172 throw new IllegalArgumentException("Invalid Content-Type header value: " + e.getMessage(), e); 173 } 174 175 Enumeration<String> headerNames = sr.getHeaderNames(); 176 177 while (headerNames.hasMoreElements()) { 178 final String headerName = headerNames.nextElement(); 179 request.setHeader(headerName, sr.getHeader(headerName)); 180 } 181 182 if (method.equals(HTTPRequest.Method.GET) || method.equals(HTTPRequest.Method.DELETE)) { 183 184 request.setQuery(sr.getQueryString()); 185 186 } else if (method.equals(HTTPRequest.Method.POST) || method.equals(HTTPRequest.Method.PUT)) { 187 188 // Impossible to read application/x-www-form-urlencoded request content on which parameters 189 // APIs have been used. To be safe we recreate the content based on the parameters in this case. 190 // See issues 191 // https://bitbucket.org/connect2id/oauth-2.0-sdk-with-openid-connect-extensions/issues/184 192 // https://bitbucket.org/connect2id/oauth-2.0-sdk-with-openid-connect-extensions/issues/186 193 if (request.getContentType() != null && request.getContentType() 194 .getBaseType().equals(CommonContentTypes.APPLICATION_URLENCODED.getBaseType())) { 195 196 // Recreate the content based on parameters 197 request.setQuery(URLUtils.serializeParametersAlt(sr.getParameterMap())); 198 } else { 199 // read body 200 StringBuilder body = new StringBuilder(256); 201 202 BufferedReader reader = sr.getReader(); 203 204 char[] cbuf = new char[256]; 205 206 int readChars; 207 208 while ((readChars = reader.read(cbuf)) != -1) { 209 210 body.append(cbuf, 0, readChars); 211 212 if (maxEntityLength > 0 && body.length() > maxEntityLength) { 213 throw new IOException( 214 "Request entity body is too large, limit is " + maxEntityLength + " chars"); 215 } 216 } 217 218 reader.close(); 219 request.setQuery(body.toString()); 220 } 221 } 222 223 return request; 224 } 225 226 227 /** 228 * Applies the status code, headers and content of the specified HTTP 229 * response to a HTTP servlet response. 230 * 231 * @param httpResponse The HTTP response. Must not be {@code null}. 232 * @param servletResponse The HTTP servlet response. Must not be 233 * {@code null}. 234 * 235 * @throws IOException If the response content couldn't be written. 236 */ 237 public static void applyHTTPResponse(final HTTPResponse httpResponse, 238 final HttpServletResponse servletResponse) 239 throws IOException { 240 241 // Set the status code 242 servletResponse.setStatus(httpResponse.getStatusCode()); 243 244 245 // Set the headers, but only if explicitly specified 246 for (Map.Entry<String,String> header : httpResponse.getHeaders().entrySet()) { 247 servletResponse.setHeader(header.getKey(), header.getValue()); 248 } 249 250 if (httpResponse.getContentType() != null) 251 servletResponse.setContentType(httpResponse.getContentType().toString()); 252 253 254 // Write out the content 255 256 if (httpResponse.getContent() != null) { 257 258 PrintWriter writer = servletResponse.getWriter(); 259 writer.print(httpResponse.getContent()); 260 writer.close(); 261 } 262 } 263 264 265 /** 266 * Prevents public instantiation. 267 */ 268 private ServletUtils() { 269 270 } 271}