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}