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