001package com.nimbusds.oauth2.sdk.http; 002 003 004import java.io.IOException; 005import java.io.PrintWriter; 006import java.net.URI; 007import java.util.Arrays; 008 009import javax.servlet.http.HttpServletResponse; 010 011import net.jcip.annotations.ThreadSafe; 012 013import net.minidev.json.JSONObject; 014 015import com.nimbusds.jwt.JWT; 016import com.nimbusds.jwt.JWTParser; 017 018import com.nimbusds.oauth2.sdk.ParseException; 019import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 020 021 022/** 023 * HTTP response with support for the parameters required to construct an 024 * {@link com.nimbusds.oauth2.sdk.Response OAuth 2.0 response message}. 025 * 026 * <p>Provided HTTP status code constants: 027 * 028 * <ul> 029 * <li>{@link #SC_OK HTTP 200 OK} 030 * <li>{@link #SC_CREATED HTTP 201 Created} 031 * <li>{@link #SC_FOUND HTTP 302 Redirect} 032 * <li>{@link #SC_BAD_REQUEST HTTP 400 Bad request} 033 * <li>{@link #SC_UNAUTHORIZED HTTP 401 Unauthorized} 034 * <li>{@link #SC_FORBIDDEN HTTP 403 Forbidden} 035 * <li>{@link #SC_SERVER_ERROR HTTP 500 Server error} 036 * </ul> 037 * 038 * <p>Supported response headers: 039 * 040 * <ul> 041 * <li>Location 042 * <li>Content-Type 043 * <li>Cache-Control 044 * <li>Pragma 045 * <li>Www-Authenticate 046 * </ul> 047 */ 048@ThreadSafe 049public class HTTPResponse extends HTTPMessage { 050 051 052 /** 053 * HTTP status code (200) indicating the request succeeded. 054 */ 055 public static final int SC_OK = 200; 056 057 058 /** 059 * HTTP status code (201) indicating the request succeeded with a new 060 * resource being created. 061 */ 062 public static final int SC_CREATED = 201; 063 064 065 /** 066 * HTTP status code (302) indicating that the resource resides 067 * temporarily under a different URI (redirect). 068 */ 069 public static final int SC_FOUND = 302; 070 071 072 /** 073 * HTTP status code (400) indicating a bad request. 074 */ 075 public static final int SC_BAD_REQUEST = 400; 076 077 078 /** 079 * HTTP status code (401) indicating that the request requires HTTP 080 * authentication. 081 */ 082 public static final int SC_UNAUTHORIZED = 401; 083 084 085 /** 086 * HTTP status code (403) indicating that access to the resource was 087 * forbidden. 088 */ 089 public static final int SC_FORBIDDEN = 403; 090 091 092 /** 093 * HTTP status code (500) indicating an internal server error. 094 */ 095 public static final int SC_SERVER_ERROR = 500; 096 097 098 /** 099 * HTTP status code (503) indicating the server is unavailable. 100 */ 101 public static final int SC_SERVICE_UNAVAILABLE = 503; 102 103 104 /** 105 * The HTTP status code. 106 */ 107 private final int statusCode; 108 109 110 /** 111 * Specifies a {@code Location} header value (for redirects). 112 */ 113 private URI location = null; 114 115 116 /** 117 * Specifies a {@code Cache-Control} header value. 118 */ 119 private String cacheControl = null; 120 121 122 /** 123 * Specifies a {@code Pragma} header value. 124 */ 125 private String pragma = null; 126 127 128 /** 129 * Specifies a {@code WWW-Authenticate} header value. 130 */ 131 private String wwwAuthenticate = null; 132 133 134 /** 135 * The raw response content. 136 */ 137 private String content = null; 138 139 140 /** 141 * Creates a new minimal HTTP response with the specified status code. 142 * 143 * @param statusCode The HTTP status code. 144 */ 145 public HTTPResponse(final int statusCode) { 146 147 this.statusCode = statusCode; 148 } 149 150 151 /** 152 * Gets the HTTP status code. 153 * 154 * @return The HTTP status code. 155 */ 156 public int getStatusCode() { 157 158 return statusCode; 159 } 160 161 162 /** 163 * Ensures this HTTP response has the specified status code. 164 * 165 * @param expectedStatusCode The expected status code(s). 166 * 167 * @throws ParseException If the status code of this HTTP response 168 * doesn't match the expected. 169 */ 170 public void ensureStatusCode(final int ... expectedStatusCode) 171 throws ParseException { 172 173 for (int c: expectedStatusCode) { 174 175 if (this.statusCode == c) 176 return; 177 } 178 179 throw new ParseException("Unexpected HTTP status code " + 180 this.statusCode + 181 ", must be " + 182 Arrays.toString(expectedStatusCode)); 183 } 184 185 186 /** 187 * Ensures this HTTP response does not have a {@link #SC_OK 200 OK} 188 * status code. 189 * 190 * @throws ParseException If the status code of this HTTP response is 191 * 200 OK. 192 */ 193 public void ensureStatusCodeNotOK() 194 throws ParseException { 195 196 if (statusCode == SC_OK) 197 throw new ParseException("Unexpected HTTP status code, must not be 200 (OK)"); 198 } 199 200 201 /** 202 * Gets the {@code Location} header value (for redirects). 203 * 204 * @return The header value, {@code null} if not specified. 205 */ 206 public URI getLocation() { 207 208 return location; 209 } 210 211 212 /** 213 * Sets the {@code Location} header value (for redirects). 214 * 215 * @param location The header value, {@code null} if not specified. 216 */ 217 public void setLocation(final URI location) { 218 219 this.location = location; 220 } 221 222 223 /** 224 * Gets the {@code Cache-Control} header value. 225 * 226 * @return The header value, {@code null} if not specified. 227 */ 228 public String getCacheControl() { 229 230 return cacheControl; 231 } 232 233 234 /** 235 * Sets the {@code Cache-Control} header value. 236 * 237 * @param cacheControl The header value, {@code null} if not specified. 238 */ 239 public void setCacheControl(final String cacheControl) { 240 241 this.cacheControl = cacheControl; 242 } 243 244 245 /** 246 * Gets the {@code Pragma} header value. 247 * 248 * @return The header value, {@code null} if not specified. 249 */ 250 public String getPragma() { 251 252 return pragma; 253 } 254 255 256 /** 257 * Sets the {@code Pragma} header value. 258 * 259 * @param pragma The header value, {@code null} if not specified. 260 */ 261 public void setPragma(final String pragma) { 262 263 this.pragma = pragma; 264 } 265 266 267 /** 268 * Gets the {@code WWW-Authenticate} header value. 269 * 270 * @return The header value, {@code null} if not specified. 271 */ 272 public String getWWWAuthenticate() { 273 274 return wwwAuthenticate; 275 } 276 277 278 /** 279 * Sets the {@code WWW-Authenticate} header value. 280 * 281 * @param wwwAuthenticate The header value, {@code null} if not 282 * specified. 283 */ 284 public void setWWWAuthenticate(final String wwwAuthenticate) { 285 286 this.wwwAuthenticate = wwwAuthenticate; 287 } 288 289 290 /** 291 * Ensures this HTTP response has a specified content body. 292 * 293 * @throws ParseException If the content body is missing or empty. 294 */ 295 private void ensureContent() 296 throws ParseException { 297 298 if (content == null || content.isEmpty()) 299 throw new ParseException("Missing or empty HTTP response body"); 300 } 301 302 303 /** 304 * Gets the raw response content. 305 * 306 * @return The raw response content, {@code null} if none. 307 */ 308 public String getContent() { 309 310 return content; 311 } 312 313 314 /** 315 * Gets the response content as a JSON object. 316 * 317 * @return The response content as a JSON object. 318 * 319 * @throws ParseException If the Content-Type header isn't 320 * {@code application/json}, the response 321 * content is {@code null}, empty or couldn't be 322 * parsed to a valid JSON object. 323 */ 324 public JSONObject getContentAsJSONObject() 325 throws ParseException { 326 327 ensureContentType(CommonContentTypes.APPLICATION_JSON); 328 329 ensureContent(); 330 331 return JSONObjectUtils.parseJSONObject(content); 332 } 333 334 335 /** 336 * Gets the response content as a JSON Web Token (JWT). 337 * 338 * @return The response content as a JSON Web Token (JWT). 339 * 340 * @throws ParseException If the Content-Type header isn't 341 * {@code application/jwt}, the response content 342 * is {@code null}, empty or couldn't be parsed 343 * to a valid JSON Web Token (JWT). 344 */ 345 public JWT getContentAsJWT() 346 throws ParseException { 347 348 ensureContentType(CommonContentTypes.APPLICATION_JWT); 349 350 ensureContent(); 351 352 try { 353 return JWTParser.parse(content); 354 355 } catch (java.text.ParseException e) { 356 357 throw new ParseException(e.getMessage(), e); 358 } 359 } 360 361 362 /** 363 * Sets the raw response content. 364 * 365 * @param content The raw response content, {@code null} if none. 366 */ 367 public void setContent(final String content) { 368 369 this.content = content; 370 } 371 372 373 /** 374 * Applies the status code, headers and content of this HTTP response 375 * object to the specified HTTP servlet response. 376 * 377 * @param sr The HTTP servlet response to have the properties of this 378 * HTTP request applied to. Must not be {@code null}. 379 * 380 * @throws IOException If the response content couldn't be written. 381 */ 382 public void applyTo(final HttpServletResponse sr) 383 throws IOException { 384 385 // Set the status code 386 sr.setStatus(statusCode); 387 388 389 // Set the headers, but only if explicitly specified 390 if (location != null) 391 sr.setHeader("Location", location.toString()); 392 393 if (getContentType() != null) 394 sr.setContentType(getContentType().toString()); 395 396 if (cacheControl != null) 397 sr.setHeader("Cache-Control", cacheControl); 398 399 if (pragma != null) 400 sr.setHeader("Pragma", pragma); 401 402 403 if (wwwAuthenticate != null) 404 sr.setHeader("Www-Authenticate", wwwAuthenticate); 405 406 407 // Write out the content 408 409 if (content != null) { 410 411 PrintWriter writer = sr.getWriter(); 412 writer.print(content); 413 writer.close(); 414 } 415 } 416}