414101 Do not escape special characters in cookies

This commit is contained in:
Greg Wilkins 2013-08-09 12:42:22 +10:00
parent 914392e80d
commit a779425994
6 changed files with 182 additions and 102 deletions

View File

@ -18,6 +18,9 @@
package org.eclipse.jetty.http; package org.eclipse.jetty.http;
import static org.eclipse.jetty.util.QuotedStringTokenizer.isQuoted;
import static org.eclipse.jetty.util.QuotedStringTokenizer.quoteOnly;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@ -54,23 +57,27 @@ import org.eclipse.jetty.util.log.Logger;
/** /**
* HTTP Fields. A collection of HTTP header and or Trailer fields. * HTTP Fields. A collection of HTTP header and or Trailer fields.
* *
* <p>This class is not synchronized as it is expected that modifications will only be performed by a * <p>This class is not synchronised as it is expected that modifications will only be performed by a
* single thread. * single thread.
*
* <p>The cookie handling provided by this class is guided by the Servlet specification and RFC6265.
* *
*/ */
public class HttpFields implements Iterable<HttpField> public class HttpFields implements Iterable<HttpField>
{ {
private static final Logger LOG = Log.getLogger(HttpFields.class); private static final Logger LOG = Log.getLogger(HttpFields.class);
public static final String __COOKIE_DELIM="\"\\\n\r\t\f\b%+ ;=";
public static final TimeZone __GMT = TimeZone.getTimeZone("GMT"); public static final TimeZone __GMT = TimeZone.getTimeZone("GMT");
public static final DateCache __dateCache = new DateCache("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US); public static final DateCache __dateCache = new DateCache("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
public static final String __COOKIE_DELIM_PATH="\"\\\t%+ :;,@?=()<>{}[]";
public static final String __COOKIE_DELIM=__COOKIE_DELIM_PATH+"/";
static static
{ {
__GMT.setID("GMT"); __GMT.setID("GMT");
__dateCache.setTimeZone(__GMT); __dateCache.setTimeZone(__GMT);
} }
public final static String __separators = ", \t"; public final static String __separators = ", \t";
private static final String[] DAYS = private static final String[] DAYS =
@ -808,69 +815,87 @@ public class HttpFields implements Iterable<HttpField>
final boolean isHttpOnly, final boolean isHttpOnly,
int version) int version)
{ {
String delim=__COOKIE_DELIM;
// Check arguments // Check arguments
if (name == null || name.length() == 0) if (name == null || name.length() == 0)
throw new IllegalArgumentException("Bad cookie name"); throw new IllegalArgumentException("Bad cookie name");
// Format value and params // Format value and params
StringBuilder buf = new StringBuilder(128); StringBuilder buf = new StringBuilder(128);
String name_value_params;
QuotedStringTokenizer.quoteIfNeeded(buf, name, delim);
buf.append('=');
String start=buf.toString();
boolean hasDomain = false;
boolean hasPath = false;
if (value != null && value.length() > 0) // Name is checked by servlet spec, but can also be passed directly so check again
QuotedStringTokenizer.quoteIfNeeded(buf, value, delim); boolean quote_name=isQuoteNeededForCookie(name);
quoteOnlyOrAppend(buf,name,quote_name);
buf.append('=');
// Remember name= part to look for other matching set-cookie
String name_equals=buf.toString();
// Append the value
boolean quote_value=isQuoteNeededForCookie(value);
quoteOnlyOrAppend(buf,value,quote_value);
if (path != null && path.length() > 0) // Look for domain and path fields and check if they need to be quoted
boolean has_domain = domain!=null && domain.length()>0;
boolean quote_domain = has_domain && isQuoteNeededForCookie(domain);
boolean has_path = path!=null && path.length()>0;
boolean quote_path = has_path && isQuoteNeededForCookiePath(path);
// Upgrade the version if we have a comment or we need to quote value/path/domain or if they were already quoted
if (version==0 && ( comment!=null || quote_name || quote_value || quote_domain || quote_path || isQuoted(name) || isQuoted(value) || isQuoted(path) || isQuoted(domain)))
version=1;
// Append version
if (version==1)
buf.append (";Version=1");
else if (version>1)
buf.append (";Version=").append(version);
// Append path
if (has_path)
{ {
hasPath = true;
buf.append(";Path="); buf.append(";Path=");
if (path.trim().startsWith("\"")) quoteOnlyOrAppend(buf,path,quote_path);
buf.append(path);
else
QuotedStringTokenizer.quoteIfNeeded(buf,path,delim);
} }
if (domain != null && domain.length() > 0)
// Append domain
if (has_domain)
{ {
hasDomain = true;
buf.append(";Domain="); buf.append(";Domain=");
QuotedStringTokenizer.quoteIfNeeded(buf,domain.toLowerCase(Locale.ENGLISH),delim); quoteOnlyOrAppend(buf,domain,quote_domain);
} }
// Handle max-age and/or expires
if (maxAge >= 0) if (maxAge >= 0)
{ {
// Always add the expires param as some browsers still don't handle max-age // Always use expires
// This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies
buf.append(";Expires="); buf.append(";Expires=");
if (maxAge == 0) if (maxAge == 0)
buf.append(__01Jan1970_COOKIE); buf.append(__01Jan1970_COOKIE);
else else
formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge); formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge);
buf.append(";Max-Age="); // for v1 cookies, also send max-age
buf.append(maxAge); if (version>=1)
{
buf.append(";Max-Age=");
buf.append(maxAge);
}
} }
// add the other fields
if (isSecure) if (isSecure)
buf.append(";Secure"); buf.append(";Secure");
if (isHttpOnly) if (isHttpOnly)
buf.append(";HttpOnly"); buf.append(";HttpOnly");
if (comment != null)
if (comment != null && comment.length() > 0)
{ {
buf.append(";Comment="); buf.append(";Comment=");
QuotedStringTokenizer.quoteIfNeeded(buf, comment, delim); quoteOnlyOrAppend(buf,comment,isQuoteNeededForCookie(comment));
} }
name_value_params = buf.toString(); // remove any existing set-cookie fields of same name
// remove existing set-cookie of same name
Iterator<HttpField> i=_fields.iterator(); Iterator<HttpField> i=_fields.iterator();
while (i.hasNext()) while (i.hasNext())
{ {
@ -878,26 +903,26 @@ public class HttpFields implements Iterable<HttpField>
if (field.getHeader()==HttpHeader.SET_COOKIE) if (field.getHeader()==HttpHeader.SET_COOKIE)
{ {
String val = (field.getValue() == null ? null : field.getValue().toString()); String val = (field.getValue() == null ? null : field.getValue().toString());
if (val!=null && val.startsWith(start)) if (val!=null && val.startsWith(name_equals))
{ {
//existing cookie has same name, does it also match domain and path? //existing cookie has same name, does it also match domain and path?
if (((!hasDomain && !val.contains("Domain")) || (hasDomain && val.contains("Domain="+domain))) && if (((!has_domain && !val.contains("Domain")) || (has_domain && val.contains(domain))) &&
((!hasPath && !val.contains("Path")) || (hasPath && val.contains("Path="+path)))) ((!has_path && !val.contains("Path")) || (has_path && val.contains(path))))
{ {
i.remove(); i.remove();
} }
} }
} }
} }
add(HttpHeader.SET_COOKIE.toString(), name_value_params); // add the set cookie
add(HttpHeader.SET_COOKIE.toString(), buf.toString());
// Expire responses with set-cookie headers so they do not get cached. // Expire responses with set-cookie headers so they do not get cached.
put(HttpHeader.EXPIRES.toString(), __01Jan1970); put(HttpHeader.EXPIRES.toString(), __01Jan1970);
} }
public void putTo(ByteBuffer bufferInFillMode) throws IOException public void putTo(ByteBuffer bufferInFillMode)
{ {
for (HttpField field : _fields) for (HttpField field : _fields)
{ {
@ -1095,19 +1120,20 @@ public class HttpFields implements Iterable<HttpField>
} }
} }
List vl = LazyList.getList(list, false); List<String> vl = LazyList.getList(list, false);
if (vl.size() < 2) return vl; if (vl.size() < 2)
return vl;
List ql = LazyList.getList(qual, false); List<Float> ql = LazyList.getList(qual, false);
// sort list with swaps // sort list with swaps
Float last = __zero; Float last = __zero;
for (int i = vl.size(); i-- > 0;) for (int i = vl.size(); i-- > 0;)
{ {
Float q = (Float) ql.get(i); Float q = ql.get(i);
if (last.compareTo(q) > 0) if (last.compareTo(q) > 0)
{ {
Object tmp = vl.get(i); String tmp = vl.get(i);
vl.set(i, vl.get(i + 1)); vl.set(i, vl.get(i + 1));
vl.set(i + 1, tmp); vl.set(i + 1, tmp);
ql.set(i, ql.get(i + 1)); ql.set(i, ql.get(i + 1));
@ -1123,4 +1149,66 @@ public class HttpFields implements Iterable<HttpField>
} }
/* ------------------------------------------------------------ */
/** Does a cookie value need to be quoted?
* @param s value string
* @return true if quoted;
* @throws IllegalArgumentException If there a control characters in the string
*/
public static boolean isQuoteNeededForCookie(String s)
{
if (s==null || s.length()==0)
return true;
if (QuotedStringTokenizer.isQuoted(s))
return false;
for (int i=0;i<s.length();i++)
{
char c = s.charAt(i);
if (__COOKIE_DELIM.indexOf(c)>=0)
return true;
if (c<0x20 || c>=0x7f)
throw new IllegalArgumentException("Illegal character in cookie value");
}
return false;
}
/* ------------------------------------------------------------ */
/** Does a cookie path need to be quoted?
* @param s value string
* @return true if quoted;
* @throws IllegalArgumentException If there a control characters in the string
*/
public static boolean isQuoteNeededForCookiePath(String s)
{
if (s==null || s.length()==0)
return true;
if (QuotedStringTokenizer.isQuoted(s))
return false;
for (int i=0;i<s.length();i++)
{
char c = s.charAt(i);
if (__COOKIE_DELIM_PATH.indexOf(c)>=0)
return true;
if (c<0x20 || c>=0x7f)
throw new IllegalArgumentException("Illegal character in cookie value");
}
return false;
}
private static void quoteOnlyOrAppend(StringBuilder buf, String s, boolean quote)
{
if (quote)
QuotedStringTokenizer.quoteOnly(buf,s);
else
buf.append(s);
}
} }

