001/* 002 * oauth2-oidc-sdk 003 * 004 * Copyright 2012-2016, Connect2id Ltd and contributors. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.oauth2.sdk.http; 019 020 021import java.net.URI; 022import java.net.URISyntaxException; 023import java.util.Arrays; 024 025import net.jcip.annotations.ThreadSafe; 026import net.minidev.json.JSONArray; 027import net.minidev.json.JSONObject; 028 029import com.nimbusds.common.contenttype.ContentType; 030import com.nimbusds.jwt.JWT; 031import com.nimbusds.jwt.JWTParser; 032import com.nimbusds.oauth2.sdk.ParseException; 033import com.nimbusds.oauth2.sdk.util.JSONArrayUtils; 034import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 035import com.nimbusds.openid.connect.sdk.Nonce; 036 037 038/** 039 * HTTP response with support for the parameters required to construct an 040 * {@link com.nimbusds.oauth2.sdk.Response OAuth 2.0 response message}. 041 * 042 * <p>Provided HTTP status code constants: 043 * 044 * <ul> 045 * <li>{@link #SC_OK HTTP 200 OK} 046 * <li>{@link #SC_CREATED HTTP 201 Created} 047 * <li>{@link #SC_FOUND HTTP 302 Redirect} 048 * <li>{@link #SC_BAD_REQUEST HTTP 400 Bad request} 049 * <li>{@link #SC_UNAUTHORIZED HTTP 401 Unauthorized} 050 * <li>{@link #SC_FORBIDDEN HTTP 403 Forbidden} 051 * <li>{@link #SC_SERVER_ERROR HTTP 500 Server error} 052 * </ul> 053 * 054 * <p>Supported response headers: 055 * 056 * <ul> 057 * <li>Location 058 * <li>Content-Type 059 * <li>Cache-Control 060 * <li>Pragma 061 * <li>Www-Authenticate 062 * <li>DPoP-Nonce 063 * </ul> 064 */ 065@ThreadSafe 066public class HTTPResponse extends HTTPMessage { 067 068 069 /** 070 * HTTP status code (200) indicating the request succeeded. 071 */ 072 public static final int SC_OK = 200; 073 074 075 /** 076 * HTTP status code (201) indicating the request succeeded with a new 077 * resource being created. 078 */ 079 public static final int SC_CREATED = 201; 080 081 082 /** 083 * HTTP status code (302) indicating that the resource resides 084 * temporarily under a different URI (redirect). 085 */ 086 public static final int SC_FOUND = 302; 087 088 089 /** 090 * HTTP status code (400) indicating a bad request. 091 */ 092 public static final int SC_BAD_REQUEST = 400; 093 094 095 /** 096 * HTTP status code (401) indicating that the request requires HTTP 097 * authentication. 098 */ 099 public static final int SC_UNAUTHORIZED = 401; 100 101 102 /** 103 * HTTP status code (403) indicating that access to the resource was 104 * forbidden. 105 */ 106 public static final int SC_FORBIDDEN = 403; 107 108 109 /** 110 * HTTP status code (404) indicating the resource was not found. 111 */ 112 public static final int SC_NOT_FOUND = 404; 113 114 115 /** 116 * HTTP status code (500) indicating an internal server error. 117 */ 118 public static final int SC_SERVER_ERROR = 500; 119 120 121 /** 122 * HTTP status code (503) indicating the server is unavailable. 123 */ 124 public static final int SC_SERVICE_UNAVAILABLE = 503; 125 126 127 /** 128 * The HTTP status code. 129 */ 130 private final int statusCode; 131 132 133 /** 134 * The HTTP status message, {@code null} if not specified. 135 */ 136 private String statusMessage; 137 138 139 /** 140 * The raw response content. 141 */ 142 private String content = null; 143 144 145 /** 146 * Creates a new minimal HTTP response with the specified status code. 147 * 148 * @param statusCode The HTTP status code. 149 */ 150 public HTTPResponse(final int statusCode) { 151 152 this.statusCode = statusCode; 153 } 154 155 156 /** 157 * Gets the HTTP status code. 158 * 159 * @return The HTTP status code. 160 */ 161 public int getStatusCode() { 162 163 return statusCode; 164 } 165 166 167 /** 168 * Returns {@code true} if the HTTP status code indicates success 169 * (2xx). 170 * 171 * @return {@code true} if the HTTP status code indicates success, else 172 * {@code false}. 173 */ 174 public boolean indicatesSuccess() { 175 176 return statusCode >= 200 && statusCode < 300; 177 } 178 179 180 /** 181 * Ensures this HTTP response has the specified status code. 182 * 183 * @param expectedStatusCode The expected status code(s). 184 * 185 * @throws ParseException If the status code of this HTTP response 186 * doesn't match the expected. 187 */ 188 public void ensureStatusCode(final int ... expectedStatusCode) 189 throws ParseException { 190 191 for (int c: expectedStatusCode) { 192 193 if (this.statusCode == c) 194 return; 195 } 196 197 throw new ParseException("Unexpected HTTP status code " + 198 this.statusCode + 199 ", must be " + 200 Arrays.toString(expectedStatusCode)); 201 } 202 203 204 /** 205 * Ensures this HTTP response does not have a {@link #SC_OK 200 OK} 206 * status code. 207 * 208 * @throws ParseException If the status code of this HTTP response is 209 * 200 OK. 210 */ 211 public void ensureStatusCodeNotOK() 212 throws ParseException { 213 214 if (statusCode == SC_OK) 215 throw new ParseException("Unexpected HTTP status code, must not be 200 (OK)"); 216 } 217 218 219 /** 220 * Gets the HTTP status message. 221 * 222 * @return The HTTP status message, {@code null} if not specified. 223 */ 224 public String getStatusMessage() { 225 226 return statusMessage; 227 } 228 229 230 /** 231 * Sets the HTTP status message. 232 * 233 * @param message The HTTP status message, {@code null} if not 234 * specified. 235 */ 236 public void setStatusMessage(final String message) { 237 238 this.statusMessage = message; 239 } 240 241 242 /** 243 * Gets the {@code Location} header value (for redirects). 244 * 245 * @return The header value, {@code null} if not specified. 246 */ 247 public URI getLocation() { 248 249 String value = getHeaderValue("Location"); 250 251 if (value == null) { 252 return null; 253 } 254 255 try { 256 return new URI(value); 257 258 } catch (URISyntaxException e) { 259 return null; 260 } 261 } 262 263 264 /** 265 * Sets the {@code Location} header value (for redirects). 266 * 267 * @param location The header value, {@code null} if not specified. 268 */ 269 public void setLocation(final URI location) { 270 271 setHeader("Location", location != null ? location.toString() : null); 272 } 273 274 275 /** 276 * Gets the {@code Cache-Control} header value. 277 * 278 * @return The header value, {@code null} if not specified. 279 */ 280 public String getCacheControl() { 281 282 return getHeaderValue("Cache-Control"); 283 } 284 285 286 /** 287 * Sets the {@code Cache-Control} header value. 288 * 289 * @param cacheControl The header value, {@code null} if not specified. 290 */ 291 public void setCacheControl(final String cacheControl) { 292 293 setHeader("Cache-Control", cacheControl); 294 } 295 296 297 /** 298 * Gets the {@code Pragma} header value. 299 * 300 * @return The header value, {@code null} if not specified. 301 */ 302 public String getPragma() { 303 304 return getHeaderValue("Pragma"); 305 } 306 307 308 /** 309 * Sets the {@code Pragma} header value. 310 * 311 * @param pragma The header value, {@code null} if not specified. 312 */ 313 public void setPragma(final String pragma) { 314 315 setHeader("Pragma", pragma); 316 } 317 318 319 /** 320 * Gets the {@code WWW-Authenticate} header value. 321 * 322 * @return The header value, {@code null} if not specified. 323 */ 324 public String getWWWAuthenticate() { 325 326 return getHeaderValue("WWW-Authenticate"); 327 } 328 329 330 /** 331 * Sets the {@code WWW-Authenticate} header value. 332 * 333 * @param wwwAuthenticate The header value, {@code null} if not 334 * specified. 335 */ 336 public void setWWWAuthenticate(final String wwwAuthenticate) { 337 338 setHeader("WWW-Authenticate", wwwAuthenticate); 339 } 340 341 342 /** 343 * Gets the {@code DPoP-Nonce} header value. 344 * 345 * @return The {@code DPoP-Nonce} header value, {@code null} if not 346 * specified or parsing failed. 347 */ 348 public Nonce getDPoPNonce() { 349 350 String nonce = getHeaderValue("DPoP-Nonce"); 351 if (nonce == null) { 352 return null; 353 } 354 355 try { 356 return new Nonce(nonce); 357 } catch (IllegalArgumentException e) { 358 return null; 359 } 360 } 361 362 363 /** 364 * Sets the {@code DPoP-Nonce} header value. 365 * 366 * @param nonce The {@code DPoP-Nonce} header value, {@code null} if 367 * not specified. 368 */ 369 public void setDPoPNonce(final Nonce nonce) { 370 371 if (nonce != null) { 372 setHeader("DPoP-Nonce", nonce.getValue()); 373 } else { 374 setHeader("DPoP-Nonce", (String[]) null); 375 } 376 } 377 378 379 /** 380 * Ensures this HTTP response has a specified content body. 381 * 382 * @throws ParseException If the content body is missing or empty. 383 */ 384 private void ensureContent() 385 throws ParseException { 386 387 if (content == null || content.isEmpty()) 388 throw new ParseException("Missing or empty HTTP response body"); 389 } 390 391 392 /** 393 * Gets the raw response content. 394 * 395 * @return The raw response content, {@code null} if none. 396 */ 397 public String getContent() { 398 399 return content; 400 } 401 402 403 /** 404 * Gets the response content as a JSON object. 405 * 406 * @return The response content as a JSON object. 407 * 408 * @throws ParseException If the Content-Type header isn't 409 * {@code application/json}, the response 410 * content is {@code null}, empty or couldn't be 411 * parsed to a valid JSON object. 412 */ 413 public JSONObject getContentAsJSONObject() 414 throws ParseException { 415 416 ensureEntityContentType(ContentType.APPLICATION_JSON); 417 418 ensureContent(); 419 420 return JSONObjectUtils.parse(content); 421 } 422 423 424 /** 425 * Gets the response content as a JSON array. 426 * 427 * @return The response content as a JSON array. 428 * 429 * @throws ParseException If the Content-Type header isn't 430 * {@code application/json}, the response 431 * content is {@code null}, empty or couldn't be 432 * parsed to a valid JSON array. 433 */ 434 public JSONArray getContentAsJSONArray() 435 throws ParseException { 436 437 ensureEntityContentType(ContentType.APPLICATION_JSON); 438 439 ensureContent(); 440 441 return JSONArrayUtils.parse(content); 442 } 443 444 445 /** 446 * Gets the response content as a JSON Web Token (JWT). 447 * 448 * @return The response content as a JSON Web Token (JWT). 449 * 450 * @throws ParseException If the Content-Type header isn't 451 * {@code application/jwt}, the response content 452 * is {@code null}, empty or couldn't be parsed 453 * to a valid JSON Web Token (JWT). 454 */ 455 public JWT getContentAsJWT() 456 throws ParseException { 457 458 ensureEntityContentType(ContentType.APPLICATION_JWT); 459 460 ensureContent(); 461 462 try { 463 return JWTParser.parse(content); 464 465 } catch (java.text.ParseException e) { 466 467 throw new ParseException(e.getMessage(), e); 468 } 469 } 470 471 472 /** 473 * Sets the raw response content. 474 * 475 * @param content The raw response content, {@code null} if none. 476 */ 477 public void setContent(final String content) { 478 479 this.content = content; 480 } 481}