Author: jboynes Date: Sun Dec 26 22:37:14 2004 New Revision: 123393 URL: http://svn.apache.org/viewcvs?view=rev&rev=123393 Log: tighten parsing for MimeType Modified: geronimo/trunk/specs/activation/src/java/javax/activation/MimeType.java geronimo/trunk/specs/activation/src/java/javax/activation/MimeTypeParameterList.java geronimo/trunk/specs/activation/src/test/javax/activation/MimeTypeParameterListTest.java geronimo/trunk/specs/activation/src/test/javax/activation/MimeTypeTest.java
Modified: geronimo/trunk/specs/activation/src/java/javax/activation/MimeType.java Url: http://svn.apache.org/viewcvs/geronimo/trunk/specs/activation/src/java/javax/activation/MimeType.java?view=diff&rev=123393&p1=geronimo/trunk/specs/activation/src/java/javax/activation/MimeType.java&r1=123392&p2=geronimo/trunk/specs/activation/src/java/javax/activation/MimeType.java&r2=123393 ============================================================================== --- geronimo/trunk/specs/activation/src/java/javax/activation/MimeType.java (original) +++ geronimo/trunk/specs/activation/src/java/javax/activation/MimeType.java Sun Dec 26 22:37:14 2004 @@ -27,13 +27,15 @@ * @version $Rev$ $Date$ */ public class MimeType implements Externalizable { - private final static String TYPE_SEPARATOR = "/"; - private final static String PARAMETER_SEPARATOR = ";"; - private final static String STAR_SUB_TYPE = "*"; + private static final String SPECIALS = "()<>@,;:\\\"/[]?="; + + static boolean isSpecial(char c) { + return Character.isWhitespace(c) || Character.isISOControl(c) || SPECIALS.indexOf(c) != -1; + } private String primaryType = "application"; private String subType = "*"; - private MimeTypeParameterList parameterList = new MimeTypeParameterList();; + private final MimeTypeParameterList parameterList = new MimeTypeParameterList();; public MimeType() { } @@ -80,22 +82,18 @@ } public String toString() { - return getBaseType() + - (parameterList == null - ? "" - : PARAMETER_SEPARATOR + parameterList.toString()); + return getBaseType() + parameterList.toString(); } public String getBaseType() { - return getPrimaryType() + TYPE_SEPARATOR + getSubType(); + return getPrimaryType() + '/' + getSubType(); } public boolean match(MimeType type) { - return ( - getPrimaryType().equals(type.getPrimaryType()) - && (getSubType().equals(STAR_SUB_TYPE) - || type.getSubType().equals(STAR_SUB_TYPE) - || getSubType().equals(type.getSubType()))); + if (!primaryType.equals(type.primaryType)) return false; + if ("*".equals(subType)) return true; + if ("*".equals(type.subType)) return true; + return subType.equals(type.subType); } public boolean match(String rawdata) throws MimeTypeParseException { @@ -104,7 +102,6 @@ public void writeExternal(ObjectOutput out) throws IOException { out.writeUTF(toString()); - out.flush(); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { @@ -116,25 +113,28 @@ } private void parseMimeType(String rawData) throws MimeTypeParseException { - int typeSeparatorPos = rawData.indexOf(TYPE_SEPARATOR); - int parameterSeparatorPos = rawData.indexOf(PARAMETER_SEPARATOR); - - if (typeSeparatorPos < 0) { - throw new MimeTypeParseException("Unable to find subtype"); + int index = rawData.indexOf('/'); + if (index == -1) { + throw new MimeTypeParseException("Expected '/'"); } - - setPrimaryType(rawData.substring(0, typeSeparatorPos)); - if (parameterSeparatorPos < 0) { - setSubType(rawData.substring(typeSeparatorPos + 1)); + setPrimaryType(rawData.substring(0, index)); + int index2 = rawData.indexOf(';', index+1); + if (index2 == -1) { + setSubType(rawData.substring(index+1)); } else { - setSubType(rawData.substring(typeSeparatorPos + 1, parameterSeparatorPos)); - parameterList = new MimeTypeParameterList(rawData.substring(parameterSeparatorPos + 1)); + setSubType(rawData.substring(index+1, index2)); + parameterList.parse(rawData.substring(index2)); } } - private static String parseToken(String tokenString) { - // TODO it seems to have unauthorized chars + private static String parseToken(String tokenString) throws MimeTypeParseException { + tokenString = tokenString.trim(); + for (int i=0; i < tokenString.length(); i++) { + char c = tokenString.charAt(i); + if (isSpecial(c)) { + throw new MimeTypeParseException("Special '" + c + "' not allowed in token"); + } + } return tokenString; } - } Modified: geronimo/trunk/specs/activation/src/java/javax/activation/MimeTypeParameterList.java Url: http://svn.apache.org/viewcvs/geronimo/trunk/specs/activation/src/java/javax/activation/MimeTypeParameterList.java?view=diff&rev=123393&p1=geronimo/trunk/specs/activation/src/java/javax/activation/MimeTypeParameterList.java&r1=123392&p2=geronimo/trunk/specs/activation/src/java/javax/activation/MimeTypeParameterList.java&r2=123393 ============================================================================== --- geronimo/trunk/specs/activation/src/java/javax/activation/MimeTypeParameterList.java (original) +++ geronimo/trunk/specs/activation/src/java/javax/activation/MimeTypeParameterList.java Sun Dec 26 22:37:14 2004 @@ -20,22 +20,18 @@ import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; -import java.util.Map; -import java.util.StringTokenizer; import java.util.Iterator; +import java.util.Map; /** * @version $Rev$ $Date$ */ public class MimeTypeParameterList { - private final static String PARAMETER_SEPARATOR = ";"; - private final static String NAME_VALUE_SEPARATOR = "="; - Map _mimeTypeParameterMap = new HashMap(); + private final Map params = new HashMap(); public MimeTypeParameterList() { - } public MimeTypeParameterList(String parameterList) throws MimeTypeParseException { @@ -44,79 +40,172 @@ protected void parse(String parameterList) throws MimeTypeParseException { if (parameterList == null) { - return; + throw new MimeTypeParseException("parameterList is null"); } - StringTokenizer tokenizer = new StringTokenizer(parameterList, PARAMETER_SEPARATOR); - while (tokenizer.hasMoreTokens()) { - String parameter = tokenizer.nextToken(); - if (parameter.length() == 0) { - continue; - } - int eq = parameter.indexOf(NAME_VALUE_SEPARATOR); - String name = null; - if (eq > -1) { - name = parseToken(parameter.substring(0, eq)); - } - String value = parseToken(parameter.substring(eq + 1)); - if ((name == null || name.length() == 0) && value.length() == 0) { - continue; - } - if (name.length() == 0 || value.length() == 0) { - throw new MimeTypeParseException("Name or value is Missing"); - } - set(name, value); - + RFC2045Parser parser = new RFC2045Parser(parameterList); + while (parser.hasMoreParams()) { + String attribute = parser.expectAttribute(); + parser.expectEquals(); + String value = parser.expectValue(); + params.put(attribute, value); } - } public int size() { - return _mimeTypeParameterMap.size(); + return params.size(); } public boolean isEmpty() { - return _mimeTypeParameterMap.isEmpty(); + return params.isEmpty(); } public String get(String name) { - return (String) _mimeTypeParameterMap.get(name); + return (String) params.get(name); } public void set(String name, String value) { - name = parseToken(name); - value = parseToken(value); - _mimeTypeParameterMap.put(name, value); + params.put(name, value); } public void remove(String name) { - _mimeTypeParameterMap.remove(name); + params.remove(name); } public Enumeration getNames() { - return Collections.enumeration(_mimeTypeParameterMap.keySet()); + return Collections.enumeration(params.keySet()); } + /** + * String representation of this parameter list. + * + * @return + */ public String toString() { - StringBuffer buf = new StringBuffer(_mimeTypeParameterMap.size() << 4); - for (Iterator i = _mimeTypeParameterMap.entrySet().iterator(); i.hasNext();) { + StringBuffer buf = new StringBuffer(params.size() << 4); + for (Iterator i = params.entrySet().iterator(); i.hasNext();) { Map.Entry entry = (Map.Entry) i.next(); - buf.append("; ").append(entry.getKey()).append('=').append(entry.getValue()); + buf.append("; ").append(entry.getKey()).append('='); + quote(buf, (String) entry.getValue()); } return buf.toString(); } - private String parseToken(String token) { - // TODO it seems to have unauthorized chars - return removeBlank(token); + private void quote(StringBuffer buf, String value) { + int length = value.length(); + boolean quote = false; + for (int i = 0; i < length; i++) { + if (MimeType.isSpecial(value.charAt(i))) { + quote = true; + break; + } + } + if (quote) { + buf.append('"'); + for (int i = 0; i < length; i++) { + char c = value.charAt(i); + if (c == '\\' || c == '"') { + buf.append('\\'); + } + buf.append(c); + } + buf.append('"'); + } else { + buf.append(value); + } } - private String removeBlank(String str) { - StringBuffer buf = new StringBuffer(); - StringTokenizer tokenizer = new StringTokenizer(str); - while (tokenizer.hasMoreTokens()) { - buf.append(tokenizer.nextToken()); + private static class RFC2045Parser { + private final String text; + private int index = 0; + + private RFC2045Parser(String text) { + this.text = text; + } + + /** + * Look the next ";" to start a parameter (skipping whitespace) + * + * @return + */ + private boolean hasMoreParams() throws MimeTypeParseException { + char c; + do { + if (index == text.length()) { + return false; + } + c = text.charAt(index++); + } while (Character.isWhitespace(c)); + if (c != ';') { + throw new MimeTypeParseException("Expected \";\" at " + (index - 1) + " in " + text); + } + return true; + } + + private String expectAttribute() throws MimeTypeParseException { + char c; + do { + if (index == text.length()) { + throw new MimeTypeParseException("Expected attribute at " + (index - 1) + " in " + text); + } + c = text.charAt(index++); + } while (Character.isWhitespace(c)); + int start = index - 1; + while (index != text.length() && !MimeType.isSpecial(text.charAt(index))) { + index += 1; + } + return text.substring(start, index); + } + + private void expectEquals() throws MimeTypeParseException { + char c; + do { + if (index == text.length()) { + throw new MimeTypeParseException("Expected \"=\" at " + (index - 1) + " in " + text); + } + c = text.charAt(index++); + } while (Character.isWhitespace(c)); + if (c != '=') { + throw new MimeTypeParseException("Expected \"=\" at " + (index - 1) + " in " + text); + } + } + + private String expectValue() throws MimeTypeParseException { + char c; + do { + if (index == text.length()) { + throw new MimeTypeParseException("Expected value at " + (index - 1) + " in " + text); + } + c = text.charAt(index++); + } while (Character.isWhitespace(c)); + if (c == '"') { + // quoted-string + StringBuffer buf = new StringBuffer(); + while (true) { + if (index == text.length()) { + throw new MimeTypeParseException("Expected closing quote at " + (index - 1) + " in " + text); + } + c = text.charAt(index++); + if (c == '"') { + break; + } + if (c == '\\') { + if (index == text.length()) { + throw new MimeTypeParseException("Expected escaped char at " + (index - 1) + " in " + text); + } + c = text.charAt(index++); + } + buf.append(c); + } + return buf.toString(); + } else { + // token + int start = index - 1; + while (index != text.length() && !MimeType.isSpecial(text.charAt(index))) { + index += 1; + } + return text.substring(start, index); + } } - return buf.toString(); } } Modified: geronimo/trunk/specs/activation/src/test/javax/activation/MimeTypeParameterListTest.java Url: http://svn.apache.org/viewcvs/geronimo/trunk/specs/activation/src/test/javax/activation/MimeTypeParameterListTest.java?view=diff&rev=123393&p1=geronimo/trunk/specs/activation/src/test/javax/activation/MimeTypeParameterListTest.java&r1=123392&p2=geronimo/trunk/specs/activation/src/test/javax/activation/MimeTypeParameterListTest.java&r2=123393 ============================================================================== --- geronimo/trunk/specs/activation/src/test/javax/activation/MimeTypeParameterListTest.java (original) +++ geronimo/trunk/specs/activation/src/test/javax/activation/MimeTypeParameterListTest.java Sun Dec 26 22:37:14 2004 @@ -17,6 +17,8 @@ package javax.activation; +import java.util.Enumeration; + import junit.framework.TestCase; @@ -25,31 +27,44 @@ * @version $Rev$ $Date$ */ public class MimeTypeParameterListTest extends TestCase { - protected void setUp() throws Exception { - super.setUp(); - } + private MimeTypeParameterList parameterList; + + protected void setUp() throws Exception { + super.setUp(); + parameterList = new MimeTypeParameterList(); + } public void testEmptyParameterList() { - MimeTypeParameterList parameterList = new MimeTypeParameterList(); assertEquals(0, parameterList.size()); + assertTrue(parameterList.isEmpty()); } public void testSimpleParameterList() throws MimeTypeParseException { - MimeTypeParameterList parameterList = new MimeTypeParameterList(";name=value"); + parameterList.parse(";name=value"); assertEquals(1, parameterList.size()); - assertEquals("name", parameterList.getNames().nextElement()); + assertFalse(parameterList.isEmpty()); + Enumeration e = parameterList.getNames(); + assertTrue(e.hasMoreElements()); + assertEquals("name", e.nextElement()); + assertFalse(e.hasMoreElements()); assertEquals("value", parameterList.get("name")); } + public void testQuotedValue() throws MimeTypeParseException { + parameterList.parse(";name=\"val()ue\""); + assertEquals(1, parameterList.size()); + assertEquals("val()ue", parameterList.get("name")); + } + public void testWhiteSpacesParameterList() throws MimeTypeParseException { - MimeTypeParameterList parameterList = new MimeTypeParameterList("; name= value ; "); + parameterList.parse("; name= value"); assertEquals(1, parameterList.size()); assertEquals("name", parameterList.getNames().nextElement()); assertEquals("value", parameterList.get("name")); } public void testLongParameterList() throws MimeTypeParseException { - MimeTypeParameterList parameterList = new MimeTypeParameterList(";name1=value1; name2 = value2; name3=value3;name4 = value4"); + parameterList.parse(";name1=value1; name2 = value2; name3=value3;name4 = value4"); assertEquals(4, parameterList.size()); assertEquals("value1", parameterList.get("name1")); assertEquals("value2", parameterList.get("name2")); @@ -59,20 +74,50 @@ public void testNoValueParameterList() { try { - new MimeTypeParameterList("; name="); + parameterList.parse("; name="); + fail("Expected MimeTypeParseException"); + } catch (MimeTypeParseException e) { + // ok + } + } + + public void testMissingValueParameterList() { + try { + parameterList.parse("; name=;name2=value"); fail("Expected MimeTypeParseException"); - } catch (MimeTypeParseException mtpEx) { + } catch (MimeTypeParseException e) { // ok } } public void testNoNameParameterList() { try { - new MimeTypeParameterList("; = value"); + parameterList.parse("; = value"); + fail("Expected MimeTypeParseException"); + } catch (MimeTypeParseException e) { + // ok + } + } + + public void testUnterminatedQuotedString() { + try { + parameterList.parse("; = \"value"); fail("Expected MimeTypeParseException"); - } catch (MimeTypeParseException mtpEx) { + } catch (MimeTypeParseException e) { // ok } + } + + public void testSpecialInAttribute() { + String specials = "()<>@,;:\\\"/[]?= \t"; + for (int i=0; i < specials.length(); i++) { + try { + parameterList.parse(";na"+specials.charAt(i)+"me=value"); + fail("Expected MimeTypeParseException for special: " + specials.charAt(i)); + } catch (MimeTypeParseException e) { + // ok + } + } } } Modified: geronimo/trunk/specs/activation/src/test/javax/activation/MimeTypeTest.java Url: http://svn.apache.org/viewcvs/geronimo/trunk/specs/activation/src/test/javax/activation/MimeTypeTest.java?view=diff&rev=123393&p1=geronimo/trunk/specs/activation/src/test/javax/activation/MimeTypeTest.java&r1=123392&p2=geronimo/trunk/specs/activation/src/test/javax/activation/MimeTypeTest.java&r2=123393 ============================================================================== --- geronimo/trunk/specs/activation/src/test/javax/activation/MimeTypeTest.java (original) +++ geronimo/trunk/specs/activation/src/test/javax/activation/MimeTypeTest.java Sun Dec 26 22:37:14 2004 @@ -17,6 +17,10 @@ package javax.activation; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; + import junit.framework.TestCase; @@ -25,14 +29,7 @@ * @version $Rev$ $Date$ */ public class MimeTypeTest extends TestCase { - - private final static String DEFAULT_PRIMARY_TYPE = "application"; - private final static String DEFAULT_SUB_TYPE = "*"; - - private String defaultRawdata; - private String primary; - private String sub; - private String withParamsRawdata; + private MimeType mimeType; public MimeTypeTest(String name) { super(name); @@ -40,45 +37,249 @@ public void setUp() throws Exception { super.setUp(); - defaultRawdata = "application/*;"; - primary = "primary"; - sub = "sub"; - withParamsRawdata = primary + "/" + sub + "; name1 =value1; name2 = value2;"; + mimeType = new MimeType(); } - public void test1DefaultConstructor() { - MimeType mimeType = new MimeType(); - assertEquals(DEFAULT_PRIMARY_TYPE, mimeType.getPrimaryType()); - assertEquals(DEFAULT_SUB_TYPE, mimeType.getSubType()); - assertEquals(0, mimeType.getParameters().size()); + public void testDefaultConstructor() throws MimeTypeParseException { + assertEquals("application/*", mimeType.getBaseType()); + assertEquals("application", mimeType.getPrimaryType()); + // not sure as RFC2045 does not allow "*" but this is what the RI does + assertEquals("*", mimeType.getSubType()); + assertTrue(mimeType.match(new MimeType())); - } + assertTrue(mimeType.match(new MimeType("application/*"))); - public void test2OthersConstructor() throws MimeTypeParseException { - MimeType mimeType = new MimeType(defaultRawdata); - MimeType defaultMimeType = new MimeType(); - assertEquals(defaultMimeType.getBaseType(), mimeType.getBaseType()); - assertTrue(mimeType.match(defaultMimeType)); - - mimeType = new MimeType(withParamsRawdata); - assertEquals(primary, mimeType.getPrimaryType()); - assertEquals(sub, mimeType.getSubType()); - assertEquals(2, mimeType.getParameters().size()); - assertEquals("value1", mimeType.getParameter("name1")); - - MimeType mimeType2 = new MimeType(primary, sub); - assertEquals(primary, mimeType2.getPrimaryType()); - assertEquals(sub, mimeType2.getSubType()); - assertTrue(mimeType2.match(mimeType)); + assertNull(mimeType.getParameter("foo")); + assertEquals(0, mimeType.getParameters().size()); + assertTrue(mimeType.getParameters().isEmpty()); } - public void test3MatchMethods() throws MimeTypeParseException { - assertTrue(new MimeType().match(new MimeType())); - assertTrue(new MimeType().match(defaultRawdata)); + public void testMimeTypeConstructor() throws MimeTypeParseException { + mimeType = new MimeType("text/plain"); + assertEquals("text/plain", mimeType.getBaseType()); + assertEquals("text", mimeType.getPrimaryType()); + assertEquals("plain", mimeType.getSubType()); + assertEquals("text/plain", mimeType.toString()); } - public void test4ExternalMethods() { - // TODO - } + public void testTypeConstructor() throws MimeTypeParseException { + mimeType = new MimeType("text", "plain"); + assertEquals("text/plain", mimeType.getBaseType()); + assertEquals("text", mimeType.getPrimaryType()); + assertEquals("plain", mimeType.getSubType()); + assertEquals("text/plain", mimeType.toString()); + } + + public void testConstructorWithParams() throws MimeTypeParseException { + mimeType = new MimeType("text/plain; charset=\"iso-8859-1\""); + assertEquals("text/plain", mimeType.getBaseType()); + assertEquals("text", mimeType.getPrimaryType()); + assertEquals("plain", mimeType.getSubType()); + MimeTypeParameterList params = mimeType.getParameters(); + assertEquals(1, params.size()); + assertEquals("iso-8859-1", params.get("charset")); + assertEquals("text/plain; charset=iso-8859-1", mimeType.toString()); + } + + public void testConstructorWithQuotableParams() throws MimeTypeParseException { + mimeType = new MimeType("text/plain; charset=\"iso(8859)\""); + assertEquals("text/plain", mimeType.getBaseType()); + assertEquals("text", mimeType.getPrimaryType()); + assertEquals("plain", mimeType.getSubType()); + MimeTypeParameterList params = mimeType.getParameters(); + assertEquals(1, params.size()); + assertEquals("iso(8859)", params.get("charset")); + assertEquals("text/plain; charset=\"iso(8859)\"", mimeType.toString()); + } + + public void testWriteExternal() throws MimeTypeParseException, IOException { + mimeType = new MimeType("text/plain; charset=iso8859-1"); + mimeType.writeExternal(new ObjectOutput() { + public void writeUTF(String str) { + assertEquals("text/plain; charset=iso8859-1", str); + } + + public void close() { + fail(); + } + + public void flush() { + fail(); + } + + public void write(int b) { + fail(); + } + + public void write(byte b[]) { + fail(); + } + + public void write(byte b[], int off, int len) { + fail(); + } + + public void writeObject(Object obj) { + fail(); + } + + public void writeDouble(double v) { + fail(); + } + + public void writeFloat(float v) { + fail(); + } + + public void writeByte(int v) { + fail(); + } + + public void writeChar(int v) { + fail(); + } + + public void writeInt(int v) { + fail(); + } + + public void writeShort(int v) { + fail(); + } + + public void writeLong(long v) { + fail(); + } + + public void writeBoolean(boolean v) { + fail(); + } + + public void writeBytes(String s) { + fail(); + } + + public void writeChars(String s){ + fail(); + } + }); + } + + public void testReadExternal() throws IOException, ClassNotFoundException { + mimeType.readExternal(new ObjectInput() { + public String readUTF() { + return "text/plain; charset=iso-8859-1"; + } + + public int available() { + fail(); + throw new AssertionError(); + } + + public int read() { + fail(); + throw new AssertionError(); + } + + public void close() { + fail(); + throw new AssertionError(); + } + + public long skip(long n) { + fail(); + throw new AssertionError(); + } + + public int read(byte b[]) { + fail(); + throw new AssertionError(); + } + + public int read(byte b[], int off, int len) { + fail(); + throw new AssertionError(); + } + + public Object readObject() { + fail(); + throw new AssertionError(); + } + + public byte readByte() { + fail(); + throw new AssertionError(); + } + + public char readChar() { + fail(); + throw new AssertionError(); + } + + public double readDouble() { + fail(); + throw new AssertionError(); + } + + public float readFloat() { + fail(); + throw new AssertionError(); + } + + public int readInt() { + fail(); + throw new AssertionError(); + } + + public int readUnsignedByte() { + fail(); + throw new AssertionError(); + } + + public int readUnsignedShort() { + fail(); + throw new AssertionError(); + } + + public long readLong() { + fail(); + throw new AssertionError(); + } + + public short readShort() { + fail(); + throw new AssertionError(); + } + + public boolean readBoolean() { + fail(); + throw new AssertionError(); + } + + public int skipBytes(int n) { + fail(); + throw new AssertionError(); + } + + public void readFully(byte b[]) { + fail(); + } + + public void readFully(byte b[], int off, int len) { + fail(); + } + + public String readLine() { + fail(); + throw new AssertionError(); + } + }); + assertEquals("text/plain", mimeType.getBaseType()); + assertEquals("text", mimeType.getPrimaryType()); + assertEquals("plain", mimeType.getSubType()); + MimeTypeParameterList params = mimeType.getParameters(); + assertEquals(1, params.size()); + assertEquals("iso-8859-1", params.get("charset")); + assertEquals("text/plain; charset=iso-8859-1", mimeType.toString()); + } } -