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