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