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