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