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