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