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