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