001package com.nimbusds.jose;
002
003
004import java.nio.charset.Charset;
005import java.text.ParseException;
006
007import net.jcip.annotations.Immutable;
008
009import net.minidev.json.JSONObject;
010
011import com.nimbusds.jwt.SignedJWT;
012
013import com.nimbusds.jose.util.Base64URL;
014import com.nimbusds.jose.util.JSONObjectUtils;
015
016
017/**
018 * Payload with JSON object, string, byte array, Base64URL, JWS object and
019 * signed JWT views. Represents the original object that was signed with JWS or
020 * encrypted with JWE. This class is immutable.
021 *
022 * <p>UTF-8 is the character set for all conversions between strings and byte
023 * arrays.
024 *
025 * <p>Conversion relations:
026 *
027 * <pre>
028 * JSONObject <=> String <=> Base64URL
029 *                       <=> byte[]
030 *                       <=> JWSObject
031 *                       <=> SignedJWT
032 * </pre>
033 *
034 * @author Vladimir Dzhuvinov
035 * @version $version$ (2014-10-28)
036 */
037@Immutable
038public final class Payload {
039
040
041        /**
042         * Enumeration of the original data types used to create a 
043         * {@link Payload}.
044         */
045        public static enum Origin {
046
047
048                /**
049                 * The payload was created from a JSON object.
050                 */
051                JSON,
052
053
054                /**
055                 * The payload was created from a string.
056                 */
057                STRING,
058
059
060                /**
061                 * The payload was created from a byte array.
062                 */
063                BYTE_ARRAY,
064
065
066                /**
067                 * The payload was created from a Base64URL-encoded object.
068                 */
069                BASE64URL,
070
071
072                /**
073                 * The payload was created from a JWS object.
074                 */
075                JWS_OBJECT,
076
077
078                /**
079                 * The payload was created from a signed JSON Web Token (JWT).
080                 */
081                SIGNED_JWT
082        }
083
084
085        /**
086         * UTF-8 is the character set for all conversions between strings and
087         * byte arrays.
088         */
089        private static final Charset CHARSET = Charset.forName("UTF-8");
090
091
092        /**
093         * The original payload data type.
094         */
095        private Origin origin;
096
097
098        /**
099         * The JSON object view.
100         */
101        private final JSONObject jsonObject;
102
103
104        /**
105         * The string view.
106         */
107        private final String string;
108
109
110        /**
111         * The byte array view.
112         */
113        private final byte[] bytes;
114
115
116        /**
117         * The Base64URL view.
118         */
119        private final Base64URL base64URL;
120
121
122        /**
123         * The JWS object view.
124         */
125        private final JWSObject jwsObject;
126
127
128        /**
129         * The signed JWT view.
130         */
131        private final SignedJWT signedJWT;
132
133
134        /**
135         * Converts a byte array to a string using {@link #CHARSET}.
136         *
137         * @param bytes The byte array to convert. May be {@code null}.
138         *
139         * @return The resulting string, {@code null} if conversion failed.
140         */
141        private static String byteArrayToString(final byte[] bytes) {
142
143                if (bytes == null) {
144
145                        return null;
146                }
147
148                return new String(bytes, CHARSET);
149        }
150
151
152        /**
153         * Converts a string to a byte array using {@link #CHARSET}.
154         *
155         * @param string The string to convert. May be {@code null}.
156         *
157         * @return The resulting byte array, {@code null} if conversion failed.
158         */
159        private static byte[] stringToByteArray(final String string) {
160
161                if (string == null) {
162
163                        return null;
164                }
165
166                return string.getBytes(CHARSET);
167        }
168
169
170        /**
171         * Creates a new payload from the specified JSON object.
172         *
173         * @param jsonObject The JSON object representing the payload. Must not
174         *                   be {@code null}.
175         */
176        public Payload(final JSONObject jsonObject) {
177
178                if (jsonObject == null) {
179                        throw new IllegalArgumentException("The JSON object must not be null");
180                }
181
182                this.jsonObject = jsonObject;
183                string = null;
184                bytes = null;
185                base64URL = null;
186                jwsObject = null;
187                signedJWT = null;
188
189                origin = Origin.JSON;
190        }
191
192
193        /**
194         * Creates a new payload from the specified string.
195         *
196         * @param string The string representing the payload. Must not be 
197         *               {@code null}.
198         */
199        public Payload(final String string) {
200
201                if (string == null) {
202                        throw new IllegalArgumentException("The string must not be null");
203                }
204
205                jsonObject = null;
206                this.string = string;
207                bytes = null;
208                base64URL = null;
209                jwsObject = null;
210                signedJWT = null;
211
212                origin = Origin.STRING;
213        }
214
215
216        /**
217         * Creates a new payload from the specified byte array.
218         *
219         * @param bytes The byte array representing the payload. Must not be 
220         *              {@code null}.
221         */
222        public Payload(final byte[] bytes) {
223
224                if (bytes == null) {
225                        throw new IllegalArgumentException("The byte array must not be null");
226                }
227
228                jsonObject = null;
229                string = null;
230                this.bytes = bytes;
231                base64URL = null;
232                jwsObject = null;
233                signedJWT = null;
234
235                origin = Origin.BYTE_ARRAY;
236        }
237
238
239        /**
240         * Creates a new payload from the specified Base64URL-encoded object.
241         *
242         * @param base64URL The Base64URL-encoded object representing the 
243         *                  payload. Must not be {@code null}.
244         */
245        public Payload(final Base64URL base64URL) {
246
247                if (base64URL == null) {
248
249                        throw new IllegalArgumentException("The Base64URL-encoded object must not be null");
250                }
251
252                jsonObject = null;
253                string = null;
254                bytes = null;
255                this.base64URL = base64URL;
256                jwsObject = null;
257                signedJWT = null;
258
259                origin = Origin.BASE64URL;
260        }
261
262
263        /**
264         * Creates a new payload from the specified JWS object. Intended for
265         * signed then encrypted JOSE objects.
266         *
267         * @param jwsObject The JWS object representing the payload. Must be in
268         *                  a signed state and not {@code null}.
269         */
270        public Payload(final JWSObject jwsObject) {
271
272                if (jwsObject == null) {
273                        throw new IllegalArgumentException("The JWS object must not be null");
274                }
275
276                if (jwsObject.getState() == JWSObject.State.UNSIGNED) {
277                        throw new IllegalArgumentException("The JWS object must be signed");
278                }
279
280                jsonObject = null;
281                string = null;
282                bytes = null;
283                base64URL = null;
284                this.jwsObject = jwsObject;
285                signedJWT = null;
286
287                origin = Origin.JWS_OBJECT;
288        }
289
290
291        /**
292         * Creates a new payload from the specified signed JSON Web Token
293         * (JWT). Intended for signed then encrypted JWTs.
294         *
295         * @param signedJWT The signed JWT representing the payload. Must be in
296         *                  a signed state and not {@code null}.
297         */
298        public Payload(final SignedJWT signedJWT) {
299
300                if (signedJWT == null) {
301                        throw new IllegalArgumentException("The signed JWT must not be null");
302                }
303
304                if (signedJWT.getState() == JWSObject.State.UNSIGNED) {
305                        throw new IllegalArgumentException("The JWT must be signed");
306                }
307
308                jsonObject = null;
309                string = null;
310                bytes = null;
311                base64URL = null;
312                this.signedJWT = signedJWT;
313                jwsObject = signedJWT; // The signed JWT is also a JWS
314
315                origin = Origin.SIGNED_JWT;
316        }
317
318
319        /**
320         * Gets the original data type used to create this payload.
321         *
322         * @return The payload origin.
323         */
324        public Origin getOrigin() {
325
326                return origin;
327        }
328
329
330        /**
331         * Returns a JSON object view of this payload.
332         *
333         * @return The JSON object view, {@code null} if the payload couldn't
334         *         be converted to a JSON object.
335         */
336        public JSONObject toJSONObject() {
337
338                if (jsonObject != null) {
339                        return jsonObject;
340                }
341
342                // Convert
343
344                String s = toString();
345
346                if (s == null) {
347                        // to string conversion failed
348                        return null;
349                }
350
351                try {
352                        return JSONObjectUtils.parseJSONObject(s);
353
354                } catch (ParseException e) {
355                        // Payload not a JSON object
356                        return null;
357                }
358        }
359
360
361        /**
362         * Returns a string view of this payload.
363         *
364         * @return The string view.
365         */
366        @Override
367        public String toString() {
368
369                if (string != null) {
370
371                        return string;
372                }
373
374                // Convert
375                if (jwsObject != null) {
376
377                        if (jwsObject.getParsedString() != null) {
378                                return jwsObject.getParsedString();
379                        } else {
380                                return jwsObject.serialize();
381                        }
382
383                } else if (jsonObject != null) {
384
385                        return jsonObject.toString();
386
387                } else if (bytes != null) {
388
389                        return byteArrayToString(bytes);
390
391                } else if (base64URL != null) {
392
393                        return base64URL.decodeToString();
394                } else {
395                        return null; // should never happen
396                }
397        }
398
399
400        /**
401         * Returns a byte array view of this payload.
402         *
403         * @return The byte array view.
404         */
405        public byte[] toBytes() {
406
407                if (bytes != null) {
408                        
409                        return bytes;
410                }
411
412                // Convert
413
414                if (base64URL != null) {
415                        return base64URL.decode();
416
417                }
418
419                return stringToByteArray(toString());
420        }
421
422
423        /**
424         * Returns a Base64URL view of this payload.
425         *
426         * @return The Base64URL view.
427         */
428        public Base64URL toBase64URL() {
429
430                if (base64URL != null) {
431
432                        return base64URL;
433                }
434
435                // Convert
436
437                return Base64URL.encode(toBytes());
438        }
439
440
441        /**
442         * Returns a JWS object view of this payload. Intended for signed then
443         * encrypted JOSE objects.
444         *
445         * @return The JWS object view, {@code null} if the payload couldn't
446         *         be converted to a JWS object.
447         */
448        public JWSObject toJWSObject() {
449
450                if (jwsObject != null) {
451
452                        return jwsObject;
453                }
454
455                try {
456                        return JWSObject.parse(toString());
457
458                } catch (ParseException e) {
459
460                        return null;
461                }
462        }
463
464
465        /**
466         * Returns a signed JSON Web Token (JWT) view of this payload. Intended
467         * for signed then encrypted JWTs.
468         *
469         * @return The signed JWT view, {@code null} if the payload couldn't be
470         *         converted to a signed JWT.
471         */
472        public SignedJWT toSignedJWT() {
473
474                if (signedJWT != null) {
475
476                        return signedJWT;
477                }
478
479                try {
480                        return SignedJWT.parse(toString());
481
482                } catch (ParseException e) {
483
484                        return null;
485                }
486        }
487}