001    package com.nimbusds.oauth2.sdk.http;
002    
003    
004    import java.io.IOException;
005    import java.io.PrintWriter;
006    import java.net.URL;
007    
008    import javax.servlet.http.HttpServletResponse;
009    
010    import net.jcip.annotations.ThreadSafe;
011    
012    import net.minidev.json.JSONObject;
013    
014    import com.nimbusds.jwt.JWT;
015    import com.nimbusds.jwt.JWTParser;
016    
017    import com.nimbusds.oauth2.sdk.ParseException;
018    import 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
049    public 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    }