001 package com.nimbusds.oauth2.sdk.http; 002 003 004 import java.io.BufferedReader; 005 import java.io.InputStreamReader; 006 import java.io.IOException; 007 import java.io.OutputStreamWriter; 008 import java.net.HttpURLConnection; 009 import java.net.MalformedURLException; 010 import java.net.URL; 011 import java.util.Map; 012 013 import javax.servlet.http.HttpServletRequest; 014 015 import net.jcip.annotations.ThreadSafe; 016 017 import net.minidev.json.JSONObject; 018 019 import com.nimbusds.oauth2.sdk.ParseException; 020 import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 021 import com.nimbusds.oauth2.sdk.util.URLUtils; 022 023 024 /** 025 * HTTP request with support for the parameters required to construct an 026 * {@link com.nimbusds.oauth2.sdk.Request OAuth 2.0 request message}. This 027 * class is thread-safe. 028 * 029 * <p>Supported HTTP methods: 030 * 031 * <ul> 032 * <li>{@link Method#GET HTTP GET} 033 * <li>{@link Method#POST HTTP POST} 034 * </ul> 035 * 036 * <p>Supported request headers: 037 * 038 * <ul> 039 * <li>Content-Type 040 * <li>Authorization 041 * </ul> 042 * 043 * @author Vladimir Dzhuvinov 044 * @version $version$ (2013-05-13) 045 */ 046 @ThreadSafe 047 public class HTTPRequest extends HTTPMessage { 048 049 050 /** 051 * Enumeration of the HTTP methods used in OAuth 2.0 requests. 052 */ 053 public static enum Method { 054 055 /** 056 * HTTP GET. 057 */ 058 GET, 059 060 061 /** 062 * HTTP POST. 063 */ 064 POST 065 } 066 067 068 /** 069 * The request method. 070 */ 071 private final Method method; 072 073 074 /** 075 * The request URL. 076 */ 077 private final URL url; 078 079 080 /** 081 * Specifies an {@code Authorization} header value. 082 */ 083 private String authorization = null; 084 085 086 /** 087 * The query string / post body. 088 */ 089 private String query = null; 090 091 092 /** 093 * Creates a new minimally specified HTTP request. 094 * 095 * @param method The HTTP request method. Must not be {@code null}. 096 * @param url The HTTP request URL. Must not be {@code null}. 097 */ 098 public HTTPRequest(final Method method, final URL url) { 099 100 if (method == null) 101 throw new IllegalArgumentException("The HTTP method must not be null"); 102 103 this.method = method; 104 105 106 if (url == null) 107 throw new IllegalArgumentException("The HTTP URL must not be null"); 108 109 this.url = url; 110 } 111 112 113 /** 114 * Creates a new HTTP request from the specified HTTP servlet request. 115 * 116 * @param sr The servlet request. Must not be {@code null}. 117 * 118 * @throws IllegalArgumentException The the servlet request method is 119 * not GET or POST, or the content type 120 * header value couldn't be parsed. 121 * @throws IOException For a POST body that couldn't be 122 * read due to an I/O exception. 123 */ 124 public HTTPRequest(final HttpServletRequest sr) 125 throws IOException { 126 127 method = HTTPRequest.Method.valueOf(sr.getMethod().toUpperCase()); 128 129 try { 130 url = new URL(sr.getRequestURL().toString()); 131 132 } catch (MalformedURLException e) { 133 134 throw new IllegalArgumentException("Invalid request URL: " + e.getMessage(), e); 135 } 136 137 String ct = sr.getContentType(); 138 139 try { 140 setContentType(sr.getContentType()); 141 142 } catch (ParseException e) { 143 144 throw new IllegalArgumentException("Invalid Content-Type header value: " + e.getMessage(), e); 145 } 146 147 setAuthorization(sr.getHeader("Authorization")); 148 149 if (method.equals(Method.GET)) { 150 151 setQuery(sr.getQueryString()); 152 153 } else if (method.equals(Method.POST)) { 154 155 // read body 156 157 StringBuilder body = new StringBuilder(256); 158 159 BufferedReader reader = sr.getReader(); 160 161 String line = null; 162 163 while ((line = reader.readLine()) != null) { 164 165 body.append(line); 166 body.append(System.getProperty("line.separator")); 167 } 168 169 reader.close(); 170 171 setQuery(body.toString()); 172 } 173 } 174 175 176 /** 177 * Gets the request method. 178 * 179 * @return The request method. 180 */ 181 public Method getMethod() { 182 183 return method; 184 } 185 186 187 /** 188 * Gets the request URL. 189 * 190 * @return The request URL. 191 */ 192 public URL getURL() { 193 194 return url; 195 } 196 197 198 /** 199 * Ensures this HTTP request has the specified method. 200 * 201 * @param expectedMethod The expected method. Must not be {@code null}. 202 * 203 * @throws ParseException If the method doesn't match the expected. 204 */ 205 public void ensureMethod(final Method expectedMethod) 206 throws ParseException { 207 208 if (method != expectedMethod) 209 throw new ParseException("The HTTP request method must be " + expectedMethod); 210 } 211 212 213 /** 214 * Gets the {@code Authorization} header value. 215 * 216 * @return The {@code Authorization} header value, {@code null} if not 217 * specified. 218 */ 219 public String getAuthorization() { 220 221 return authorization; 222 } 223 224 225 /** 226 * Sets the {@code Authorization} header value. 227 * 228 * @param authz The {@code Authorization} header value, {@code null} if 229 * not specified. 230 */ 231 public void setAuthorization(final String authz) { 232 233 authorization = authz; 234 } 235 236 237 /** 238 * Gets the raw (undecoded) query string if the request is HTTP GET or 239 * the entity body if the request is HTTP POST. 240 * 241 * <p>Note that the '?' character preceding the query string in GET 242 * requests is not included in the returned string. 243 * 244 * <p>Example query string (line breaks for clarity): 245 * 246 * <pre> 247 * response_type=code 248 * &client_id=s6BhdRkqt3 249 * &state=xyz 250 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 251 * </pre> 252 * 253 * @return For HTTP GET requests the URL query string, for HTTP POST 254 * requests the body. {@code null} if not specified. 255 */ 256 public String getQuery() { 257 258 return query; 259 } 260 261 262 /** 263 * Sets the raw (undecoded) query string if the request is HTTP GET or 264 * the entity body if the request is HTTP POST. 265 * 266 * <p>Note that the '?' character preceding the query string in GET 267 * requests must not be included. 268 * 269 * <p>Example query string (line breaks for clarity): 270 * 271 * <pre> 272 * response_type=code 273 * &client_id=s6BhdRkqt3 274 * &state=xyz 275 * &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb 276 * </pre> 277 * 278 * @param query For HTTP GET requests the URL query string, for HTTP 279 * POST requests the body. {@code null} if not specified. 280 */ 281 public void setQuery(final String query) { 282 283 this.query = query; 284 } 285 286 287 /** 288 * Ensures this HTTP response has a specified query string or entity 289 * body. 290 * 291 * @throws ParseException If the query string or entity body is missing 292 * or empty. 293 */ 294 private void ensureQuery() 295 throws ParseException { 296 297 if (query == null || query.isEmpty()) 298 throw new ParseException("Missing or empty HTTP query string / entity body"); 299 } 300 301 302 /** 303 * Gets the request query as a parameter map. The parameters are 304 * decoded according to {@code application/x-www-form-urlencoded}. 305 * 306 * @return The request query parameters, decoded. If none the map will 307 * be empty. 308 */ 309 public Map<String,String> getQueryParameters() { 310 311 return URLUtils.parseParameters(query); 312 } 313 314 315 /** 316 * Gets the request query or entity body as a JSON Object. 317 * 318 * @return The request query or entity body as a JSON object. 319 * 320 * @throws ParseException If the Content-Type header isn't 321 * {@code application/json}, the request query 322 * or entity body is {@code null}, empty or 323 * couldn't be parsed to a valid JSON object. 324 */ 325 public JSONObject getQueryAsJSONObject() 326 throws ParseException { 327 328 ensureContentType(CommonContentTypes.APPLICATION_JSON); 329 330 ensureQuery(); 331 332 return JSONObjectUtils.parseJSONObject(query); 333 } 334 335 336 /** 337 * Processes this HTTP request by making a connection to the request 338 * URL and retrieving the resulting HTTP response. 339 * 340 * @return The resulting HTTP response. 341 * 342 * @throws IOException If the HTTP request couldn't be processed, due 343 * to a network or other error. 344 */ 345 public HTTPResponse process() 346 throws IOException { 347 348 URL finalURL; 349 350 if (method.equals(HTTPRequest.Method.GET) && query != null) { 351 352 // Append query string 353 354 try { 355 finalURL = new URL(url, "?" + query); 356 357 } catch (MalformedURLException e) { 358 359 throw new IOException("Couldn't append query string: " + e.getMessage(), e); 360 } 361 362 } else { 363 364 finalURL = url; 365 } 366 367 HttpURLConnection conn = (HttpURLConnection)finalURL.openConnection(); 368 369 if (authorization != null) 370 conn.setRequestProperty("Authorization", authorization); 371 372 if (method.equals(HTTPRequest.Method.POST)) { 373 374 conn.setDoOutput(true); 375 376 conn.setRequestProperty("Content-Type", CommonContentTypes.APPLICATION_URLENCODED.toString()); 377 378 if (query != null) { 379 380 OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream()); 381 writer.write(query); 382 writer.flush(); 383 } 384 } 385 386 387 BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); 388 389 StringBuilder body = new StringBuilder(); 390 391 String line = null; 392 393 while ((line = reader.readLine()) != null) { 394 395 body.append(line); 396 body.append(System.getProperty("line.separator")); 397 } 398 399 reader.close(); 400 401 402 HTTPResponse response = new HTTPResponse(conn.getResponseCode()); 403 404 String location = conn.getHeaderField("Location"); 405 406 if (location != null) { 407 408 try { 409 response.setLocation(new URL(location)); 410 411 } catch (MalformedURLException e) { 412 413 throw new IOException("Couldn't parse Location header: " + e.getMessage(), e); 414 } 415 416 } 417 418 419 try { 420 response.setContentType(conn.getContentType()); 421 422 } catch (ParseException e) { 423 424 throw new IOException("Couldn't parse Content-Type header: " + e.getMessage(), e); 425 } 426 427 428 response.setCacheControl(conn.getHeaderField("Cache-Control")); 429 430 response.setPragma(conn.getHeaderField("Pragma")); 431 432 response.setWWWAuthenticate(conn.getHeaderField("WWW-Authenticate")); 433 434 String bodyContent = body.toString(); 435 436 if (! bodyContent.isEmpty()) 437 response.setContent(bodyContent); 438 439 440 return response; 441 } 442 }