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}. 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
049public 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}