001package com.nimbusds.jose; 002 003 004import java.text.ParseException; 005import java.util.*; 006 007import net.jcip.annotations.Immutable; 008 009import net.minidev.json.JSONObject; 010 011import com.nimbusds.jose.util.Base64URL; 012import com.nimbusds.jose.util.JSONObjectUtils; 013 014 015/** 016 * Plaintext JOSE header. 017 * 018 * <p>Supports all {@link #getRegisteredParameterNames registered header 019 * parameters} of the plain specification: 020 * 021 * <ul> 022 * <li>alg (set to {@link Algorithm#NONE "none"}). 023 * <li>typ 024 * <li>cty 025 * <li>crit 026 * </ul> 027 * 028 * <p>The header may also carry {@link #getCustomParams custom parameters}; 029 * these will be serialised and parsed along the registered ones. 030 * 031 * <p>Example: 032 * 033 * <pre> 034 * { 035 * "alg" : "none" 036 * } 037 * </pre> 038 * 039 * @author Vladimir Dzhuvinov 040 * @version $version$ (2014-08-20) 041 */ 042@Immutable 043public final class PlainHeader extends Header { 044 045 046 /** 047 * The registered parameter names. 048 */ 049 private static final Set<String> REGISTERED_PARAMETER_NAMES; 050 051 052 /** 053 * Initialises the registered parameter name set. 054 */ 055 static { 056 Set<String> p = new HashSet<>(); 057 058 p.add("alg"); 059 p.add("typ"); 060 p.add("cty"); 061 p.add("crit"); 062 063 REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p); 064 } 065 066 067 /** 068 * Builder for constructing plain headers. 069 * 070 * <p>Example use: 071 * 072 * <pre> 073 * PlainHeader header = new PlainHeader.Builder(). 074 * contentType("text/plain"). 075 * customParam("exp", new Date().getTime()). 076 * build(); 077 * </pre> 078 */ 079 public static class Builder { 080 081 082 /** 083 * The JOSE object type. 084 */ 085 private JOSEObjectType typ; 086 087 088 /** 089 * The content type. 090 */ 091 private String cty; 092 093 094 /** 095 * The critical headers. 096 */ 097 private Set<String> crit; 098 099 100 /** 101 * Custom header parameters. 102 */ 103 private Map<String,Object> customParams; 104 105 106 /** 107 * The parsed Base64URL. 108 */ 109 private Base64URL parsedBase64URL; 110 111 112 /** 113 * Creates a new plain header builder. 114 */ 115 public Builder() { 116 117 } 118 119 120 /** 121 * Creates a new plain header builder with the parameters from 122 * the specified header. 123 * 124 * @param plainHeader The plain header to use. Must not be 125 * {@code null}. 126 */ 127 public Builder(final PlainHeader plainHeader) { 128 129 typ = plainHeader.getType(); 130 cty = plainHeader.getContentType(); 131 crit = plainHeader.getCriticalParams(); 132 customParams = plainHeader.getCustomParams(); 133 } 134 135 136 /** 137 * Sets the type ({@code typ}) parameter. 138 * 139 * @param typ The type parameter, {@code null} if not 140 * specified. 141 * 142 * @return This builder. 143 */ 144 public Builder type(final JOSEObjectType typ) { 145 146 this.typ = typ; 147 return this; 148 } 149 150 151 /** 152 * Sets the content type ({@code cty}) parameter. 153 * 154 * @param cty The content type parameter, {@code null} if not 155 * specified. 156 * 157 * @return This builder. 158 */ 159 public Builder contentType(final String cty) { 160 161 this.cty = cty; 162 return this; 163 } 164 165 166 /** 167 * Sets the critical header parameters ({@code crit}) 168 * parameter. 169 * 170 * @param crit The names of the critical header parameters, 171 * empty set or {@code null} if none. 172 * 173 * @return This builder. 174 */ 175 public Builder criticalParams(final Set<String> crit) { 176 177 this.crit = crit; 178 return this; 179 } 180 181 182 /** 183 * Sets a custom (non-registered) parameter. 184 * 185 * @param name The name of the custom parameter. Must not 186 * match a registered parameter name and must not 187 * be {@code null}. 188 * @param value The value of the custom parameter, should map 189 * to a valid JSON entity, {@code null} if not 190 * specified. 191 * 192 * @return This builder. 193 * 194 * @throws IllegalArgumentException If the specified parameter 195 * name matches a registered 196 * parameter name. 197 */ 198 public Builder customParam(final String name, final Object value) { 199 200 if (getRegisteredParameterNames().contains(name)) { 201 throw new IllegalArgumentException("The parameter name \"" + name + "\" matches a registered name"); 202 } 203 204 if (customParams == null) { 205 customParams = new HashMap<>(); 206 } 207 208 customParams.put(name, value); 209 210 return this; 211 } 212 213 214 /** 215 * Sets the custom (non-registered) parameters. The values must 216 * be serialisable to a JSON entity, otherwise will be ignored. 217 * 218 * @param customParameters The custom parameters, empty map or 219 * {@code null} if none. 220 * 221 * @return This builder. 222 */ 223 public Builder customParams(final Map<String, Object> customParameters) { 224 225 this.customParams = customParameters; 226 return this; 227 } 228 229 230 /** 231 * Sets the parsed Base64URL. 232 * 233 * @param base64URL The parsed Base64URL, {@code null} if the 234 * header is created from scratch. 235 * 236 * @return This builder. 237 */ 238 public Builder parsedBase64URL(final Base64URL base64URL) { 239 240 this.parsedBase64URL = base64URL; 241 return this; 242 } 243 244 245 /** 246 * Builds a new plain header. 247 * 248 * @return The plain header. 249 */ 250 public PlainHeader build() { 251 252 return new PlainHeader(typ, cty, crit, customParams, parsedBase64URL); 253 } 254 } 255 256 257 /** 258 * Creates a new minimal plain header with algorithm 259 * {@link Algorithm#NONE none}. 260 */ 261 public PlainHeader() { 262 263 this(null, null, null, null, null); 264 } 265 266 267 /** 268 * Creates a new plain header with algorithm 269 * {@link Algorithm#NONE none}. 270 * 271 * @param typ The type ({@code typ}) parameter, 272 * {@code null} if not specified. 273 * @param cty The content type ({@code cty}) parameter, 274 * {@code null} if not specified. 275 * @param crit The names of the critical header 276 * ({@code crit}) parameters, empty set or 277 * {@code null} if none. 278 * @param customParams The custom parameters, empty map or 279 * {@code null} if none. 280 * @param parsedBase64URL The parsed Base64URL, {@code null} if the 281 * header is created from scratch. 282 */ 283 public PlainHeader(final JOSEObjectType typ, 284 final String cty, 285 final Set<String> crit, 286 final Map<String, Object> customParams, 287 final Base64URL parsedBase64URL) { 288 289 super(Algorithm.NONE, typ, cty, crit, customParams, parsedBase64URL); 290 } 291 292 293 /** 294 * Deep copy constructor. 295 * 296 * @param plainHeader The plain header to copy. Must not be 297 * {@code null}. 298 */ 299 public PlainHeader(final PlainHeader plainHeader) { 300 301 this( 302 plainHeader.getType(), 303 plainHeader.getContentType(), 304 plainHeader.getCriticalParams(), 305 plainHeader.getCustomParams(), 306 plainHeader.getParsedBase64URL() 307 ); 308 } 309 310 311 /** 312 * Gets the registered parameter names for plain headers. 313 * 314 * @return The registered parameter names, as an unmodifiable set. 315 */ 316 public static Set<String> getRegisteredParameterNames() { 317 318 return REGISTERED_PARAMETER_NAMES; 319 } 320 321 322 /** 323 * Gets the algorithm ({@code alg}) parameter. 324 * 325 * @return {@link Algorithm#NONE}. 326 */ 327 @Override 328 public Algorithm getAlgorithm() { 329 330 return Algorithm.NONE; 331 } 332 333 334 /** 335 * Parses a plain header from the specified JSON object. 336 * 337 * @param jsonObject The JSON object to parse. Must not be 338 * {@code null}. 339 * 340 * @return The plain header. 341 * 342 * @throws ParseException If the specified JSON object doesn't 343 * represent a valid plain header. 344 */ 345 public static PlainHeader parse(final JSONObject jsonObject) 346 throws ParseException { 347 348 return parse(jsonObject, null); 349 } 350 351 352 /** 353 * Parses a plain header from the specified JSON object. 354 * 355 * @param jsonObject The JSON object to parse. Must not be 356 * {@code null}. 357 * @param parsedBase64URL The original parsed Base64URL, {@code null} 358 * if not applicable. 359 * 360 * @return The plain header. 361 * 362 * @throws ParseException If the specified JSON object doesn't 363 * represent a valid plain header. 364 */ 365 public static PlainHeader parse(final JSONObject jsonObject, 366 final Base64URL parsedBase64URL) 367 throws ParseException { 368 369 // Get the "alg" parameter 370 Algorithm alg = Header.parseAlgorithm(jsonObject); 371 372 if (alg != Algorithm.NONE) { 373 throw new ParseException("The algorithm \"alg\" header parameter must be \"none\"", 0); 374 } 375 376 PlainHeader.Builder header = new Builder().parsedBase64URL(parsedBase64URL); 377 378 // Parse optional + custom parameters 379 for(final String name: jsonObject.keySet()) { 380 381 switch (name) { 382 383 case "alg": 384 // skip 385 break; 386 case "typ": 387 header = header.type(new JOSEObjectType(JSONObjectUtils.getString(jsonObject, name))); 388 break; 389 case "cty": 390 header = header.contentType(JSONObjectUtils.getString(jsonObject, name)); 391 break; 392 case "crit": 393 header = header.criticalParams(new HashSet<>(JSONObjectUtils.getStringList(jsonObject, name))); 394 break; 395 default: 396 header = header.customParam(name, jsonObject.get(name)); 397 break; 398 } 399 } 400 401 return header.build(); 402 } 403 404 405 /** 406 * Parses a plain header from the specified JSON string. 407 * 408 * @param jsonString The JSON string to parse. Must not be 409 * {@code null}. 410 * 411 * @return The plain header. 412 * 413 * @throws ParseException If the specified JSON string doesn't 414 * represent a valid plain header. 415 */ 416 public static PlainHeader parse(final String jsonString) 417 throws ParseException { 418 419 return parse(jsonString, null); 420 } 421 422 423 /** 424 * Parses a plain header from the specified JSON string. 425 * 426 * @param jsonString The JSON string to parse. Must not be 427 * {@code null}. 428 * @param parsedBase64URL The original parsed Base64URL, {@code null} 429 * if not applicable. 430 * 431 * @return The plain header. 432 * 433 * @throws ParseException If the specified JSON string doesn't 434 * represent a valid plain header. 435 */ 436 public static PlainHeader parse(final String jsonString, 437 final Base64URL parsedBase64URL) 438 throws ParseException { 439 440 return parse(JSONObjectUtils.parseJSONObject(jsonString), parsedBase64URL); 441 } 442 443 444 /** 445 * Parses a plain header from the specified Base64URL. 446 * 447 * @param base64URL The Base64URL to parse. Must not be {@code null}. 448 * 449 * @return The plain header. 450 * 451 * @throws ParseException If the specified Base64URL doesn't represent 452 * a valid plain header. 453 */ 454 public static PlainHeader parse(final Base64URL base64URL) 455 throws ParseException { 456 457 return parse(base64URL.decodeToString(), base64URL); 458 } 459}