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