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