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