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 (404) indicating the resource was not found.
109         */
110        public static final int SC_NOT_FOUND = 404;
111
112
113        /**
114         * HTTP status code (500) indicating an internal server error.
115         */
116        public static final int SC_SERVER_ERROR = 500;
117
118
119        /**
120         * HTTP status code (503) indicating the server is unavailable.
121         */
122        public static final int SC_SERVICE_UNAVAILABLE = 503;
123
124
125        /**
126         * The HTTP status code.
127         */
128        private final int statusCode;
129        
130        
131        /**
132         * The HTTP status message, {@code null} if not specified.
133         */
134        private String statusMessage;
135        
136        
137        /**
138         * The raw response content.
139         */
140        private String content = null;
141        
142        
143        /**
144         * Creates a new minimal HTTP response with the specified status code.
145         *
146         * @param statusCode The HTTP status code.
147         */
148        public HTTPResponse(final int statusCode) {
149        
150                this.statusCode = statusCode;
151        }
152        
153        
154        /**
155         * Gets the HTTP status code.
156         *
157         * @return The HTTP status code.
158         */
159        public int getStatusCode() {
160        
161                return statusCode;
162        }
163
164
165        /**
166         * Returns {@code true} if the HTTP status code indicates success
167         * (2xx).
168         *
169         * @return {@code true} if the HTTP status code indicates success, else
170         *         {@code false}.
171         */
172        public boolean indicatesSuccess() {
173
174                return statusCode >= 200 && statusCode < 300;
175        }
176        
177        
178        /**
179         * Ensures this HTTP response has the specified status code.
180         *
181         * @param expectedStatusCode The expected status code(s).
182         *
183         * @throws ParseException If the status code of this HTTP response 
184         *                        doesn't match the expected.
185         */ 
186        public void ensureStatusCode(final int ... expectedStatusCode)
187                throws ParseException {
188
189                for (int c: expectedStatusCode) {
190
191                        if (this.statusCode == c)
192                                return;
193                }
194
195                throw new ParseException("Unexpected HTTP status code " + 
196                        this.statusCode + 
197                        ", must be " +  
198                        Arrays.toString(expectedStatusCode));
199        }
200
201
202        /**
203         * Ensures this HTTP response does not have a {@link #SC_OK 200 OK} 
204         * status code.
205         *
206         * @throws ParseException If the status code of this HTTP response is
207         *                        200 OK.
208         */
209        public void ensureStatusCodeNotOK()
210                throws ParseException {
211
212                if (statusCode == SC_OK)
213                        throw new ParseException("Unexpected HTTP status code, must not be 200 (OK)");
214        }
215        
216        
217        /**
218         * Gets the HTTP status message.
219         *
220         * @return The HTTP status message, {@code null} if not specified.
221         */
222        public String getStatusMessage() {
223                
224                return statusMessage;
225        }
226        
227        
228        /**
229         * Sets the HTTP status message.
230         *
231         * @param message The HTTP status message, {@code null} if not
232         *                specified.
233         */
234        public void setStatusMessage(final String message) {
235                
236                this.statusMessage = message;
237        }
238        
239        
240        /**
241         * Gets the {@code Location} header value (for redirects).
242         *
243         * @return The header value, {@code null} if not specified.
244         */
245        public URI getLocation() {
246
247                String value = getHeaderValue("Location");
248
249                if (value == null) {
250                        return null;
251                }
252
253                try {
254                        return new URI(value);
255
256                } catch (URISyntaxException e) {
257                        return null;
258                }
259        }
260        
261        
262        /**
263         * Sets the {@code Location} header value (for redirects).
264         *
265         * @param location The header value, {@code null} if not specified.
266         */
267        public void setLocation(final URI location) {
268        
269                setHeader("Location", location != null ? location.toString() : null);
270        }
271        
272        
273        /**
274         * Gets the {@code Cache-Control} header value.
275         *
276         * @return The header value, {@code null} if not specified.
277         */
278        public String getCacheControl() {
279        
280                return getHeaderValue("Cache-Control");
281        }
282
283
284        /**
285         * Sets the {@code Cache-Control} header value.
286         *
287         * @param cacheControl The header value, {@code null} if not specified.
288         */
289        public void setCacheControl(final String cacheControl) {
290        
291                setHeader("Cache-Control", cacheControl);
292        }
293        
294        
295        /**
296         * Gets the {@code Pragma} header value.
297         *
298         * @return The header value, {@code null} if not specified.
299         */
300        public String getPragma() {
301        
302                return getHeaderValue("Pragma");
303        }
304        
305        
306        /**
307         * Sets the {@code Pragma} header value.
308         *
309         * @param pragma The header value, {@code null} if not specified.
310         */
311        public void setPragma(final String pragma) {
312        
313                setHeader("Pragma", pragma);
314        }
315        
316        
317        /**
318         * Gets the {@code WWW-Authenticate} header value.
319         *
320         * @return The header value, {@code null} if not specified.
321         */
322        public String getWWWAuthenticate() {
323        
324                return getHeaderValue("WWW-Authenticate");
325        }
326        
327        
328        /**
329         * Sets the {@code WWW-Authenticate} header value.
330         *
331         * @param wwwAuthenticate The header value, {@code null} if not 
332         *                        specified.
333         */
334        public void setWWWAuthenticate(final String wwwAuthenticate) {
335        
336                setHeader("WWW-Authenticate", wwwAuthenticate);
337        }
338        
339        
340        /**
341         * Ensures this HTTP response has a specified content body.
342         *
343         * @throws ParseException If the content body is missing or empty.
344         */
345        private void ensureContent()
346                throws ParseException {
347                
348                if (content == null || content.isEmpty())
349                        throw new ParseException("Missing or empty HTTP response body");
350        }
351        
352        
353        /**
354         * Gets the raw response content.
355         *
356         * @return The raw response content, {@code null} if none.
357         */
358        public String getContent() {
359        
360                return content;
361        }
362        
363        
364        /**
365         * Gets the response content as a JSON object.
366         *
367         * @return The response content as a JSON object.
368         *
369         * @throws ParseException If the Content-Type header isn't 
370         *                        {@code application/json}, the response
371         *                        content is {@code null}, empty or couldn't be
372         *                        parsed to a valid JSON object.
373         */
374        public JSONObject getContentAsJSONObject()
375                throws ParseException {
376                
377                ensureEntityContentType(ContentType.APPLICATION_JSON);
378                
379                ensureContent();
380                
381                return JSONObjectUtils.parse(content);
382        }
383
384
385        /**
386         * Gets the response content as a JSON array.
387         *
388         * @return The response content as a JSON array.
389         *
390         * @throws ParseException If the Content-Type header isn't
391         *                        {@code application/json}, the response
392         *                        content is {@code null}, empty or couldn't be
393         *                        parsed to a valid JSON array.
394         */
395        public JSONArray getContentAsJSONArray()
396                throws ParseException {
397
398                ensureEntityContentType(ContentType.APPLICATION_JSON);
399
400                ensureContent();
401
402                return JSONArrayUtils.parse(content);
403        }
404        
405        
406        /**
407         * Gets the response content as a JSON Web Token (JWT).
408         *
409         * @return The response content as a JSON Web Token (JWT).
410         *
411         * @throws ParseException If the Content-Type header isn't
412         *                        {@code application/jwt}, the response content 
413         *                        is {@code null}, empty or couldn't be parsed
414         *                        to a valid JSON Web Token (JWT).
415         */
416        public JWT getContentAsJWT()
417                throws ParseException {
418                
419                ensureEntityContentType(ContentType.APPLICATION_JWT);
420                
421                ensureContent();
422                
423                try {
424                        return JWTParser.parse(content);
425                        
426                } catch (java.text.ParseException e) {
427                
428                        throw new ParseException(e.getMessage(), e);
429                }
430        }
431        
432        
433        /**
434         * Sets the raw response content.
435         *
436         * @param content The raw response content, {@code null} if none.
437         */
438        public void setContent(final String content) {
439        
440                this.content = content;
441        }
442}