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 * Plaintext (unsecured) JOSE header.
021 *
022 * <p>Supports all {@link #getRegisteredParameterNames registered header
023 * parameters} of the plain 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 $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 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 plain header builder.
118                 */
119                public Builder() {
120
121                }
122
123
124                /**
125                 * Creates a new plain header builder with the parameters from
126                 * the specified header.
127                 *
128                 * @param plainHeader The plain 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 plain header.
251                 *
252                 * @return The plain 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 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 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 plain 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 plain 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 a plain header from the specified JSON object.
340         *
341         * @param jsonObject      The JSON object to parse. Must not be
342         *                        {@code null}.
343         *
344         * @return The plain header.
345         *
346         * @throws ParseException If the specified JSON object doesn't
347         *                        represent a valid plain header.
348         */
349        public static PlainHeader parse(final JSONObject jsonObject)
350                throws ParseException {
351
352                return parse(jsonObject, null);
353        }
354
355
356        /**
357         * Parses a plain header from the specified JSON object.
358         *
359         * @param jsonObject      The JSON object to parse. Must not be
360         *                        {@code null}.
361         * @param parsedBase64URL The original parsed Base64URL, {@code null}
362         *                        if not applicable.
363         *
364         * @return The plain header.
365         *
366         * @throws ParseException If the specified JSON object doesn't
367         *                        represent a valid plain header.
368         */
369        public static PlainHeader parse(final JSONObject jsonObject,
370                                        final Base64URL parsedBase64URL)
371                throws ParseException {
372
373                // Get the "alg" parameter
374                Algorithm alg = Header.parseAlgorithm(jsonObject);
375
376                if (alg != Algorithm.NONE) {
377                        throw new ParseException("The algorithm \"alg\" header parameter must be \"none\"", 0);
378                }
379
380                PlainHeader.Builder header = new Builder().parsedBase64URL(parsedBase64URL);
381
382                // Parse optional + custom parameters
383                for(final String name: jsonObject.keySet()) {
384
385
386                        
387                        if("alg".equals(name)) {
388                                // skip
389                        } else if("typ".equals(name)) {
390                                header = header.type(new JOSEObjectType(JSONObjectUtils.getString(jsonObject, name)));
391                        } else if("cty".equals(name)) {
392                                header = header.contentType(JSONObjectUtils.getString(jsonObject, name));
393                        } else if("crit".equals(name)) {
394                                header = header.criticalParams(new HashSet<>(JSONObjectUtils.getStringList(jsonObject, name)));
395                        } else {
396                                header = header.customParam(name, jsonObject.get(name));
397                        }
398                }
399
400                return header.build();
401        }
402
403
404        /**
405         * Parses a plain header from the specified JSON string.
406         *
407         * @param jsonString The JSON string to parse. Must not be
408         *                   {@code null}.
409         *
410         * @return The plain header.
411         *
412         * @throws ParseException If the specified JSON string doesn't
413         *                        represent a valid plain header.
414         */
415        public static PlainHeader parse(final String jsonString)
416                throws ParseException {
417
418                return parse(jsonString, null);
419        }
420
421
422        /**
423         * Parses a plain header from the specified JSON string.
424         *
425         * @param jsonString      The JSON string to parse. Must not be
426         *                        {@code null}.
427         * @param parsedBase64URL The original parsed Base64URL, {@code null}
428         *                        if not applicable.
429         *
430         * @return The plain header.
431         *
432         * @throws ParseException If the specified JSON string doesn't 
433         *                        represent a valid plain header.
434         */
435        public static PlainHeader parse(final String jsonString,
436                                        final Base64URL parsedBase64URL)
437                throws ParseException {
438
439                return parse(JSONObjectUtils.parseJSONObject(jsonString), parsedBase64URL);
440        }
441
442
443        /**
444         * Parses a plain header from the specified Base64URL.
445         *
446         * @param base64URL The Base64URL to parse. Must not be {@code null}.
447         *
448         * @return The plain header.
449         *
450         * @throws ParseException If the specified Base64URL doesn't represent
451         *                        a valid plain header.
452         */
453        public static PlainHeader parse(final Base64URL base64URL)
454                throws ParseException {
455
456                return parse(base64URL.decodeToString(), base64URL);
457        }
458}