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