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