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 * Returns {@code true} if the HTTP status code indicates success 164 * (2xx). 165 * 166 * @return {@code true} if the HTTP status code indicates success, else 167 * {@code false}. 168 */ 169 public boolean indicatesSuccess() { 170 171 return statusCode >= 200 && statusCode < 300; 172 } 173 174 175 /** 176 * Ensures this HTTP response has the specified status code. 177 * 178 * @param expectedStatusCode The expected status code(s). 179 * 180 * @throws ParseException If the status code of this HTTP response 181 * doesn't match the expected. 182 */ 183 public void ensureStatusCode(final int ... expectedStatusCode) 184 throws ParseException { 185 186 for (int c: expectedStatusCode) { 187 188 if (this.statusCode == c) 189 return; 190 } 191 192 throw new ParseException("Unexpected HTTP status code " + 193 this.statusCode + 194 ", must be " + 195 Arrays.toString(expectedStatusCode)); 196 } 197 198 199 /** 200 * Ensures this HTTP response does not have a {@link #SC_OK 200 OK} 201 * status code. 202 * 203 * @throws ParseException If the status code of this HTTP response is 204 * 200 OK. 205 */ 206 public void ensureStatusCodeNotOK() 207 throws ParseException { 208 209 if (statusCode == SC_OK) 210 throw new ParseException("Unexpected HTTP status code, must not be 200 (OK)"); 211 } 212 213 214 /** 215 * Gets the {@code Location} header value (for redirects). 216 * 217 * @return The header value, {@code null} if not specified. 218 */ 219 public URI getLocation() { 220 221 return location; 222 } 223 224 225 /** 226 * Sets the {@code Location} header value (for redirects). 227 * 228 * @param location The header value, {@code null} if not specified. 229 */ 230 public void setLocation(final URI location) { 231 232 this.location = location; 233 } 234 235 236 /** 237 * Gets the {@code Cache-Control} header value. 238 * 239 * @return The header value, {@code null} if not specified. 240 */ 241 public String getCacheControl() { 242 243 return cacheControl; 244 } 245 246 247 /** 248 * Sets the {@code Cache-Control} header value. 249 * 250 * @param cacheControl The header value, {@code null} if not specified. 251 */ 252 public void setCacheControl(final String cacheControl) { 253 254 this.cacheControl = cacheControl; 255 } 256 257 258 /** 259 * Gets the {@code Pragma} header value. 260 * 261 * @return The header value, {@code null} if not specified. 262 */ 263 public String getPragma() { 264 265 return pragma; 266 } 267 268 269 /** 270 * Sets the {@code Pragma} header value. 271 * 272 * @param pragma The header value, {@code null} if not specified. 273 */ 274 public void setPragma(final String pragma) { 275 276 this.pragma = pragma; 277 } 278 279 280 /** 281 * Gets the {@code WWW-Authenticate} header value. 282 * 283 * @return The header value, {@code null} if not specified. 284 */ 285 public String getWWWAuthenticate() { 286 287 return wwwAuthenticate; 288 } 289 290 291 /** 292 * Sets the {@code WWW-Authenticate} header value. 293 * 294 * @param wwwAuthenticate The header value, {@code null} if not 295 * specified. 296 */ 297 public void setWWWAuthenticate(final String wwwAuthenticate) { 298 299 this.wwwAuthenticate = wwwAuthenticate; 300 } 301 302 303 /** 304 * Ensures this HTTP response has a specified content body. 305 * 306 * @throws ParseException If the content body is missing or empty. 307 */ 308 private void ensureContent() 309 throws ParseException { 310 311 if (content == null || content.isEmpty()) 312 throw new ParseException("Missing or empty HTTP response body"); 313 } 314 315 316 /** 317 * Gets the raw response content. 318 * 319 * @return The raw response content, {@code null} if none. 320 */ 321 public String getContent() { 322 323 return content; 324 } 325 326 327 /** 328 * Gets the response content as a JSON object. 329 * 330 * @return The response content as a JSON object. 331 * 332 * @throws ParseException If the Content-Type header isn't 333 * {@code application/json}, the response 334 * content is {@code null}, empty or couldn't be 335 * parsed to a valid JSON object. 336 */ 337 public JSONObject getContentAsJSONObject() 338 throws ParseException { 339 340 ensureContentType(CommonContentTypes.APPLICATION_JSON); 341 342 ensureContent(); 343 344 return JSONObjectUtils.parseJSONObject(content); 345 } 346 347 348 /** 349 * Gets the response content as a JSON Web Token (JWT). 350 * 351 * @return The response content as a JSON Web Token (JWT). 352 * 353 * @throws ParseException If the Content-Type header isn't 354 * {@code application/jwt}, the response content 355 * is {@code null}, empty or couldn't be parsed 356 * to a valid JSON Web Token (JWT). 357 */ 358 public JWT getContentAsJWT() 359 throws ParseException { 360 361 ensureContentType(CommonContentTypes.APPLICATION_JWT); 362 363 ensureContent(); 364 365 try { 366 return JWTParser.parse(content); 367 368 } catch (java.text.ParseException e) { 369 370 throw new ParseException(e.getMessage(), e); 371 } 372 } 373 374 375 /** 376 * Sets the raw response content. 377 * 378 * @param content The raw response content, {@code null} if none. 379 */ 380 public void setContent(final String content) { 381 382 this.content = content; 383 } 384 385 386 /** 387 * Applies the status code, headers and content of this HTTP response 388 * object to the specified HTTP servlet response. 389 * 390 * @param sr The HTTP servlet response to have the properties of this 391 * HTTP request applied to. Must not be {@code null}. 392 * 393 * @throws IOException If the response content couldn't be written. 394 */ 395 public void applyTo(final HttpServletResponse sr) 396 throws IOException { 397 398 // Set the status code 399 sr.setStatus(statusCode); 400 401 402 // Set the headers, but only if explicitly specified 403 if (location != null) 404 sr.setHeader("Location", location.toString()); 405 406 if (getContentType() != null) 407 sr.setContentType(getContentType().toString()); 408 409 if (cacheControl != null) 410 sr.setHeader("Cache-Control", cacheControl); 411 412 if (pragma != null) 413 sr.setHeader("Pragma", pragma); 414 415 416 if (wwwAuthenticate != null) 417 sr.setHeader("Www-Authenticate", wwwAuthenticate); 418 419 420 // Write out the content 421 422 if (content != null) { 423 424 PrintWriter writer = sr.getWriter(); 425 writer.print(content); 426 writer.close(); 427 } 428 } 429}