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