in core/src/main/java/hudson/model/DirectoryBrowserSupport.java [187:373]
private void serveFile(StaplerRequest req, StaplerResponse rsp, VirtualFile root, String icon, boolean serveDirIndex) throws IOException, ServletException, InterruptedException {
// handle form submission
String pattern = req.getParameter("pattern");
if(pattern==null)
pattern = req.getParameter("path"); // compatibility with Hudson<1.129
if(pattern!=null && Util.isSafeToRedirectTo(pattern)) {// avoid open redirect
rsp.sendRedirect2(pattern);
return;
}
String path = getPath(req);
if(path.replace('\\', '/').contains("/../")) {
// don't serve anything other than files in the artifacts dir
rsp.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
// split the path to the base directory portion "abc/def/ghi" which doesn't include any wildcard,
// and the GLOB portion "**/*.xml" (the rest)
StringBuilder _base = new StringBuilder();
StringBuilder _rest = new StringBuilder();
int restSize=-1; // number of ".." needed to go back to the 'base' level.
boolean zip=false; // if we are asked to serve a zip file bundle
boolean plain = false; // if asked to serve a plain text directory listing
{
boolean inBase = true;
StringTokenizer pathTokens = new StringTokenizer(path,"/");
while(pathTokens.hasMoreTokens()) {
String pathElement = pathTokens.nextToken();
// Treat * and ? as wildcard unless they match a literal filename
if((pathElement.contains("?") || pathElement.contains("*"))
&& inBase && !root.child((_base.length() > 0 ? _base + "/" : "") + pathElement).exists())
inBase = false;
if(pathElement.equals("*zip*")) {
// the expected syntax is foo/bar/*zip*/bar.zip
// the last 'bar.zip' portion is to causes browses to set a good default file name.
// so the 'rest' portion ends here.
zip=true;
break;
}
if(pathElement.equals("*plain*")) {
plain = true;
break;
}
StringBuilder sb = inBase?_base:_rest;
if(sb.length()>0) sb.append('/');
sb.append(pathElement);
if(!inBase)
restSize++;
}
}
restSize = Math.max(restSize,0);
String base = _base.toString();
String rest = _rest.toString();
if(!ALLOW_SYMLINK_ESCAPE && (root.supportIsDescendant() && !root.isDescendant(base))){
LOGGER.log(Level.WARNING, "Trying to access a file outside of the directory, target: "+ base);
rsp.sendError(HttpServletResponse.SC_FORBIDDEN, "Trying to access a file outside of the directory, target: " + base);
return;
}
// this is the base file/directory
VirtualFile baseFile = base.isEmpty() ? root : root.child(base);
if(baseFile.isDirectory()) {
if(zip) {
rsp.setContentType("application/zip");
zip(rsp, root, baseFile, rest);
return;
}
if (plain) {
rsp.setContentType("text/plain;charset=UTF-8");
try (OutputStream os = rsp.getOutputStream()) {
for (VirtualFile kid : baseFile.list()) {
os.write(kid.getName().getBytes(StandardCharsets.UTF_8));
if (kid.isDirectory()) {
os.write('/');
}
os.write('\n');
}
os.flush();
}
return;
}
if(rest.length()==0) {
// if the target page to be displayed is a directory and the path doesn't end with '/', redirect
StringBuffer reqUrl = req.getRequestURL();
if(reqUrl.charAt(reqUrl.length()-1)!='/') {
rsp.sendRedirect2(reqUrl.append('/').toString());
return;
}
}
List<List<Path>> glob = null;
boolean patternUsed = rest.length() > 0;
if(patternUsed) {
// the rest is Ant glob pattern
glob = patternScan(baseFile, rest, createBackRef(restSize));
} else
if(serveDirIndex) {
// serve directory index
glob = baseFile.run(new BuildChildPaths(baseFile, req.getLocale()));
}
if(glob!=null) {
List<List<Path>> filteredGlob = keepReadabilityOnlyOnDescendants(baseFile, patternUsed, glob);
// serve glob
req.setAttribute("it", this);
List<Path> parentPaths = buildParentPath(base,restSize);
req.setAttribute("parentPath",parentPaths);
req.setAttribute("backPath", createBackRef(restSize));
req.setAttribute("topPath", createBackRef(parentPaths.size()+restSize));
req.setAttribute("files", filteredGlob);
req.setAttribute("icon", icon);
req.setAttribute("path", path);
req.setAttribute("pattern",rest);
req.setAttribute("dir", baseFile);
if (ResourceDomainConfiguration.isResourceRequest(req)) {
req.getView(this, "plaindir.jelly").forward(req, rsp);
} else {
req.getView(this, "dir.jelly").forward(req, rsp);
}
return;
}
// convert a directory service request to a single file service request by serving
// 'index.html'
baseFile = baseFile.child(indexFileName);
}
//serve a single file
if(!baseFile.exists()) {
rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
boolean view = rest.equals("*view*");
if(rest.equals("*fingerprint*")) {
try (InputStream fingerprintInput = baseFile.open()) {
rsp.forward(Jenkins.get().getFingerprint(Util.getDigestOf(fingerprintInput)), "/", req);
}
return;
}
URL external = baseFile.toExternalURL();
if (external != null) {
// or this URL could be emitted directly from dir.jelly
// though we would prefer to delay toExternalURL calls unless and until needed
rsp.sendRedirect2(external.toExternalForm());
return;
}
long lastModified = baseFile.lastModified();
long length = baseFile.length();
if(LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Serving "+baseFile+" with lastModified=" + lastModified + ", length=" + length);
if (view) {
// for binary files, provide the file name for download
rsp.setHeader("Content-Disposition", "inline; filename=" + baseFile.getName());
// pseudo file name to let the Stapler set text/plain
rsp.serveFile(req, baseFile.open(), lastModified, -1, length, "plain.txt");
} else {
if (resourceToken != null) {
// redirect to second domain
rsp.sendRedirect(302, ResourceDomainRootAction.get().getRedirectUrl(resourceToken, req.getRestOfPath()));
} else {
if (!ResourceDomainConfiguration.isResourceRequest(req)) {
// if we're serving this from the main domain, set CSP headers
String csp = SystemProperties.getString(CSP_PROPERTY_NAME, DEFAULT_CSP_VALUE);
if (!csp.trim().equals("")) {
// allow users to prevent sending this header by setting empty system property
for (String header : new String[]{"Content-Security-Policy", "X-WebKit-CSP", "X-Content-Security-Policy"}) {
rsp.setHeader(header, csp);
}
}
}
rsp.serveFile(req, baseFile.open(), lastModified, -1, length, baseFile.getName());
}
}
}