1. /*
  2. * @(#)MimeTypeParameterList.java 1.5 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package java.awt.datatransfer;
  8. import java.util.Hashtable;
  9. import java.util.Enumeration;
  10. /**
  11. * An object that encapsualtes the parameter list of a MimeType
  12. * as defined in RFC 2045 and 2046.
  13. */
  14. class MimeTypeParameterList implements Cloneable {
  15. /**
  16. * Default constructor.
  17. */
  18. public MimeTypeParameterList() {
  19. parameters = new Hashtable();
  20. }
  21. public MimeTypeParameterList(String rawdata) throws MimeTypeParseException {
  22. parameters = new Hashtable();
  23. // now parse rawdata
  24. parse(rawdata);
  25. }
  26. private MimeTypeParameterList(Hashtable ht) throws CloneNotSupportedException {
  27. parameters = (Hashtable)ht.clone();
  28. }
  29. /**
  30. * A routine for parsing the parameter list out of a String.
  31. */
  32. protected void parse(String rawdata) throws MimeTypeParseException {
  33. int length = rawdata.length();
  34. if(length > 0) {
  35. int currentIndex = skipWhiteSpace(rawdata, 0);
  36. int lastIndex = 0;
  37. if(currentIndex < length) {
  38. char currentChar = rawdata.charAt(currentIndex);
  39. while ((currentIndex < length) && (currentChar == ';')) {
  40. String name;
  41. String value;
  42. boolean foundit;
  43. // eat the ';'
  44. ++currentIndex;
  45. // now parse the parameter name
  46. // skip whitespace
  47. currentIndex = skipWhiteSpace(rawdata, currentIndex);
  48. if(currentIndex < length) {
  49. // find the end of the token char run
  50. lastIndex = currentIndex;
  51. currentChar = rawdata.charAt(currentIndex);
  52. while((currentIndex < length) && isTokenChar(currentChar)) {
  53. ++currentIndex;
  54. currentChar = rawdata.charAt(currentIndex);
  55. }
  56. name = rawdata.substring(lastIndex, currentIndex).toLowerCase();
  57. // now parse the '=' that separates the name from the value
  58. // skip whitespace
  59. currentIndex = skipWhiteSpace(rawdata, currentIndex);
  60. if((currentIndex < length) && (rawdata.charAt(currentIndex) == '=')) {
  61. // eat it and parse the parameter value
  62. ++currentIndex;
  63. // skip whitespace
  64. currentIndex = skipWhiteSpace(rawdata, currentIndex);
  65. if(currentIndex < length) {
  66. // now find out whether or not we have a quoted value
  67. currentChar = rawdata.charAt(currentIndex);
  68. if(currentChar == '"') {
  69. // yup it's quoted so eat it and capture the quoted string
  70. ++currentIndex;
  71. lastIndex = currentIndex;
  72. if(currentIndex < length) {
  73. // find the next unescqped quote
  74. foundit = false;
  75. while((currentIndex < length) && !foundit) {
  76. currentChar = rawdata.charAt(currentIndex);
  77. if(currentChar == '\\') {
  78. // found an escape sequence so pass this and the next character
  79. currentIndex += 2;
  80. } else if(currentChar == '"') {
  81. // foundit!
  82. foundit = true;
  83. } else {
  84. ++currentIndex;
  85. }
  86. }
  87. if(currentChar == '"') {
  88. value = unquote(rawdata.substring(lastIndex, currentIndex));
  89. // eat the quote
  90. ++currentIndex;
  91. } else {
  92. throw new MimeTypeParseException("Encountered unterminated quoted parameter value.");
  93. }
  94. } else {
  95. throw new MimeTypeParseException("Encountered unterminated quoted parameter value.");
  96. }
  97. } else if(isTokenChar(currentChar)) {
  98. // nope it's an ordinary token so it ends with a non-token char
  99. lastIndex = currentIndex;
  100. foundit = false;
  101. while((currentIndex < length) && !foundit) {
  102. currentChar = rawdata.charAt(currentIndex);
  103. if(isTokenChar(currentChar)) {
  104. ++currentIndex;
  105. } else {
  106. foundit = true;
  107. }
  108. }
  109. value = rawdata.substring(lastIndex, currentIndex);
  110. } else {
  111. // it ain't a value
  112. throw new MimeTypeParseException("Unexpected character encountered at index " + currentIndex);
  113. }
  114. // now put the data into the hashtable
  115. parameters.put(name, value);
  116. } else {
  117. throw new MimeTypeParseException("Couldn't find a value for parameter named " + name);
  118. }
  119. } else {
  120. throw new MimeTypeParseException("Couldn't find the '=' that separates a parameter name from its value.");
  121. }
  122. } else {
  123. throw new MimeTypeParseException("Couldn't find parameter name");
  124. }
  125. // setup the next iteration
  126. currentIndex = skipWhiteSpace(rawdata, currentIndex);
  127. if(currentIndex < length) {
  128. currentChar = rawdata.charAt(currentIndex);
  129. }
  130. }
  131. if(currentIndex < length) {
  132. throw new MimeTypeParseException("More characters encountered in input than expected.");
  133. }
  134. }
  135. }
  136. }
  137. /**
  138. * return the number of name-value pairs in this list.
  139. */
  140. public int size() {
  141. return parameters.size();
  142. }
  143. /**
  144. * Determine whether or not this list is empty.
  145. */
  146. public boolean isEmpty() {
  147. return parameters.isEmpty();
  148. }
  149. /**
  150. * Retrieve the value associated with the given name, or null if there
  151. * is no current association.
  152. */
  153. public String get(String name) {
  154. return (String)parameters.get(name.trim().toLowerCase());
  155. }
  156. /**
  157. * Set the value to be associated with the given name, replacing
  158. * any previous association.
  159. */
  160. public void set(String name, String value) {
  161. parameters.put(name.trim().toLowerCase(), value);
  162. }
  163. /**
  164. * Remove any value associated with the given name.
  165. */
  166. public void remove(String name) {
  167. parameters.remove(name.trim().toLowerCase());
  168. }
  169. /**
  170. * Retrieve an enumeration of all the names in this list.
  171. */
  172. public Enumeration getNames() {
  173. return parameters.keys();
  174. }
  175. public String toString() {
  176. StringBuffer buffer = new StringBuffer();
  177. buffer.ensureCapacity(parameters.size() * 16); // heuristic: 8 characters per field
  178. Enumeration keys = parameters.keys();
  179. while(keys.hasMoreElements())
  180. {
  181. buffer.append("; ");
  182. String key = (String)keys.nextElement();
  183. buffer.append(key);
  184. buffer.append('=');
  185. buffer.append(quote((String)parameters.get(key)));
  186. }
  187. return buffer.toString();
  188. }
  189. /**
  190. * @return a clone of this object
  191. */
  192. public Object clone() throws CloneNotSupportedException {
  193. return new MimeTypeParameterList(parameters); // clones hashtable ...
  194. }
  195. private Hashtable parameters;
  196. // below here be scary parsing related things
  197. /**
  198. * Determine whether or not a given character belongs to a legal token.
  199. */
  200. private static boolean isTokenChar(char c) {
  201. return ((c > 040) && (c < 0177)) && (TSPECIALS.indexOf(c) < 0);
  202. }
  203. /**
  204. * return the index of the first non white space character in
  205. * rawdata at or after index i.
  206. */
  207. private static int skipWhiteSpace(String rawdata, int i) {
  208. int length = rawdata.length();
  209. if (i < length) {
  210. char c = rawdata.charAt(i);
  211. while ((i < length) && Character.isWhitespace(c)) {
  212. ++i;
  213. c = rawdata.charAt(i);
  214. }
  215. }
  216. return i;
  217. }
  218. /**
  219. * A routine that knows how and when to quote and escape the given value.
  220. */
  221. private static String quote(String value) {
  222. boolean needsQuotes = false;
  223. // check to see if we actually have to quote this thing
  224. int length = value.length();
  225. for(int i = 0; (i < length) && !needsQuotes; ++i) {
  226. needsQuotes = !isTokenChar(value.charAt(i));
  227. }
  228. if(needsQuotes) {
  229. StringBuffer buffer = new StringBuffer();
  230. buffer.ensureCapacity((int)(length * 1.5));
  231. // add the initial quote
  232. buffer.append('"');
  233. // add the properly escaped text
  234. for(int i = 0; i < length; ++i) {
  235. char c = value.charAt(i);
  236. if((c == '\\') || (c == '"')) {
  237. buffer.append('\\');
  238. }
  239. buffer.append(c);
  240. }
  241. // add the closing quote
  242. buffer.append('"');
  243. return buffer.toString();
  244. }
  245. else
  246. {
  247. return value;
  248. }
  249. }
  250. /**
  251. * A routine that knows how to strip the quotes and escape sequences from the given value.
  252. */
  253. private static String unquote(String value) {
  254. int valueLength = value.length();
  255. StringBuffer buffer = new StringBuffer();
  256. buffer.ensureCapacity(valueLength);
  257. boolean escaped = false;
  258. for(int i = 0; i < valueLength; ++i) {
  259. char currentChar = value.charAt(i);
  260. if(!escaped && (currentChar != '\\')) {
  261. buffer.append(currentChar);
  262. } else if(escaped) {
  263. buffer.append(currentChar);
  264. escaped = false;
  265. } else {
  266. escaped = true;
  267. }
  268. }
  269. return buffer.toString();
  270. }
  271. /**
  272. * A string that holds all the special chars.
  273. */
  274. private static final String TSPECIALS = "()<>@,;:\\\"/[]?=";
  275. }