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