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