View File

@ -279,10 +279,10 @@ public class HttpFieldsTest
//test cookies with same name, domain and path, only 1 allowed //test cookies with same name, domain and path, only 1 allowed
fields.addSetCookie("everything","wrong","domain","path",0,"to be replaced",true,true,0); fields.addSetCookie("everything","wrong","domain","path",0,"to be replaced",true,true,0);
fields.addSetCookie("everything","value","domain","path",0,"comment",true,true,0); fields.addSetCookie("everything","value","domain","path",0,"comment",true,true,0);
assertEquals("everything=value;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",fields.getStringField("Set-Cookie")); assertEquals("everything=value;Version=1;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",fields.getStringField("Set-Cookie"));
Enumeration<String> e =fields.getValues("Set-Cookie"); Enumeration<String> e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements()); assertTrue(e.hasMoreElements());
assertEquals("everything=value;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement()); assertEquals("everything=value;Version=1;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
assertFalse(e.hasMoreElements()); assertFalse(e.hasMoreElements());
assertEquals("Thu, 01 Jan 1970 00:00:00 GMT",fields.getStringField("Expires")); assertEquals("Thu, 01 Jan 1970 00:00:00 GMT",fields.getStringField("Expires"));
assertFalse(e.hasMoreElements()); assertFalse(e.hasMoreElements());
@ -293,9 +293,9 @@ public class HttpFieldsTest
fields.addSetCookie("everything","value","domain2","path",0,"comment",true,true,0); fields.addSetCookie("everything","value","domain2","path",0,"comment",true,true,0);
e =fields.getValues("Set-Cookie"); e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements()); assertTrue(e.hasMoreElements());
assertEquals("everything=other;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement()); assertEquals("everything=other;Version=1;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
assertTrue(e.hasMoreElements()); assertTrue(e.hasMoreElements());
assertEquals("everything=value;Path=path;Domain=domain2;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement()); assertEquals("everything=value;Version=1;Path=path;Domain=domain2;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
assertFalse(e.hasMoreElements()); assertFalse(e.hasMoreElements());
//test cookies with same name, same path, one with domain, one without //test cookies with same name, same path, one with domain, one without
@ -304,9 +304,9 @@ public class HttpFieldsTest
fields.addSetCookie("everything","value","","path",0,"comment",true,true,0); fields.addSetCookie("everything","value","","path",0,"comment",true,true,0);
e =fields.getValues("Set-Cookie"); e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements()); assertTrue(e.hasMoreElements());
assertEquals("everything=other;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement()); assertEquals("everything=other;Version=1;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
assertTrue(e.hasMoreElements()); assertTrue(e.hasMoreElements());
assertEquals("everything=value;Path=path;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement()); assertEquals("everything=value;Version=1;Path=path;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
assertFalse(e.hasMoreElements()); assertFalse(e.hasMoreElements());
@ -316,9 +316,9 @@ public class HttpFieldsTest
fields.addSetCookie("everything","value","domain1","path2",0,"comment",true,true,0); fields.addSetCookie("everything","value","domain1","path2",0,"comment",true,true,0);
e =fields.getValues("Set-Cookie"); e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements()); assertTrue(e.hasMoreElements());
assertEquals("everything=other;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement()); assertEquals("everything=other;Version=1;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
assertTrue(e.hasMoreElements()); assertTrue(e.hasMoreElements());
assertEquals("everything=value;Path=path2;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement()); assertEquals("everything=value;Version=1;Path=path2;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
assertFalse(e.hasMoreElements()); assertFalse(e.hasMoreElements());
//test cookies with same name, same domain, one with path, one without //test cookies with same name, same domain, one with path, one without
@ -327,9 +327,9 @@ public class HttpFieldsTest
fields.addSetCookie("everything","value","domain1","",0,"comment",true,true,0); fields.addSetCookie("everything","value","domain1","",0,"comment",true,true,0);
e =fields.getValues("Set-Cookie"); e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements()); assertTrue(e.hasMoreElements());
assertEquals("everything=other;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement()); assertEquals("everything=other;Version=1;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
assertTrue(e.hasMoreElements()); assertTrue(e.hasMoreElements());
assertEquals("everything=value;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement()); assertEquals("everything=value;Version=1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
assertFalse(e.hasMoreElements()); assertFalse(e.hasMoreElements());
//test cookies same name only, no path, no domain //test cookies same name only, no path, no domain
@ -338,13 +338,13 @@ public class HttpFieldsTest
fields.addSetCookie("everything","value","","",0,"comment",true,true,0); fields.addSetCookie("everything","value","","",0,"comment",true,true,0);
e =fields.getValues("Set-Cookie"); e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements()); assertTrue(e.hasMoreElements());
assertEquals("everything=value;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement()); assertEquals("everything=value;Version=1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
assertFalse(e.hasMoreElements()); assertFalse(e.hasMoreElements());
fields.clear(); fields.clear();
fields.addSetCookie("ev erything","va lue","do main","pa th",1,"co mment",true,true,2); fields.addSetCookie("ev erything","va lue","do main","pa th",1,"co mment",true,true,1);
String setCookie=fields.getStringField("Set-Cookie"); String setCookie=fields.getStringField("Set-Cookie");
assertThat(setCookie,Matchers.startsWith("\"ev erything\"=\"va lue\";Path=\"pa th\";Domain=\"do main\";Expires=")); assertThat(setCookie,Matchers.startsWith("\"ev erything\"=\"va lue\";Version=1;Path=\"pa th\";Domain=\"do main\";Expires="));
assertThat(setCookie,Matchers.endsWith(" GMT;Max-Age=1;Secure;HttpOnly;Comment=\"co mment\"")); assertThat(setCookie,Matchers.endsWith(" GMT;Max-Age=1;Secure;HttpOnly;Comment=\"co mment\""));
fields.clear(); fields.clear();
@ -376,7 +376,7 @@ public class HttpFieldsTest
fields=new HttpFields(); fields=new HttpFields();
fields.addSetCookie("name","value==",null,null,-1,null,false,false,0); fields.addSetCookie("name","value==",null,null,-1,null,false,false,0);
setCookie=fields.getStringField("Set-Cookie"); setCookie=fields.getStringField("Set-Cookie");
assertEquals("name=\"value==\"",setCookie); assertEquals("name=\"value==\";Version=1",setCookie);
} }

View File

@ -601,7 +601,7 @@ public class ResponseTest
String set = response.getHttpFields().getStringField("Set-Cookie"); String set = response.getHttpFields().getStringField("Set-Cookie");
assertEquals("name=value;Path=/path;Domain=domain;Secure;HttpOnly;Comment=comment", set); assertEquals("name=value;Version=1;Path=/path;Domain=domain;Secure;HttpOnly;Comment=comment", set);
} }
@ -630,7 +630,7 @@ public class ResponseTest
assertNotNull(set); assertNotNull(set);
ArrayList<String> list = Collections.list(set); ArrayList<String> list = Collections.list(set);
assertEquals(2, list.size()); assertEquals(2, list.size());
assertTrue(list.contains("name=value;Path=/path;Domain=domain;Secure;HttpOnly;Comment=comment")); assertTrue(list.contains("name=value;Version=1;Path=/path;Domain=domain;Secure;HttpOnly;Comment=comment"));
assertTrue(list.contains("name2=value2;Path=/path;Domain=domain")); assertTrue(list.contains("name2=value2;Path=/path;Domain=domain"));
//get rid of the cookies //get rid of the cookies

View File

@ -189,9 +189,9 @@ public class ServerHTTPSPDYTest extends AbstractHTTPSPDYTest
assertThat("response code is 200 OK", replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value() assertThat("response code is 200 OK", replyHeaders.get(HTTPSPDYHeader.STATUS.name(version)).value()
.contains("200"), is(true)); .contains("200"), is(true));
assertThat(replyInfo.getHeaders().get("Set-Cookie").values()[0], is(cookie1 + "=\"" + cookie1Value + assertThat(replyInfo.getHeaders().get("Set-Cookie").values()[0], is(cookie1 + "=\"" + cookie1Value +
"\"")); "\";Version=1"));
assertThat(replyInfo.getHeaders().get("Set-Cookie").values()[1], is(cookie2 + "=\"" + cookie2Value + assertThat(replyInfo.getHeaders().get("Set-Cookie").values()[1], is(cookie2 + "=\"" + cookie2Value +
"\"")); "\";Version=1"));
replyLatch.countDown(); replyLatch.countDown();
} }
}); });

