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