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