View File

@ -332,6 +332,32 @@ public class QuotedStringTokenizer
escapes['\r'] = 'r'; escapes['\r'] = 'r';
} }
/* ------------------------------------------------------------ */
/** Quote a string into an Appendable.
* Only quotes and backslash are escaped.
* @param buffer The Appendable
* @param input The String to quote.
*/
public static void quoteOnly(Appendable buffer, String input)
{
try
{
buffer.append('"');
for (int i = 0; i < input.length(); ++i)
{
char c = input.charAt(i);
if (c == '"' || c == '\\')
buffer.append('\\');
buffer.append(c);
}
buffer.append('"');
}
catch (IOException x)
{
throw new RuntimeException(x);
}
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** Quote a string into an Appendable. /** Quote a string into an Appendable.
* The characters ", \, \n, \r, \t, \f and \b are escaped * The characters ", \, \n, \r, \t, \f and \b are escaped
@ -377,38 +403,6 @@ public class QuotedStringTokenizer
} }
} }
/* ------------------------------------------------------------ */
/** Quote a string into a StringBuffer only if needed.
* Quotes are forced if any delim characters are present.
*
* @param buf The StringBuffer
* @param s The String to quote.
* @param delim String of characters that must be quoted.
* @return true if quoted;
*/
public static boolean quoteIfNeeded(Appendable buf, String s,String delim)
{
for (int i=0;i<s.length();i++)
{
char c = s.charAt(i);
if (delim.indexOf(c)>=0)
{
quote(buf,s);
return true;
}
}
try
{
buf.append(s);
return false;
}
catch(IOException e)
{
throw new RuntimeException(e);
}
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public static String unquoteOnly(String s) public static String unquoteOnly(String s)
@ -565,6 +559,12 @@ public class QuotedStringTokenizer
(c == '/') || (c == '"') || (c == 'u')); (c == '/') || (c == '"') || (c == 'u'));
} }
/* ------------------------------------------------------------ */
public static boolean isQuoted(String s)
{
return s!=null && s.length()>0 && s.charAt(0)=='"' && s.charAt(s.length()-1)=='"';
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* @return handle double quotes if true * @return handle double quotes if true

View File

@ -112,14 +112,6 @@ public class QuotedStringTokenizerTest
QuotedStringTokenizer.quote(buf,"abcefg\""); QuotedStringTokenizer.quote(buf,"abcefg\"");
assertEquals("\"abcefg\\\"\"",buf.toString()); assertEquals("\"abcefg\\\"\"",buf.toString());
buf.setLength(0);
QuotedStringTokenizer.quoteIfNeeded(buf,"abc \n efg","\"\\\n\r\t\f\b%+ ;=");
assertEquals("\"abc \\n efg\"",buf.toString());
buf.setLength(0);
QuotedStringTokenizer.quoteIfNeeded(buf,"abcefg","\"\\\n\r\t\f\b%+ ;=");
assertEquals("abcefg",buf.toString());
} }
/* /*