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