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}