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 com.nimbusds.jwt.JWT;
026import com.nimbusds.jwt.JWTParser;
027import com.nimbusds.oauth2.sdk.ParseException;
028import com.nimbusds.oauth2.sdk.util.JSONArrayUtils;
029import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
030import net.jcip.annotations.ThreadSafe;
031import net.minidev.json.JSONArray;
032import net.minidev.json.JSONObject;
033
034
035/**
036 * HTTP response with support for the parameters required to construct an 
037 * {@link com.nimbusds.oauth2.sdk.Response OAuth 2.0 response message}.
038 *
039 * <p>Provided HTTP status code constants:
040 *
041 * <ul>
042 *     <li>{@link #SC_OK HTTP 200 OK}
043 *     <li>{@link #SC_CREATED HTTP 201 Created}
044 *     <li>{@link #SC_FOUND HTTP 302 Redirect}
045 *     <li>{@link #SC_BAD_REQUEST HTTP 400 Bad request}
046 *     <li>{@link #SC_UNAUTHORIZED HTTP 401 Unauthorized}
047 *     <li>{@link #SC_FORBIDDEN HTTP 403 Forbidden}
048 *     <li>{@link #SC_SERVER_ERROR HTTP 500 Server error}
049 * </ul>
050 *
051 * <p>Supported response headers:
052 *
053 * <ul>
054 *     <li>Location
055 *     <li>Content-Type
056 *     <li>Cache-Control
057 *     <li>Pragma
058 *     <li>Www-Authenticate
059 * </ul>
060 */
061@ThreadSafe
062public class HTTPResponse extends HTTPMessage {
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 (500) indicating an internal server error.
107         */
108        public static final int SC_SERVER_ERROR = 500;
109
110
111        /**
112         * HTTP status code (503) indicating the server is unavailable.
113         */
114        public static final int SC_SERVICE_UNAVAILABLE = 503;
115
116
117        /**
118         * The HTTP status code.
119         */
120        private final int statusCode;
121        
122        
123        /**
124         * The HTTP status message, {@code null} if not specified.
125         */
126        private String statusMessage;
127        
128        
129        /**
130         * The raw response content.
131         */
132        private String content = null;
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        /**
210         * Gets the HTTP status message.
211         *
212         * @return The HTTP status message, {@code null} if not specified.
213         */
214        public String getStatusMessage() {
215                
216                return statusMessage;
217        }
218        
219        
220        /**
221         * Sets the HTTP status message.
222         *
223         * @param message The HTTP status message, {@code null} if not
224         *                specified.
225         */
226        public void setStatusMessage(final String message) {
227                
228                this.statusMessage = message;
229        }
230        
231        
232        /**
233         * Gets the {@code Location} header value (for redirects).
234         *
235         * @return The header value, {@code null} if not specified.
236         */
237        public URI getLocation() {
238
239                String value = getHeaderValue("Location");
240
241                if (value == null) {
242                        return null;
243                }
244
245                try {
246                        return new URI(value);
247
248                } catch (URISyntaxException e) {
249                        return null;
250                }
251        }
252        
253        
254        /**
255         * Sets the {@code Location} header value (for redirects).
256         *
257         * @param location The header value, {@code null} if not specified.
258         */
259        public void setLocation(final URI location) {
260        
261                setHeader("Location", location != null ? location.toString() : null);
262        }
263        
264        
265        /**
266         * Gets the {@code Cache-Control} header value.
267         *
268         * @return The header value, {@code null} if not specified.
269         */
270        public String getCacheControl() {
271        
272                return getHeaderValue("Cache-Control");
273        }
274
275
276        /**
277         * Sets the {@code Cache-Control} header value.
278         *
279         * @param cacheControl The header value, {@code null} if not specified.
280         */
281        public void setCacheControl(final String cacheControl) {
282        
283                setHeader("Cache-Control", cacheControl);
284        }
285        
286        
287        /**
288         * Gets the {@code Pragma} header value.
289         *
290         * @return The header value, {@code null} if not specified.
291         */
292        public String getPragma() {
293        
294                return getHeaderValue("Pragma");
295        }
296        
297        
298        /**
299         * Sets the {@code Pragma} header value.
300         *
301         * @param pragma The header value, {@code null} if not specified.
302         */
303        public void setPragma(final String pragma) {
304        
305                setHeader("Pragma", pragma);
306        }
307        
308        
309        /**
310         * Gets the {@code WWW-Authenticate} header value.
311         *
312         * @return The header value, {@code null} if not specified.
313         */
314        public String getWWWAuthenticate() {
315        
316                return getHeaderValue("WWW-Authenticate");
317        }
318        
319        
320        /**
321         * Sets the {@code WWW-Authenticate} header value.
322         *
323         * @param wwwAuthenticate The header value, {@code null} if not 
324         *                        specified.
325         */
326        public void setWWWAuthenticate(final String wwwAuthenticate) {
327        
328                setHeader("WWW-Authenticate", wwwAuthenticate);
329        }
330        
331        
332        /**
333         * Ensures this HTTP response has a specified content body.
334         *
335         * @throws ParseException If the content body is missing or empty.
336         */
337        private void ensureContent()
338                throws ParseException {
339                
340                if (content == null || content.isEmpty())
341                        throw new ParseException("Missing or empty HTTP response body");
342        }
343        
344        
345        /**
346         * Gets the raw response content.
347         *
348         * @return The raw response content, {@code null} if none.
349         */
350        public String getContent() {
351        
352                return content;
353        }
354        
355        
356        /**
357         * Gets the response content as a JSON object.
358         *
359         * @return The response content as a JSON object.
360         *
361         * @throws ParseException If the Content-Type header isn't 
362         *                        {@code application/json}, the response
363         *                        content is {@code null}, empty or couldn't be
364         *                        parsed to a valid JSON object.
365         */
366        public JSONObject getContentAsJSONObject()
367                throws ParseException {
368                
369                ensureContentType(CommonContentTypes.APPLICATION_JSON);
370                
371                ensureContent();
372                
373                return JSONObjectUtils.parse(content);
374        }
375
376
377        /**
378         * Gets the response content as a JSON array.
379         *
380         * @return The response content as a JSON array.
381         *
382         * @throws ParseException If the Content-Type header isn't
383         *                        {@code application/json}, the response
384         *                        content is {@code null}, empty or couldn't be
385         *                        parsed to a valid JSON array.
386         */
387        public JSONArray getContentAsJSONArray()
388                throws ParseException {
389
390                ensureContentType(CommonContentTypes.APPLICATION_JSON);
391
392                ensureContent();
393
394                return JSONArrayUtils.parse(content);
395        }
396        
397        
398        /**
399         * Gets the response content as a JSON Web Token (JWT).
400         *
401         * @return The response content as a JSON Web Token (JWT).
402         *
403         * @throws ParseException If the Content-Type header isn't
404         *                        {@code application/jwt}, the response content 
405         *                        is {@code null}, empty or couldn't be parsed
406         *                        to a valid JSON Web Token (JWT).
407         */
408        public JWT getContentAsJWT()
409                throws ParseException {
410                
411                ensureContentType(CommonContentTypes.APPLICATION_JWT);
412                
413                ensureContent();
414                
415                try {
416                        return JWTParser.parse(content);
417                        
418                } catch (java.text.ParseException e) {
419                
420                        throw new ParseException(e.getMessage(), e);
421                }
422        }
423        
424        
425        /**
426         * Sets the raw response content.
427         *
428         * @param content The raw response content, {@code null} if none.
429         */
430        public void setContent(final String content) {
431        
432                this.content = content;
433        }
434}