View Javadoc

1   /**
2    *
3    * Copyright 2003-2004 The Apache Software Foundation
4    *
5    *  Licensed under the Apache License, Version 2.0 (the "License");
6    *  you may not use this file except in compliance with the License.
7    *  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  
18  package javax.mail.internet;
19  
20  import java.util.Collections;
21  import java.util.Enumeration;
22  import java.util.HashMap;
23  import java.util.Iterator;
24  import java.util.Map;
25  import java.util.StringTokenizer;
26  import java.util.List;
27  import java.util.ArrayList;// Represents lists in things like
28  
29  import org.apache.geronimo.mail.util.ASCIIUtil;
30  import org.apache.geronimo.mail.util.RFC2231Encoder;
31  import org.apache.geronimo.mail.util.SessionUtil;
32  
33  // Content-Type: text/plain;charset=klingon
34  //
35  // The ;charset=klingon is the parameter list, may have more of them with ';'
36  
37  /**
38   * @version $Rev: 381393 $ $Date: 2006-02-27 09:38:03 -0800 (Mon, 27 Feb 2006) $
39   */
40  public class ParameterList {
41      private static final String MIME_ENCODEPARAMETERS = "mail.mime.encodeparameters";
42      private static final String MIME_DECODEPARAMETERS = "mail.mime.decodeparameters";
43      private static final String MIME_DECODEPARAMETERS_STRICT = "mail.mime.decodeparameters.strict";
44  
45      private static final int HEADER_SIZE_LIMIT = 76;
46  
47      private Map _parameters = new HashMap();
48  
49      private boolean encodeParameters = false;
50      private boolean decodeParameters = false;
51      private boolean decodeParametersStrict = false;
52  
53      public ParameterList() {
54          // figure out how parameter handling is to be performed.
55          getInitialProperties();
56      }
57  
58      public ParameterList(String list) throws ParseException {
59          // figure out how parameter handling is to be performed.
60          getInitialProperties();
61          // get a token parser for the type information
62          HeaderTokenizer tokenizer = new HeaderTokenizer(list, HeaderTokenizer.MIME);
63          while (true) {
64              HeaderTokenizer.Token token = tokenizer.next();
65  
66              switch (token.getType()) {
67                  // the EOF token terminates parsing.
68                  case HeaderTokenizer.Token.EOF:
69                      return;
70  
71                  // each new parameter is separated by a semicolon, including the first, which separates
72                  // the parameters from the main part of the header.
73                  case ';':
74                      // the next token needs to be a parameter name
75                      token = tokenizer.next();
76                      // allow a trailing semicolon on the parameters.
77                      if (token.getType() == HeaderTokenizer.Token.EOF) {
78                          return;
79                      }
80  
81                      if (token.getType() != HeaderTokenizer.Token.ATOM) {
82                          throw new ParseException("Invalid parameter name: " + token.getValue());
83                      }
84  
85                      // get the parameter name as a lower case version for better mapping.
86                      String name = token.getValue().toLowerCase();
87  
88                      token = tokenizer.next();
89  
90                      // parameters are name=value, so we must have the "=" here.
91                      if (token.getType() != '=') {
92                          throw new ParseException("Missing '='");
93                      }
94  
95                      // now the value, which may be an atom or a literal
96                      token = tokenizer.next();
97  
98                      if (token.getType() != HeaderTokenizer.Token.ATOM && token.getType() != HeaderTokenizer.Token.QUOTEDSTRING) {
99                          throw new ParseException("Invalid parameter value: " + token.getValue());
100                     }
101 
102                     String value = token.getValue();
103                     String encodedValue = null;
104 
105                     // we might have to do some additional decoding.  A name that ends with "*"
106                     // is marked as being encoded, so if requested, we decode the value.
107                     if (decodeParameters && name.endsWith("*")) {
108                         // the name needs to be pruned of the marker, and we need to decode the value.
109                         name = name.substring(0, name.length() - 1);
110                         // get a new decoder
111                         RFC2231Encoder decoder = new RFC2231Encoder(HeaderTokenizer.MIME);
112 
113                         try {
114                             // decode the value
115                             encodedValue = decoder.decode(value);
116                         } catch (Exception e) {
117                             // if we're doing things strictly, then raise a parsing exception for errors.
118                             // otherwise, leave the value in its current state.
119                             if (decodeParametersStrict) {
120                                 throw new ParseException("Invalid RFC2231 encoded parameter");
121                             }
122                         }
123                     }
124                     _parameters.put(name, new ParameterValue(name, value, encodedValue));
125 
126                     break;
127 
128                 default:
129                     throw new ParseException("Missing ';'");
130 
131             }
132         }
133     }
134 
135     /**
136      * Get the initial parameters that control parsing and values.
137      * These parameters are controlled by System properties.
138      */
139     private void getInitialProperties() {
140         decodeParameters = SessionUtil.getBooleanProperty(MIME_DECODEPARAMETERS, false);
141         decodeParametersStrict = SessionUtil.getBooleanProperty(MIME_DECODEPARAMETERS_STRICT, false);
142         encodeParameters = SessionUtil.getBooleanProperty(MIME_ENCODEPARAMETERS, false);
143     }
144 
145     public int size() {
146         return _parameters.size();
147     }
148 
149     public String get(String name) {
150         ParameterValue value = (ParameterValue)_parameters.get(name.toLowerCase());
151         if (value != null) {
152             return value.value;
153         }
154         return null;
155     }
156 
157     public void set(String name, String value) {
158         name = name.toLowerCase();
159         _parameters.put(name, new ParameterValue(name, value));
160     }
161 
162     public void remove(String name) {
163         _parameters.remove(name);
164     }
165 
166     public Enumeration getNames() {
167         return Collections.enumeration(_parameters.keySet());
168     }
169 
170     public String toString() {
171         // we need to perform folding, but out starting point is 0.
172         return toString(0);
173     }
174 
175     public String toString(int used) {
176         StringBuffer stringValue = new StringBuffer();
177 
178         Iterator values = _parameters.values().iterator();
179 
180         while (values.hasNext()) {
181             ParameterValue parm = (ParameterValue)values.next();
182             // get the values we're going to encode in here.
183             String name = parm.getEncodedName();
184             String value = parm.toString();
185 
186             // add the semicolon separator.  We also add a blank so that folding/unfolding rules can be used.
187             stringValue.append("; ");
188             used += 2;
189 
190             // too big for the current header line?
191             if ((used + name.length() + value.length() + 1) > HEADER_SIZE_LIMIT) {
192                 // and a CRLF-whitespace combo.
193                 stringValue.append("\r\n ");
194                 // reset the counter for a fresh line
195                 used = 3;
196             }
197             // now add the keyword/value pair.
198             stringValue.append(name);
199             stringValue.append("=");
200 
201             used += name.length() + 1;
202 
203             // we're not out of the woods yet.  It is possible that the keyword/value pair by itself might
204             // be too long for a single line.  If that's the case, the we need to fold the value, if possible
205             if (used + value.length() > HEADER_SIZE_LIMIT) {
206                 String foldedValue = ASCIIUtil.fold(used, value);
207 
208                 stringValue.append(foldedValue);
209 
210                 // now we need to sort out how much of the current line is in use.
211                 int lastLineBreak = foldedValue.lastIndexOf('\n');
212 
213                 if (lastLineBreak != -1) {
214                     used = foldedValue.length() - lastLineBreak + 1;
215                 }
216                 else {
217                     used += foldedValue.length();
218                 }
219             }
220             else {
221                 // no folding required, just append.
222                 stringValue.append(value);
223                 used += value.length();
224             }
225         }
226 
227         return stringValue.toString();
228     }
229 
230 
231     /**
232      * Utility class for representing parameter values in the list.
233      */
234     class ParameterValue {
235         public String name;              // the name of the parameter
236         public String value;             // the original set value
237         public String encodedValue;      // an encoded value, if encoding is requested.
238 
239         public ParameterValue(String name, String value) {
240             this.name = name;
241             this.value = value;
242             this.encodedValue = null;
243         }
244 
245         public ParameterValue(String name, String value, String encodedValue) {
246             this.name = name;
247             this.value = value;
248             this.encodedValue = encodedValue;
249         }
250 
251         public String toString() {
252             if (encodedValue != null) {
253                 return MimeUtility.quote(encodedValue, HeaderTokenizer.MIME);
254             }
255             return MimeUtility.quote(value, HeaderTokenizer.MIME);
256         }
257 
258         public String getEncodedName() {
259             if (encodedValue != null) {
260                 return name + "*";
261             }
262             return name;
263         }
264     }
265 }