ParameterListpublic class ParameterList extends Object This class holds MIME parameters (attribute-value pairs).
The mail.mime.encodeparameters and
mail.mime.decodeparameters System properties
control whether encoded parameters, as specified by
RFC 2231,
are supported. By default, such encoded parameters are not
supported.
Also, in the current implementation, setting the System property
mail.mime.decodeparameters.strict to "true"
will cause a ParseException to be thrown for errors
detected while decoding encoded parameters. By default, if any
decoding errors occur, the original (undecoded) string is used. |
Fields Summary |
---|
private Map | listThe map of name, value pairs.
The value object is either a String, for unencoded
values, or a Value object, for encoded values,
or a MultiValue object, for multi-segment parameters.
We use a LinkedHashMap so that parameters are (as much as
possible) kept in the original order. Note however that
multi-segment parameters (see below) will appear in the
position of the first seen segment and orphan segments
will all move to the end. | private Set | multisegmentNamesA set of names for multi-segment parameters that we
haven't processed yet. Normally such names are accumulated
during the inital parse and processed at the end of the parse,
but such names can also be set via the set method when the
IMAP provider accumulates pre-parsed pieces of a parameter list.
(A special call to the set method tells us when the IMAP provider
is done setting parameters.)
A multi-segment parameter is defined by RFC 2231. For example,
"title*0=part1; title*1=part2", which represents a parameter
named "title" with value "part1part2".
Note also that each segment of the value might or might not be
encoded, indicated by a trailing "*" on the parameter name.
If any segment is encoded, the first segment must be encoded.
Only the first segment contains the charset and language
information needed to decode any encoded segments.
RFC 2231 introduces many possible failure modes, which we try
to handle as gracefully as possible. Generally, a failure to
decode a parameter value causes the non-decoded parameter value
to be used instead. Missing segments cause all later segments
to be appear as independent parameters with names that include
the segment number. For example, "title*0=part1; title*1=part2;
title*3=part4" appears as two parameters named "title" and "title*3". | private Map | slistA map containing the segments for all not-yet-processed
multi-segment parameters. The map is indexed by "name*seg".
The value object is either a String or a Value object.
The Value object is not decoded during the initial parse
because the segments may appear in any order and until the
first segment appears we don't know what charset to use to
decode any encoded segments. The segments are decoded in
order in the combineMultisegmentNames method. | private String | lastNameMWB 3BView: The name of the last parameter added to the map.
Used for the AppleMail hack. | private static boolean | encodeParameters | private static boolean | decodeParameters | private static boolean | decodeParametersStrict | private static boolean | applehack | private static final char[] | hex |
Constructors Summary |
---|
public ParameterList()No-arg Constructor.
// initialize other collections only if they'll be needed
if (decodeParameters) {
multisegmentNames = new HashSet();
slist = new HashMap();
}
| public ParameterList(String s)Constructor that takes a parameter-list string. The String
is parsed and the parameters are collected and stored internally.
A ParseException is thrown if the parse fails.
Note that an empty parameter-list string is valid and will be
parsed into an empty ParameterList.
this();
HeaderTokenizer h = new HeaderTokenizer(s, HeaderTokenizer.MIME);
for (;;) {
HeaderTokenizer.Token tk = h.next();
int type = tk.getType();
String name, value;
if (type == HeaderTokenizer.Token.EOF) // done
break;
if ((char)type == ';") {
// expect parameter name
tk = h.next();
// tolerate trailing semicolon, even though it violates the spec
if (tk.getType() == HeaderTokenizer.Token.EOF)
break;
// parameter name must be a MIME Atom
if (tk.getType() != HeaderTokenizer.Token.ATOM)
throw new ParseException("Expected parameter name, " +
"got \"" + tk.getValue() + "\"");
name = tk.getValue().toLowerCase(Locale.ENGLISH);
// expect '='
tk = h.next();
if ((char)tk.getType() != '=")
throw new ParseException("Expected '=', " +
"got \"" + tk.getValue() + "\"");
// expect parameter value
tk = h.next();
type = tk.getType();
// parameter value must be a MIME Atom or Quoted String
if (type != HeaderTokenizer.Token.ATOM &&
type != HeaderTokenizer.Token.QUOTEDSTRING)
throw new ParseException("Expected parameter value, " +
"got \"" + tk.getValue() + "\"");
value = tk.getValue();
lastName = name;
if (decodeParameters)
putEncodedName(name, value);
else
list.put(name, value);
} else {
// MWB 3BView new code to add in filenames generated by
// AppleMail.
// Note - one space is assumed between name elements.
// This may not be correct but it shouldn't matter too much.
// Note: AppleMail encodes filenames with non-ascii characters
// correctly, so we don't need to worry about the name* subkeys.
if (applehack && type == HeaderTokenizer.Token.ATOM &&
lastName != null &&
(lastName.equals("name") ||
lastName.equals("filename"))) {
// Add value to previous value
String lastValue = (String)list.get(lastName);
value = lastValue + " " + tk.getValue();
list.put(lastName, value);
} else {
throw new ParseException("Expected ';', " +
"got \"" + tk.getValue() + "\"");
}
}
}
if (decodeParameters) {
/*
* After parsing all the parameters, combine all the
* multi-segment parameter values together.
*/
combineMultisegmentNames(false);
}
|
Methods Summary |
---|
private void | combineMultisegmentNames(boolean keepConsistentOnFailure)Iterate through the saved set of names of multi-segment parameters,
for each parameter find all segments stored in the slist map,
decode each segment as needed, combine the segments together into
a single decoded value, and save all segments in a MultiValue object
in the main list indexed by the parameter name.
boolean success = false;
try {
Iterator it = multisegmentNames.iterator();
while (it.hasNext()) {
String name = (String)it.next();
StringBuffer sb = new StringBuffer();
MultiValue mv = new MultiValue();
/*
* Now find all the segments for this name and
* decode each segment as needed.
*/
String charset = null;
int segment;
for (segment = 0; ; segment++) {
String sname = name + "*" + segment;
Object v = slist.get(sname);
if (v == null) // out of segments
break;
mv.add(v);
String value = null;
if (v instanceof Value) {
try {
Value vv = (Value)v;
String evalue = vv.encodedValue;
value = evalue; // in case of exception
if (segment == 0) {
// the first segment specified the charset
// for all other encoded segments
Value vnew = decodeValue(evalue);
charset = vv.charset = vnew.charset;
value = vv.value = vnew.value;
} else {
if (charset == null) {
// should never happen
multisegmentNames.remove(name);
break;
}
value = vv.value = decodeBytes(evalue, charset);
}
} catch (NumberFormatException nex) {
if (decodeParametersStrict)
throw new ParseException(nex.toString());
} catch (UnsupportedEncodingException uex) {
if (decodeParametersStrict)
throw new ParseException(uex.toString());
} catch (StringIndexOutOfBoundsException ex) {
if (decodeParametersStrict)
throw new ParseException(ex.toString());
}
// if anything went wrong decoding the value,
// we just use the original value (set above)
} else {
value = (String)v;
}
sb.append(value);
slist.remove(sname);
}
if (segment == 0) {
// didn't find any segments at all
list.remove(name);
} else {
mv.value = sb.toString();
list.put(name, mv);
}
}
success = true;
} finally {
/*
* If we get here because of an exception that's going to
* be thrown (success == false) from the constructor
* (keepConsistentOnFailure == false), this is all wasted effort.
*/
if (keepConsistentOnFailure || success) {
// we should never end up with anything in slist,
// but if we do, add it all to list
if (slist.size() > 0) {
// first, decode any values that we'll add to the list
Iterator sit = slist.values().iterator();
while (sit.hasNext()) {
Object v = sit.next();
if (v instanceof Value) {
Value vv = (Value)v;
Value vnew = decodeValue(vv.encodedValue);
vv.charset = vnew.charset;
vv.value = vnew.value;
}
}
list.putAll(slist);
}
// clear out the set of names and segments
multisegmentNames.clear();
slist.clear();
}
}
| private static java.lang.String | decodeBytes(java.lang.String value, java.lang.String charset)Decode the encoded bytes in value using the specified charset.
/*
* Decode the ASCII characters in value
* into an array of bytes, and then convert
* the bytes to a String using the specified
* charset. We'll never need more bytes than
* encoded characters, so use that to size the
* array.
*/
byte[] b = new byte[value.length()];
int i, bi;
for (i = 0, bi = 0; i < value.length(); i++) {
char c = value.charAt(i);
if (c == '%") {
String hex = value.substring(i + 1, i + 3);
c = (char)Integer.parseInt(hex, 16);
i += 2;
}
b[bi++] = (byte)c;
}
return new String(b, 0, bi, MimeUtility.javaCharset(charset));
| private static javax.mail.internet.ParameterList$Value | decodeValue(java.lang.String value)Decode a parameter value.
Value v = new Value();
v.encodedValue = value;
v.value = value; // in case we fail to decode it
try {
int i = value.indexOf('\'");
if (i <= 0) {
if (decodeParametersStrict)
throw new ParseException(
"Missing charset in encoded value: " + value);
return v; // not encoded correctly? return as is.
}
String charset = value.substring(0, i);
int li = value.indexOf('\'", i + 1);
if (li < 0) {
if (decodeParametersStrict)
throw new ParseException(
"Missing language in encoded value: " + value);
return v; // not encoded correctly? return as is.
}
String lang = value.substring(i + 1, li);
value = value.substring(li + 1);
v.charset = charset;
v.value = decodeBytes(value, charset);
} catch (NumberFormatException nex) {
if (decodeParametersStrict)
throw new ParseException(nex.toString());
} catch (UnsupportedEncodingException uex) {
if (decodeParametersStrict)
throw new ParseException(uex.toString());
} catch (StringIndexOutOfBoundsException ex) {
if (decodeParametersStrict)
throw new ParseException(ex.toString());
}
return v;
| private static javax.mail.internet.ParameterList$Value | encodeValue(java.lang.String value, java.lang.String charset)Encode a parameter value, if necessary.
If the value is encoded, a Value object is returned.
Otherwise, null is returned.
XXX - Could return a MultiValue object if parameter value is too long.
if (MimeUtility.checkAscii(value) == MimeUtility.ALL_ASCII)
return null; // no need to encode it
byte[] b; // charset encoded bytes from the string
try {
b = value.getBytes(MimeUtility.javaCharset(charset));
} catch (UnsupportedEncodingException ex) {
return null;
}
StringBuffer sb = new StringBuffer(b.length + charset.length() + 2);
sb.append(charset).append("''");
for (int i = 0; i < b.length; i++) {
char c = (char)(b[i] & 0xff);
// do we need to encode this character?
if (c <= ' " || c >= 0x7f || c == '*" || c == '\'" || c == '%" ||
HeaderTokenizer.MIME.indexOf(c) >= 0) {
sb.append('%").append(hex[c>>4]).append(hex[c&0xf]);
} else
sb.append(c);
}
Value v = new Value();
v.charset = charset;
v.value = value;
v.encodedValue = sb.toString();
return v;
| public java.lang.String | get(java.lang.String name)Returns the value of the specified parameter. Note that
parameter names are case-insensitive.
String value;
Object v = list.get(name.trim().toLowerCase(Locale.ENGLISH));
if (v instanceof MultiValue)
value = ((MultiValue)v).value;
else if (v instanceof Value)
value = ((Value)v).value;
else
value = (String)v;
return value;
| public java.util.Enumeration | getNames()Return an enumeration of the names of all parameters in this
list.
return new ParamEnum(list.keySet().iterator());
| private void | putEncodedName(java.lang.String name, java.lang.String value)If the name is an encoded or multi-segment name (or both)
handle it appropriately, storing the appropriate String
or Value object. Multi-segment names are stored in the
main parameter list as an emtpy string as a placeholder,
replaced later in combineMultisegmentNames with a MultiValue
object. This causes all pieces of the multi-segment parameter
to appear in the position of the first seen segment of the
parameter.
int star = name.indexOf('*");
if (star < 0) {
// single parameter, unencoded value
list.put(name, value);
} else if (star == name.length() - 1) {
// single parameter, encoded value
name = name.substring(0, star);
list.put(name, decodeValue(value));
} else {
// multiple segments
String rname = name.substring(0, star);
multisegmentNames.add(rname);
list.put(rname, "");
Object v;
if (name.endsWith("*")) {
// encoded value
v = new Value();
((Value)v).encodedValue = value;
((Value)v).value = value; // default; decoded later
name = name.substring(0, name.length() - 1);
} else {
// unencoded value
v = value;
}
slist.put(name, v);
}
| private static java.lang.String | quote(java.lang.String value)
return MimeUtility.quote(value, HeaderTokenizer.MIME);
| public void | remove(java.lang.String name)Removes the specified parameter from this ParameterList.
This method does nothing if the parameter is not present.
list.remove(name.trim().toLowerCase(Locale.ENGLISH));
| public void | set(java.lang.String name, java.lang.String value)Set a parameter. If this parameter already exists, it is
replaced by this new value.
// XXX - an incredible kludge used by the IMAP provider
// to indicate that it's done setting parameters
if (name == null && value != null && value.equals("DONE")) {
/*
* If we've accumulated any multi-segment names from calls to
* the set method from the IMAP provider, combine the pieces.
* Ignore any parse errors (e.g., from decoding the values)
* because it's too late to report them.
*/
if (decodeParameters && multisegmentNames.size() > 0) {
try {
combineMultisegmentNames(true);
} catch (ParseException pex) {
// too late to do anything about it
}
}
return;
}
name = name.trim().toLowerCase(Locale.ENGLISH);
if (decodeParameters) {
try {
putEncodedName(name, value);
} catch (ParseException pex) {
// ignore it
list.put(name, value);
}
} else
list.put(name, value);
| public void | set(java.lang.String name, java.lang.String value, java.lang.String charset)Set a parameter. If this parameter already exists, it is
replaced by this new value. If the
mail.mime.encodeparameters System property
is true, and the parameter value is non-ASCII, it will be
encoded with the specified charset, as specified by RFC 2231.
if (encodeParameters) {
Value ev = encodeValue(value, charset);
// was it actually encoded?
if (ev != null)
list.put(name.trim().toLowerCase(Locale.ENGLISH), ev);
else
set(name, value);
} else
set(name, value);
| public int | size()Return the number of parameters in this list.
return list.size();
| public java.lang.String | toString()Convert this ParameterList into a MIME String. If this is
an empty list, an empty string is returned.
return toString(0);
| public java.lang.String | toString(int used)Convert this ParameterList into a MIME String. If this is
an empty list, an empty string is returned.
The 'used' parameter specifies the number of character positions
already taken up in the field into which the resulting parameter
list is to be inserted. It's used to determine where to fold the
resulting parameter list.
ToStringBuffer sb = new ToStringBuffer(used);
Iterator e = list.keySet().iterator();
while (e.hasNext()) {
String name = (String)e.next();
Object v = list.get(name);
if (v instanceof MultiValue) {
MultiValue vv = (MultiValue)v;
String ns = name + "*";
for (int i = 0; i < vv.size(); i++) {
Object va = vv.get(i);
if (va instanceof Value)
sb.addNV(ns + i + "*", ((Value)va).encodedValue);
else
sb.addNV(ns + i, (String)va);
}
} else if (v instanceof Value)
sb.addNV(name + "*", ((Value)v).encodedValue);
else
sb.addNV(name, (String)v);
}
return sb.toString();
|
|