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