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