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