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