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