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