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 2015-04-21
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 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                return bytes != null ? new String(bytes, CHARSET) : null;
144        }
145
146
147        /**
148         * Converts a string to a byte array using {@link #CHARSET}.
149         *
150         * @param string The string to convert. May be {@code null}.
151         *
152         * @return The resulting byte array, {@code null} if conversion failed.
153         */
154        private static byte[] stringToByteArray(final String string) {
155
156                return string != null ? string.getBytes(CHARSET) : null;
157        }
158
159
160        /**
161         * Creates a new payload from the specified JSON object.
162         *
163         * @param jsonObject The JSON object representing the payload. Must not
164         *                   be {@code null}.
165         */
166        public Payload(final JSONObject jsonObject) {
167
168                if (jsonObject == null) {
169                        throw new IllegalArgumentException("The JSON object must not be null");
170                }
171
172                this.jsonObject = jsonObject;
173                string = null;
174                bytes = null;
175                base64URL = null;
176                jwsObject = null;
177                signedJWT = null;
178
179                origin = Origin.JSON;
180        }
181
182
183        /**
184         * Creates a new payload from the specified string.
185         *
186         * @param string The string representing the payload. Must not be 
187         *               {@code null}.
188         */
189        public Payload(final String string) {
190
191                if (string == null) {
192                        throw new IllegalArgumentException("The string must not be null");
193                }
194
195                jsonObject = null;
196                this.string = string;
197                bytes = null;
198                base64URL = null;
199                jwsObject = null;
200                signedJWT = null;
201
202                origin = Origin.STRING;
203        }
204
205
206        /**
207         * Creates a new payload from the specified byte array.
208         *
209         * @param bytes The byte array representing the payload. Must not be 
210         *              {@code null}.
211         */
212        public Payload(final byte[] bytes) {
213
214                if (bytes == null) {
215                        throw new IllegalArgumentException("The byte array must not be null");
216                }
217
218                jsonObject = null;
219                string = null;
220                this.bytes = bytes;
221                base64URL = null;
222                jwsObject = null;
223                signedJWT = null;
224
225                origin = Origin.BYTE_ARRAY;
226        }
227
228
229        /**
230         * Creates a new payload from the specified Base64URL-encoded object.
231         *
232         * @param base64URL The Base64URL-encoded object representing the 
233         *                  payload. Must not be {@code null}.
234         */
235        public Payload(final Base64URL base64URL) {
236
237                if (base64URL == null) {
238                        throw new IllegalArgumentException("The Base64URL-encoded object must not be null");
239                }
240
241                jsonObject = null;
242                string = null;
243                bytes = null;
244                this.base64URL = base64URL;
245                jwsObject = null;
246                signedJWT = null;
247
248                origin = Origin.BASE64URL;
249        }
250
251
252        /**
253         * Creates a new payload from the specified JWS object. Intended for
254         * signed then encrypted JOSE objects.
255         *
256         * @param jwsObject The JWS object representing the payload. Must be in
257         *                  a signed state and not {@code null}.
258         */
259        public Payload(final JWSObject jwsObject) {
260
261                if (jwsObject == null) {
262                        throw new IllegalArgumentException("The JWS object must not be null");
263                }
264
265                if (jwsObject.getState() == JWSObject.State.UNSIGNED) {
266                        throw new IllegalArgumentException("The JWS object must be signed");
267                }
268
269                jsonObject = null;
270                string = null;
271                bytes = null;
272                base64URL = null;
273                this.jwsObject = jwsObject;
274                signedJWT = null;
275
276                origin = Origin.JWS_OBJECT;
277        }
278
279
280        /**
281         * Creates a new payload from the specified signed JSON Web Token
282         * (JWT). Intended for signed then encrypted JWTs.
283         *
284         * @param signedJWT The signed JWT representing the payload. Must be in
285         *                  a signed state and not {@code null}.
286         */
287        public Payload(final SignedJWT signedJWT) {
288
289                if (signedJWT == null) {
290                        throw new IllegalArgumentException("The signed JWT must not be null");
291                }
292
293                if (signedJWT.getState() == JWSObject.State.UNSIGNED) {
294                        throw new IllegalArgumentException("The JWT must be signed");
295                }
296
297                jsonObject = null;
298                string = null;
299                bytes = null;
300                base64URL = null;
301                this.signedJWT = signedJWT;
302                jwsObject = signedJWT; // The signed JWT is also a JWS
303
304                origin = Origin.SIGNED_JWT;
305        }
306
307
308        /**
309         * Gets the original data type used to create this payload.
310         *
311         * @return The payload origin.
312         */
313        public Origin getOrigin() {
314
315                return origin;
316        }
317
318
319        /**
320         * Returns a JSON object view of this payload.
321         *
322         * @return The JSON object view, {@code null} if the payload couldn't
323         *         be converted to a JSON object.
324         */
325        public JSONObject toJSONObject() {
326
327                if (jsonObject != null) {
328                        return jsonObject;
329                }
330
331                // Convert
332
333                String s = toString();
334
335                if (s == null) {
336                        // to string conversion failed
337                        return null;
338                }
339
340                try {
341                        return JSONObjectUtils.parseJSONObject(s);
342
343                } catch (ParseException e) {
344                        // Payload not a JSON object
345                        return null;
346                }
347        }
348
349
350        /**
351         * Returns a string view of this payload.
352         *
353         * @return The string view.
354         */
355        @Override
356        public String toString() {
357
358                if (string != null) {
359
360                        return string;
361                }
362
363                // Convert
364                if (jwsObject != null) {
365
366                        if (jwsObject.getParsedString() != null) {
367                                return jwsObject.getParsedString();
368                        } else {
369                                return jwsObject.serialize();
370                        }
371
372                } else if (jsonObject != null) {
373
374                        return jsonObject.toString();
375
376                } else if (bytes != null) {
377
378                        return byteArrayToString(bytes);
379
380                } else if (base64URL != null) {
381
382                        return base64URL.decodeToString();
383                } else {
384                        return null; // should never happen
385                }
386        }
387
388
389        /**
390         * Returns a byte array view of this payload.
391         *
392         * @return The byte array view.
393         */
394        public byte[] toBytes() {
395
396                if (bytes != null) {
397                        return bytes;
398                }
399
400                // Convert
401                if (base64URL != null) {
402                        return base64URL.decode();
403
404                }
405
406                return stringToByteArray(toString());
407        }
408
409
410        /**
411         * Returns a Base64URL view of this payload.
412         *
413         * @return The Base64URL view.
414         */
415        public Base64URL toBase64URL() {
416
417                if (base64URL != null) {
418                        return base64URL;
419                }
420
421                // Convert
422                return Base64URL.encode(toBytes());
423        }
424
425
426        /**
427         * Returns a JWS object view of this payload. Intended for signed then
428         * encrypted JOSE objects.
429         *
430         * @return The JWS object view, {@code null} if the payload couldn't
431         *         be converted to a JWS object.
432         */
433        public JWSObject toJWSObject() {
434
435                if (jwsObject != null) {
436                        return jwsObject;
437                }
438
439                try {
440                        return JWSObject.parse(toString());
441
442                } catch (ParseException e) {
443
444                        return null;
445                }
446        }
447
448
449        /**
450         * Returns a signed JSON Web Token (JWT) view of this payload. Intended
451         * for signed then encrypted JWTs.
452         *
453         * @return The signed JWT view, {@code null} if the payload couldn't be
454         *         converted to a signed JWT.
455         */
456        public SignedJWT toSignedJWT() {
457
458                if (signedJWT != null) {
459                        return signedJWT;
460                }
461
462                try {
463                        return SignedJWT.parse(toString());
464
465                } catch (ParseException e) {
466
467                        return null;
468                }
469        }
470}