001package com.nimbusds.oauth2.sdk.http;
002
003
004import java.io.IOException;
005import java.io.PrintWriter;
006import java.net.URL;
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 URL 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         * Ensures this HTTP response has the specified status code.
164         *
165         * @param statusCode The expected status code(s).
166         *
167         * @throws ParseException If the status code of this HTTP response 
168         *                        doesn't match the expected.
169         */ 
170        public void ensureStatusCode(final int ... statusCode)
171                throws ParseException {
172
173                for (int c: statusCode) {
174
175                        if (this.statusCode == c)
176                                return;
177                }
178
179                throw new ParseException("Unexpected HTTP status code, must be " +  Arrays.toString(statusCode));
180        }
181
182
183        /**
184         * Ensures this HTTP response does not have a {@link #SC_OK 200 OK} 
185         * status code.
186         *
187         * @throws ParseException If the status code of this HTTP response is
188         *                        200 OK.
189         */
190        public void ensureStatusCodeNotOK()
191                throws ParseException {
192
193                if (statusCode == SC_OK)
194                        throw new ParseException("Unexpected HTTP status code, must not be 200 (OK)");
195        }
196        
197        
198        /**
199         * Gets the {@code Location} header value (for redirects).
200         *
201         * @return The header value, {@code null} if not specified.
202         */
203        public URL getLocation() {
204        
205                return location;
206        }
207        
208        
209        /**
210         * Sets the {@code Location} header value (for redirects).
211         *
212         * @param location The header value, {@code null} if not specified.
213         */
214        public void setLocation(final URL location) {
215        
216                this.location = location;
217        }
218        
219        
220        /**
221         * Gets the {@code Cache-Control} header value.
222         *
223         * @return The header value, {@code null} if not specified.
224         */
225        public String getCacheControl() {
226        
227                return cacheControl;
228        }
229
230
231        /**
232         * Sets the {@code Cache-Control} header value.
233         *
234         * @param cacheControl The header value, {@code null} if not specified.
235         */
236        public void setCacheControl(final String cacheControl) {
237        
238                this.cacheControl = cacheControl;
239        }
240        
241        
242        /**
243         * Gets the {@code Pragma} header value.
244         *
245         * @return The header value, {@code null} if not specified.
246         */
247        public String getPragma() {
248        
249                return pragma;
250        }
251        
252        
253        /**
254         * Sets the {@code Pragma} header value.
255         *
256         * @param pragma The header value, {@code null} if not specified.
257         */
258        public void setPragma(final String pragma) {
259        
260                this.pragma = pragma;
261        }
262        
263        
264        /**
265         * Gets the {@code WWW-Authenticate} header value.
266         *
267         * @return The header value, {@code null} if not specified.
268         */
269        public String getWWWAuthenticate() {
270        
271                return wwwAuthenticate;
272        }
273        
274        
275        /**
276         * Sets the {@code WWW-Authenticate} header value.
277         *
278         * @param wwwAuthenticate The header value, {@code null} if not 
279         *                        specified.
280         */
281        public void setWWWAuthenticate(final String wwwAuthenticate) {
282        
283                this.wwwAuthenticate = wwwAuthenticate;
284        }
285        
286        
287        /**
288         * Ensures this HTTP response has a specified content body.
289         *
290         * @throws ParseException If the content body is missing or empty.
291         */
292        private void ensureContent()
293                throws ParseException {
294                
295                if (content == null || content.isEmpty())
296                        throw new ParseException("Missing or empty HTTP response body");
297        }
298        
299        
300        /**
301         * Gets the raw response content.
302         *
303         * @return The raw response content, {@code null} if none.
304         */
305        public String getContent() {
306        
307                return content;
308        }
309        
310        
311        /**
312         * Gets the response content as a JSON object.
313         *
314         * @return The response content as a JSON object.
315         *
316         * @throws ParseException If the Content-Type header isn't 
317         *                        {@code application/json}, the response
318         *                        content is {@code null}, empty or couldn't be
319         *                        parsed to a valid JSON object.
320         */
321        public JSONObject getContentAsJSONObject()
322                throws ParseException {
323                
324                ensureContentType(CommonContentTypes.APPLICATION_JSON);
325                
326                ensureContent();
327                
328                return JSONObjectUtils.parseJSONObject(content);
329        }
330        
331        
332        /**
333         * Gets the response content as a JSON Web Token (JWT).
334         *
335         * @return The response content as a JSON Web Token (JWT).
336         *
337         * @throws ParseException If the Content-Type header isn't
338         *                        {@code application/jwt}, the response content 
339         *                        is {@code null}, empty or couldn't be parsed
340         *                        to a valid JSON Web Token (JWT).
341         */
342        public JWT getContentAsJWT()
343                throws ParseException {
344                
345                ensureContentType(CommonContentTypes.APPLICATION_JWT);
346                
347                ensureContent();
348                
349                try {
350                        return JWTParser.parse(content);
351                        
352                } catch (java.text.ParseException e) {
353                
354                        throw new ParseException(e.getMessage(), e);
355                }
356        }
357        
358        
359        /**
360         * Sets the raw response content.
361         *
362         * @param content The raw response content, {@code null} if none.
363         */
364        public void setContent(final String content) {
365        
366                this.content = content;
367        }
368        
369        
370        /**
371         * Applies the status code, headers and content of this HTTP response
372         * object to the specified HTTP servlet response.
373         *
374         * @param sr The HTTP servlet response to have the properties of this
375         *           HTTP request applied to. Must not be {@code null}.
376         *
377         * @throws IOException If the response content couldn't be written.
378         */
379        public void applyTo(final HttpServletResponse sr)
380                throws IOException {
381        
382                // Set the status code
383                sr.setStatus(statusCode);
384        
385        
386                // Set the headers, but only if explicitly specified    
387                if (location != null)
388                        sr.setHeader("Location", location.toString());
389                
390                if (getContentType() != null)
391                        sr.setContentType(getContentType().toString());
392                
393                if (cacheControl != null)
394                        sr.setHeader("Cache-Control", cacheControl);
395                
396                if (pragma != null)
397                        sr.setHeader("Pragma", pragma);
398                
399                
400                if (wwwAuthenticate != null)
401                        sr.setHeader("Www-Authenticate", wwwAuthenticate);
402        
403        
404                // Write out the content
405        
406                if (content != null) {
407                
408                        PrintWriter writer = sr.getWriter();
409                        writer.print(content);
410                        writer.close();
411                }
412        }
413}