001    package com.nimbusds.jose;
002    
003    
004    import java.text.ParseException;
005    import java.util.ArrayList;
006    import java.util.Collections;
007    import java.util.HashMap;
008    import java.util.Map;
009    import java.util.Set;
010    
011    import net.minidev.json.JSONObject;
012    
013    import com.nimbusds.jose.util.Base64URL;
014    import com.nimbusds.jose.util.JSONObjectUtils;
015    
016    
017    /**
018     * The base abstract class for plaintext, JSON Web Signature (JWS) and JSON Web 
019     * Encryption (JWE) headers.
020     *
021     * <p>The header may also carry {@link #setCustomParameters custom parameters};
022     * these will be serialised and parsed along the reserved ones.
023     *
024     * @author Vladimir Dzhuvinov
025     * @version $version$ (2013-05-07)
026     */
027    public abstract class Header implements ReadOnlyHeader {
028    
029    
030            /**
031             * The algorithm ({@code alg}) parameter.
032             */
033            final protected Algorithm alg;
034    
035    
036            /**
037             * The JOSE object type ({@code typ}) parameter.
038             */
039            private JOSEObjectType typ;
040    
041    
042            /**
043             * The content type ({@code cty}) parameter.
044             */
045            private String cty;
046    
047    
048            /**
049             * The critical headers ({@code crit}) parameter.
050             */
051            private Set<String> crit;
052    
053    
054            /**
055             * Custom header parameters.
056             */
057            private Map<String,Object> customParameters = new HashMap<String,Object>();
058    
059    
060            /**
061             * The original parsed Base64URL, {@code null} if the header was 
062             * created from scratch.
063             */
064            private Base64URL parsedBase64URL;
065    
066    
067            /**
068             * Creates a new header with the specified algorithm ({@code alg}) 
069             * parameter.
070             *
071             * @param alg The algorithm parameter. Must not be {@code null}.
072             */
073            protected Header(final Algorithm alg) {
074    
075                    if (alg == null) {
076                            throw new IllegalArgumentException("The algorithm \"alg\" header parameter must not be null");
077                    }
078    
079                    this.alg = alg;
080            }
081    
082    
083            @Override
084            public JOSEObjectType getType() {
085    
086                    return typ;
087            }
088    
089    
090            /**
091             * Sets the type ({@code typ}) parameter.
092             *
093             * @param typ The type parameter, {@code null} if not specified.
094             */
095            public void setType(final JOSEObjectType typ) {
096    
097                    this.typ = typ;
098            }
099    
100    
101            @Override
102            public String getContentType() {
103    
104                    return cty;
105            }
106    
107    
108            /**
109             * Sets the content type ({@code cty}) parameter.
110             *
111             * @param cty The content type parameter, {@code null} if not specified.
112             */
113            public void setContentType(final String cty) {
114    
115                    this.cty = cty;
116            }
117    
118    
119            @Override
120            public Set<String> getCriticalHeaders() {
121    
122                    return crit;
123            }
124    
125    
126    
127            /**
128             * Sets the critical headers ({@code crit}) parameter.
129             *
130             * @param crit The names of the critical header parameters, empty set 
131             *             {@code null} if none.
132             */
133            public void setCriticalHeaders(Set<String> crit) {
134    
135                    this.crit = crit;
136            }
137    
138    
139            @Override
140            public Object getCustomParameter(final String name) {
141    
142                    return customParameters.get(name);
143            }
144    
145    
146            /**
147             * Sets a custom (non-reserved) parameter. Callers and extending classes
148             * should ensure the parameter name doesn't match a reserved parameter 
149             * name.
150             *
151             * @param name  The name of the custom parameter. Must not match a 
152             *              reserved parameter name and must not be {@code null}.
153             * @param value The value of the custom parameter, should map to a valid
154             *              JSON entity, {@code null} if not specified.
155             */
156            protected void setCustomParameter(final String name, final Object value) {
157    
158                    customParameters.put(name, value);
159            }
160    
161    
162            @Override
163            public Map<String,Object> getCustomParameters() {
164    
165                    return Collections.unmodifiableMap(customParameters);
166            }
167    
168    
169            /**
170             * Sets the custom (non-reserved) parameters. The values must be 
171             * serialisable to a JSON entity, otherwise will be ignored.
172             *
173             * @param customParameters The custom parameters, empty map or 
174             *                         {@code null} if none.
175             */
176            public void setCustomParameters(final Map<String,Object> customParameters) {
177    
178                    if (customParameters == null) {
179                            return;
180                    }
181    
182                    this.customParameters = customParameters;
183            }
184    
185    
186            /**
187             * Sets the original parsed Base64URL used to create this header.
188             *
189             * @param parsedBase64URL The parsed Base64URL, {@code null} if the 
190             *                        header was created from scratch.
191             */
192            protected void setParsedBase64URL(final Base64URL parsedBase64URL) {
193    
194                    this.parsedBase64URL = parsedBase64URL;
195            }
196    
197    
198            @Override
199            public JSONObject toJSONObject() {
200    
201                    // Include custom parameters, they will be overwritten if their
202                    // names match specified reserved ones
203                    JSONObject o = new JSONObject(customParameters);
204    
205                    // Alg is always defined
206                    o.put("alg", alg.toString());
207    
208                    if (typ != null) {
209                            o.put("typ", typ.toString());
210                    }
211    
212                    if (cty != null) {
213                            o.put("cty", cty);
214                    }
215    
216                    if (crit != null && ! crit.isEmpty()) {
217                            o.put("crit", new ArrayList<String>(crit));
218                    }
219    
220                    return o;
221            }
222    
223    
224            @Override
225            public String toString() {
226    
227                    return toJSONObject().toString();
228            }
229    
230    
231            @Override
232            public Base64URL toBase64URL() {
233    
234                    if (parsedBase64URL == null) {
235    
236                            // Header was created from scratch, return new Base64URL
237                            return Base64URL.encode(toString());
238    
239                    } else {
240    
241                            // Header was parsed, return original Base64URL
242                            return parsedBase64URL;
243                    }
244            }
245    
246    
247            /**
248             * Parses an algorithm ({@code alg}) parameter from the specified 
249             * header JSON object. Intended for initial parsing of plain, JWS and 
250             * JWE headers.
251             *
252             * <p>The algorithm type (none, JWS or JWE) is determined by inspecting
253             * the algorithm name for "none" and the presence of an "enc" parameter.
254             *
255             * @param json The JSON object to parse. Must not be {@code null}.
256             *
257             * @return The algorithm, an instance of {@link Algorithm#NONE},
258             *         {@link JWSAlgorithm} or {@link JWEAlgorithm}.
259             *
260             * @throws ParseException If the {@code alg} parameter couldn't be 
261             *                        parsed.
262             */
263            public static Algorithm parseAlgorithm(final JSONObject json)
264                            throws ParseException {
265    
266                    String algName = JSONObjectUtils.getString(json, "alg");
267    
268                    // Infer algorithm type
269    
270                    if (algName.equals(Algorithm.NONE.getName())) {
271                            // Plain
272                            return Algorithm.NONE;
273                    } else if (json.containsKey("enc")) {
274                            // JWE
275                            return JWEAlgorithm.parse(algName);
276                    } else {
277                            // JWS
278                            return JWSAlgorithm.parse(algName);
279                    }
280            }
281    
282    
283            /**
284             * Parses a {@link PlainHeader}, {@link JWSHeader} or {@link JWEHeader} 
285             * from the specified JSON object.
286             *
287             * @param json The JSON object to parse. Must not be {@code null}.
288             *
289             * @return The header.
290             *
291             * @throws ParseException If the specified JSON object doesn't 
292             *                        represent a valid header.
293             */
294            public static Header parse(final JSONObject json)
295                    throws ParseException {
296    
297                    Algorithm alg = parseAlgorithm(json);
298    
299                    if (alg.equals(Algorithm.NONE)) {
300    
301                            return PlainHeader.parse(json);
302    
303                    } else if (alg instanceof JWSAlgorithm) {
304    
305                            return JWSHeader.parse(json);
306    
307                    } else if (alg instanceof JWEAlgorithm) {
308    
309                            return JWEHeader.parse(json);
310    
311                    } else {
312    
313                            throw new AssertionError("Unexpected algorithm type: " + alg);
314                    }
315            }
316    }