提交 044c4ad1 编写于 作者: M mindless

[FIXED HUDSON-2793] Fix from HUDSON-2379 introduced a bug for artifact/workspace

filenames with spaces.
- Added new Util.rawEncode which encodes the right set of characters (based on
  RFC1738) and deprecated Util.encode which only encodes non-ASCII.  Unit test too.
- Change previous fix to use Util.rawEncode instead of URLEncoder.encode
- New fix: add h.xmlEscape call for each displayed filename in artifacts/workspace
  so & and < are encoded for HTML display
- Add all these fixes in Run/artifacts-index.jelly which wasn't included before


git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@14355 71c3de6d-444a-0410-be80-ed276b4c234a
上级 cfa07b50
...@@ -34,6 +34,9 @@ import java.net.InetAddress; ...@@ -34,6 +34,9 @@ import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CharsetEncoder;
import java.security.DigestInputStream; import java.security.DigestInputStream;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
...@@ -554,7 +557,11 @@ public class Util { ...@@ -554,7 +557,11 @@ public class Util {
/** /**
* Escapes non-ASCII characters in URL. * Escapes non-ASCII characters in URL.
* @deprecated Only escapes non-ASCII but leaves other URL-unsafe characters.
* Util.rawEncode should generally be used instead, though be careful to pass only
* a single path component to that method (it will encode /, but this method does not).
*/ */
@Deprecated
public static String encode(String s) { public static String encode(String s) {
try { try {
boolean escaped = false; boolean escaped = false;
...@@ -588,6 +595,53 @@ public class Util { ...@@ -588,6 +595,53 @@ public class Util {
} }
} }
/**
* Encode a single path component for use in an HTTP URL.
* Escapes all non-ASCII, general unsafe (space and "#%<>[\]^`{|}~)
* and HTTP special characters (/;:?) as specified in RFC1738,
* plus backslash (Windows path separator).
* Note that slash(/) is encoded, so the given string should be a
* single path component used in constructing a URL.
* Method name inspired by PHP's rawencode.
*/
public static String rawEncode(String s) {
boolean escaped = false;
StringBuilder out = null;
CharsetEncoder enc = null;
CharBuffer buf = null;
char c;
for (int i = 0, m = s.length(); i < m; i++) {
c = s.charAt(i);
if ((c<64 || c>90) && (c<97 || c>122) && (c<38 || c>57 || c==47)
&& c!=61 && c!=36 && c!=33 && c!=95) {
if (!escaped) {
out = new StringBuilder(i + (m - i) * 3);
out.append(s.substring(0, i));
enc = Charset.forName("UTF-8").newEncoder();
buf = CharBuffer.allocate(1);
escaped = true;
}
// 1 char -> UTF8
buf.put(0,c);
buf.rewind();
try {
for (byte b : enc.encode(buf).array()) {
out.append('%');
out.append(toDigit((b >> 4) & 0xF));
out.append(toDigit(b & 0xF));
}
} catch (CharacterCodingException ex) { }
} else if (escaped) {
out.append(c);
}
}
return escaped ? out.toString() : s;
}
private static char toDigit(int n) {
return (char)(n < 10 ? '0' + n : 'A' + n - 10);
}
/** /**
* Surrounds by a single-quote. * Surrounds by a single-quote.
*/ */
...@@ -596,7 +650,7 @@ public class Util { ...@@ -596,7 +650,7 @@ public class Util {
} }
/** /**
* Escapes HTML unsafe characters like &lt;, &amp;to the respective character entities. * Escapes HTML unsafe characters like &lt;, &amp; to the respective character entities.
*/ */
public static String escape(String text) { public static String escape(String text) {
StringBuilder buf = new StringBuilder(text.length()+64); StringBuilder buf = new StringBuilder(text.length()+64);
...@@ -639,12 +693,6 @@ public class Util { ...@@ -639,12 +693,6 @@ public class Util {
return buf.toString(); return buf.toString();
} }
private static char toDigit(int n) {
char ch = Character.forDigit(n,16);
if(ch>='a') ch = (char)(ch-'a'+'A');
return ch;
}
/** /**
* Creates an empty file. * Creates an empty file.
*/ */
......
...@@ -17,7 +17,6 @@ import java.io.IOException; ...@@ -17,7 +17,6 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.Serializable; import java.io.Serializable;
import java.net.URLEncoder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
...@@ -380,14 +379,14 @@ public final class DirectoryBrowserSupport { ...@@ -380,14 +379,14 @@ public final class DirectoryBrowserSupport {
Arrays.sort(files,new FileComparator()); Arrays.sort(files,new FileComparator());
for( File f : files ) { for( File f : files ) {
Path p = new Path(URLEncoder.encode(f.getName(),"UTF-8"),f.getName(),f.isDirectory(),f.length(), f.canRead()); Path p = new Path(Util.rawEncode(f.getName()),f.getName(),f.isDirectory(),f.length(), f.canRead());
if(!f.isDirectory()) { if(!f.isDirectory()) {
r.add(Collections.singletonList(p)); r.add(Collections.singletonList(p));
} else { } else {
// find all empty intermediate directory // find all empty intermediate directory
List<Path> l = new ArrayList<Path>(); List<Path> l = new ArrayList<Path>();
l.add(p); l.add(p);
String relPath = URLEncoder.encode(f.getName(),"UTF-8"); String relPath = Util.rawEncode(f.getName());
while(true) { while(true) {
// files that don't start with '.' qualify for 'meaningful files', nor SCM related files // files that don't start with '.' qualify for 'meaningful files', nor SCM related files
File[] sub = f.listFiles(new FilenameFilter() { File[] sub = f.listFiles(new FilenameFilter() {
...@@ -398,7 +397,7 @@ public final class DirectoryBrowserSupport { ...@@ -398,7 +397,7 @@ public final class DirectoryBrowserSupport {
if(sub==null || sub.length!=1 || !sub[0].isDirectory()) if(sub==null || sub.length!=1 || !sub[0].isDirectory())
break; break;
f = sub[0]; f = sub[0];
relPath += '/'+URLEncoder.encode(f.getName(),"UTF-8"); relPath += '/'+Util.rawEncode(f.getName());
l.add(new Path(relPath,f.getName(),true,0, f.canRead())); l.add(new Path(relPath,f.getName(),true,0, f.canRead()));
} }
r.add(l); r.add(l);
...@@ -465,7 +464,7 @@ public final class DirectoryBrowserSupport { ...@@ -465,7 +464,7 @@ public final class DirectoryBrowserSupport {
buildPathList(baseDir, parent, pathList, href); buildPathList(baseDir, parent, pathList, href);
} }
href.append(URLEncoder.encode(filePath.getName(),"UTF-8")); href.append(Util.rawEncode(filePath.getName()));
if (filePath.isDirectory()) { if (filePath.isDirectory()) {
href.append("/"); href.append("/");
} }
......
...@@ -45,7 +45,6 @@ import java.io.Writer; ...@@ -45,7 +45,6 @@ import java.io.Writer;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
...@@ -567,16 +566,16 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run ...@@ -567,16 +566,16 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
private void addArtifacts( File dir, String path, String pathHref, List<Artifact> r ) { private void addArtifacts( File dir, String path, String pathHref, List<Artifact> r ) {
String[] children = dir.list(); String[] children = dir.list();
if(children==null) return; if(children==null) return;
for (String child : children) try { for (String child : children) {
if(r.size()>CUTOFF) if(r.size()>CUTOFF)
return; return;
File sub = new File(dir, child); File sub = new File(dir, child);
if (sub.isDirectory()) { if (sub.isDirectory()) {
addArtifacts(sub, path + child + '/', pathHref + URLEncoder.encode(child,"UTF-8") + '/', r); addArtifacts(sub, path + child + '/', pathHref + Util.rawEncode(child) + '/', r);
} else { } else {
r.add(new Artifact(path + child, pathHref + URLEncoder.encode(child,"UTF-8"))); r.add(new Artifact(path + child, pathHref + Util.rawEncode(child)));
} }
} catch (UnsupportedEncodingException e) { /* Won't happen as UTF-8 is hardcoded */ } }
} }
private static final int CUTOFF = 17; // 0, 1,... 16, and then "too many" private static final int CUTOFF = 17; // 0, 1,... 16, and then "too many"
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
<form action="${backPath}" method="get"> <form action="${backPath}" method="get">
<a href="${topPath}"><img src="${imagesURL}/48x48/${icon}" class="rootIcon" alt=""/></a> <a href="${topPath}"><img src="${imagesURL}/48x48/${icon}" class="rootIcon" alt=""/></a>
<j:forEach var="p" items="${parentPath}"> <j:forEach var="p" items="${parentPath}">
<a href="${p.href}">${p.title}</a> <a href="${p.href}">${h.xmlEscape(p.title)}</a>
/ /
</j:forEach> </j:forEach>
<input type="text" name="pattern" value="${pattern}" /> <input type="text" name="pattern" value="${pattern}" />
...@@ -34,10 +34,10 @@ ...@@ -34,10 +34,10 @@
<j:choose> <j:choose>
<j:when test="${x.readable}"> <j:when test="${x.readable}">
<a href="${t.href}">${t.title}</a> <a href="${t.href}">${h.xmlEscape(t.title)}</a>
</j:when> </j:when>
<j:otherwise> <j:otherwise>
${t.title} ${h.xmlEscape(t.title)}
</j:otherwise> </j:otherwise>
</j:choose> </j:choose>
...@@ -54,7 +54,7 @@ ...@@ -54,7 +54,7 @@
<img src="${imagesURL}/16x16/fingerprint.gif" alt="fingerprint" /> <img src="${imagesURL}/16x16/fingerprint.gif" alt="fingerprint" />
</a> </a>
<st:nbsp/> <st:nbsp/>
<a href="${x.href}/*view*/">view</a> <a href="${x.href}/*view*/">${%view}</a>
</j:if> </j:if>
</td> </td>
</j:if> </j:if>
......
...@@ -3,13 +3,13 @@ ...@@ -3,13 +3,13 @@
<st:include page="sidepanel.jelly" /> <st:include page="sidepanel.jelly" />
<l:main-panel> <l:main-panel>
<t:buildCaption> <t:buildCaption>
Build Artifacts ${%Build Artifacts}
</t:buildCaption> </t:buildCaption>
<ul> <ul>
<j:forEach var="f" items="${it.artifacts}"> <j:forEach var="f" items="${it.artifacts}">
<li><a href="artifact/${f}">${f}</a></li> <li><a href="artifact/${f.href}">${h.xmlEscape(f.displayPath)}</a></li>
</j:forEach> </j:forEach>
</ul> </ul>
</l:main-panel> </l:main-panel>
</l:layout> </l:layout>
</j:jelly> </j:jelly>
\ No newline at end of file
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
<ul> <ul>
<j:forEach var="f" items="${artifacts}"> <j:forEach var="f" items="${artifacts}">
<li> <li>
<a href="${baseURL}artifact/${f.href}">${f.displayPath}</a> <a href="${baseURL}artifact/${f.href}">${h.xmlEscape(f.displayPath)}</a>
<st:nbsp/> <st:nbsp/>
<a href="${baseURL}artifact/${f.href}/*fingerprint*/"><img src="${imagesURL}/16x16/fingerprint.gif" alt="[fingerprint]" /></a> <a href="${baseURL}artifact/${f.href}/*fingerprint*/"><img src="${imagesURL}/16x16/fingerprint.gif" alt="[fingerprint]" /></a>
</li> </li>
......
...@@ -67,6 +67,23 @@ public class UtilTest extends TestCase { ...@@ -67,6 +67,23 @@ public class UtilTest extends TestCase {
assertEquals(encoded, "http://hudson/job/Hudson%20Job"); assertEquals(encoded, "http://hudson/job/Hudson%20Job");
} }
/**
* Test the rawEncode() method.
*/
public void testRawEncode() {
String[] data = { // Alternating raw,encoded
"abcdefghijklmnopqrstuvwxyz", "abcdefghijklmnopqrstuvwxyz",
"ABCDEFGHIJKLMNOPQRSTUVWXYZ", "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"01234567890!@$&*()-_=+',.", "01234567890!@$&*()-_=+',.",
" \"#%/:;<>?", "%20%22%23%25%2F%3A%3B%3C%3E%3F",
"[\\]^`{|}~", "%5B%5C%5D%5E%60%7B%7C%7D%7E",
"d\u00E9velopp\u00E9s", "d%C3%A9%00velopp%C3%A9%00s",
};
for (int i = 0; i < data.length; i += 2) {
assertEquals("test " + i, data[i + 1], Util.rawEncode(data[i]));
}
}
/** /**
* Test the tryParseNumber() method. * Test the tryParseNumber() method.
*/ */
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册