File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\model\DirectoryBrowserSupport.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi, Erik Ramfelt
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24 package hudson.model;
25
26 import hudson.FilePath;
27 import hudson.Util;
28 import hudson.util.IOException2;
29 import hudson.FilePath.FileCallable;
30 import hudson.remoting.VirtualChannel;
31 import jenkins.model.Jenkins;
32 import org.kohsuke.stapler.StaplerRequest;
33 import org.kohsuke.stapler.StaplerResponse;
34 import org.kohsuke.stapler.HttpResponse;
35 import org.apache.tools.ant.types.FileSet;
36 import org.apache.tools.ant.DirectoryScanner;
37
38 import javax.servlet.ServletException;
39 import javax.servlet.http.HttpServletResponse;
40 import java.io.File;
41 import java.io.FilenameFilter;
42 import java.io.IOException;
43 import java.io.InputStream;
44 import java.io.OutputStream;
45 import java.io.Serializable;
46 import java.text.Collator;
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.Collections;
50 import java.util.Comparator;
51 import java.util.List;
52 import java.util.Locale;
53 import java.util.StringTokenizer;
54 import java.util.logging.Logger;
55 import java.util.logging.Level;
56
57 /**
58 * Has convenience methods to serve file system.
59 *
60 * <p>
61 * This object can be used in a mix-in style to provide a directory browsing capability
62 * to a {@link ModelObject}.
63 *
64 * @author Kohsuke Kawaguchi
65 */
66 public final class DirectoryBrowserSupport implements HttpResponse {
67
68 public final ModelObject owner;
69
70 public final String title;
71
72 private final FilePath base;
73 private final String icon;
74 private final boolean serveDirIndex;
75 private String indexFileName = "index.html";
76
77 /**
78 * @deprecated as of 1.297
79 * Use {@link #DirectoryBrowserSupport(ModelObject, FilePath, String, String, boolean)}
80 */
81 public DirectoryBrowserSupport(ModelObject owner, String title) {
82 this(owner,null,title,null,false);
83 }
84
85 /**
86 * @param owner
87 * The parent model object under which the directory browsing is added.
88 * @param base
89 * The root of the directory that's bound to URL.
90 * @param title
91 * Used in the HTML caption.
92 * @param icon
93 * The icon file name, like "folder.gif"
94 * @param serveDirIndex
95 * True to generate the directory index.
96 * False to serve "index.html"
97 */
98 public DirectoryBrowserSupport(ModelObject owner, FilePath base, String title, String icon, boolean serveDirIndex) {
99 this.owner = owner;
100 this.base = base;
101 this.title = title;
102 this.icon = icon;
103 this.serveDirIndex = serveDirIndex;
104 }
105
106 public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node) throws IOException, ServletException {
107 try {
108 serveFile(req,rsp,base,icon,serveDirIndex);
109 } catch (InterruptedException e) {
110 throw new IOException2("interrupted",e);
111 }
112 }
113
114 /**
115 * If the directory is requested but the directory listing is disabled, a file of this name
116 * is served. By default it's "index.html".
117 * @since 1.312
118 */
119 public void setIndexFileName(String fileName) {
120 this.indexFileName = fileName;
121 }
122
123 /**
124 * Serves a file from the file system (Maps the URL to a directory in a file system.)
125 *
126 * @param icon
127 * The icon file name, like "folder-open.gif"
128 * @param serveDirIndex
129 * True to generate the directory index.
130 * False to serve "index.html"
131 * @deprecated as of 1.297
132 * Instead of calling this method explicitly, just return the {@link DirectoryBrowserSupport} object
133 * from the {@code doXYZ} method and let Stapler generate a response for you.
134 */
135 public void serveFile(StaplerRequest req, StaplerResponse rsp, FilePath root, String icon, boolean serveDirIndex) throws IOException, ServletException, InterruptedException {
136 // handle form submission
137 String pattern = req.getParameter("pattern");
138 if(pattern==null)
139 pattern = req.getParameter("path"); // compatibility with Hudson<1.129
140 if(pattern!=null && !Util.isAbsoluteUri(pattern)) {// avoid open redirect
141 rsp.sendRedirect2(pattern);
142 return;
143 }
144
145 String path = getPath(req);
146 if(path.replace('\\','/').indexOf("/../")!=-1) {
147 // don't serve anything other than files in the artifacts dir
148 rsp.sendError(HttpServletResponse.SC_BAD_REQUEST);
149 return;
150 }
151
152 // split the path to the base directory portion "abc/def/ghi" which doesn't include any wildcard,
153 // and the GLOB portion "**/*.xml" (the rest)
154 StringBuilder _base = new StringBuilder();
155 StringBuilder _rest = new StringBuilder();
156 int restSize=-1; // number of ".." needed to go back to the 'base' level.
157 boolean zip=false; // if we are asked to serve a zip file bundle
158 boolean plain = false; // if asked to serve a plain text directory listing
159 {
160 boolean inBase = true;
161 StringTokenizer pathTokens = new StringTokenizer(path,"/");
162 while(pathTokens.hasMoreTokens()) {
163 String pathElement = pathTokens.nextToken();
164 // Treat * and ? as wildcard unless they match a literal filename
165 if((pathElement.contains("?") || pathElement.contains("*"))
166 && inBase && !(new FilePath(root, (_base.length() > 0 ? _base + "/" : "") + pathElement).exists()))
167 inBase = false;
168 if(pathElement.equals("*zip*")) {
169 // the expected syntax is foo/bar/*zip*/bar.zip
170 // the last 'bar.zip' portion is to causes browses to set a good default file name.
171 // so the 'rest' portion ends here.
172 zip=true;
173 break;
174 }
175 if(pathElement.equals("*plain*")) {
176 plain = true;
177 break;
178 }
179
180 StringBuilder sb = inBase?_base:_rest;
181 if(sb.length()>0) sb.append('/');
182 sb.append(pathElement);
183 if(!inBase)
184 restSize++;
185 }
186 }
187 restSize = Math.max(restSize,0);
188 String base = _base.toString();
189 String rest = _rest.toString();
190
191 // this is the base file/directory
192 FilePath baseFile = new FilePath(root,base);
193
194 if(baseFile.isDirectory()) {
195 if(zip) {
196 rsp.setContentType("application/zip");
197 baseFile.zip(rsp.getOutputStream(),rest);
198 return;
199 }
200 if (plain) {
201 rsp.setContentType("text/plain;charset=UTF-8");
202 OutputStream os = rsp.getOutputStream();
203 try {
204 for (String kid : baseFile.act(new SimpleChildList())) {
205 os.write(kid.getBytes("UTF-8"));
206 os.write('\n');
207 }
208 os.flush();
209 } finally {
210 os.close();
211 }
212 return;
213 }
214
215 if(rest.length()==0) {
216 // if the target page to be displayed is a directory and the path doesn't end with '/', redirect
217 StringBuffer reqUrl = req.getRequestURL();
218 if(reqUrl.charAt(reqUrl.length()-1)!='/') {
219 rsp.sendRedirect2(reqUrl.append('/').toString());
220 return;
221 }
222 }
223
224 FileCallable<List<List<Path>>> glob = null;
225
226 if(rest.length()>0) {
227 // the rest is Ant glob pattern
228 glob = new PatternScanner(rest,createBackRef(restSize));
229 } else
230 if(serveDirIndex) {
231 // serve directory index
232 glob = new ChildPathBuilder(req.getLocale());
233 }
234
235 if(glob!=null) {
236 // serve glob
237 req.setAttribute("it", this);
238 List<Path> parentPaths = buildParentPath(base,restSize);
239 req.setAttribute("parentPath",parentPaths);
240 req.setAttribute("backPath", createBackRef(restSize));
241 req.setAttribute("topPath", createBackRef(parentPaths.size()+restSize));
242 req.setAttribute("files", baseFile.act(glob));
243 req.setAttribute("icon", icon);
244 req.setAttribute("path", path);
245 req.setAttribute("pattern",rest);
246 req.setAttribute("dir", baseFile);
247 req.getView(this,"dir.jelly").forward(req, rsp);
248 return;
249 }
250
251 // convert a directory service request to a single file service request by serving
252 // 'index.html'
253 baseFile = baseFile.child(indexFileName);
254 }
255
256 //serve a single file
257 if(!baseFile.exists()) {
258 rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
259 return;
260 }
261
262 boolean view = rest.equals("*view*");
263
264 if(rest.equals("*fingerprint*")) {
265 rsp.forward(Jenkins.getInstance().getFingerprint(baseFile.digest()),"/",req);
266 return;
267 }
268
269 ContentInfo ci = baseFile.act(new ContentInfo());
270
271 if(LOGGER.isLoggable(Level.FINE))
272 LOGGER.fine("Serving "+baseFile+" with lastModified="+ci.lastModified+", contentLength="+ci.contentLength);
273
274 InputStream in = baseFile.read();
275 if (view) {
276 // for binary files, provide the file name for download
277 rsp.setHeader("Content-Disposition", "inline; filename=" + baseFile.getName());
278
279 // pseudo file name to let the Stapler set text/plain
280 rsp.serveFile(req, in, ci.lastModified, -1, ci.contentLength, "plain.txt");
281 } else {
282 rsp.serveFile(req, in, ci.lastModified, -1, ci.contentLength, baseFile.getName() );
283 }
284 }
285
286 private String getPath(StaplerRequest req) {
287 String path = req.getRestOfPath();
288 if(path.length()==0)
289 path = "/";
290 return path;
291 }
292
293 private static final class ContentInfo implements FileCallable<ContentInfo> {
294 long contentLength;
295 long lastModified;
296
297 public ContentInfo invoke(File f, VirtualChannel channel) throws IOException {
298 contentLength = f.length();
299 lastModified = f.lastModified();
300 return this;
301 }
302
303 private static final long serialVersionUID = 1L;
304 }
305
306 /**
307 * Builds a list of {@link Path} that represents ancestors
308 * from a string like "/foo/bar/zot".
309 */
310 private List<Path> buildParentPath(String pathList, int restSize) {
311 List<Path> r = new ArrayList<Path>();
312 StringTokenizer tokens = new StringTokenizer(pathList, "/");
313 int total = tokens.countTokens();
314 int current=1;
315 while(tokens.hasMoreTokens()) {
316 String token = tokens.nextToken();
317 r.add(new Path(createBackRef(total-current+restSize),token,true,0, true));
318 current++;
319 }
320 return r;
321 }
322
323 private static String createBackRef(int times) {
324 if(times==0) return "./";
325 StringBuilder buf = new StringBuilder(3*times);
326 for(int i=0; i<times; i++ )
327 buf.append("../");
328 return buf.toString();
329 }
330
331 /**
332 * Represents information about one file or folder.
333 */
334 public static final class Path implements Serializable {
335 /**
336 * Relative URL to this path from the current page.
337 */
338 private final String href;
339 /**
340 * Name of this path. Just the file name portion.
341 */
342 private final String title;
343
344 private final boolean isFolder;
345
346 /**
347 * File size, or null if this is not a file.
348 */
349 private final long size;
350
351 /**
352 * If the current user can read the file.
353 */
354 private final boolean isReadable;
355
356 public Path(String href, String title, boolean isFolder, long size, boolean isReadable) {
357 this.href = href;
358 this.title = title;
359 this.isFolder = isFolder;
360 this.size = size;
361 this.isReadable = isReadable;
362 }
363
364 public boolean isFolder() {
365 return isFolder;
366 }
367
368 public boolean isReadable() {
369 return isReadable;
370 }
371
372 public String getHref() {
373 return href;
374 }
375
376 public String getTitle() {
377 return title;
378 }
379
380 public String getIconName() {
381 if (isReadable)
382 return isFolder?"folder.png":"text.png";
383 else
384 return isFolder?"folder-error.png":"text-error.png";
385 }
386
387 public long getSize() {
388 return size;
389 }
390
391 private static final long serialVersionUID = 1L;
392 }
393
394
395
396 private static final class FileComparator implements Comparator<File> {
397 private Collator collator;
398
399 public FileComparator(Locale locale) {
400 this.collator = Collator.getInstance(locale);
401 }
402
403 public int compare(File lhs, File rhs) {
404 // directories first, files next
405 int r = dirRank(lhs)-dirRank(rhs);
406 if(r!=0) return r;
407 // otherwise alphabetical
408 return this.collator.compare(lhs.getName(), rhs.getName());
409 }
410
411 private int dirRank(File f) {
412 if(f.isDirectory()) return 0;
413 else return 1;
414 }
415 }
416
417 /**
418 * Simple list of names of children of a folder.
419 * Subfolders will have a trailing slash appended.
420 */
421 private static final class SimpleChildList implements FileCallable<List<String>> {
422 private static final long serialVersionUID = 1L;
423 public List<String> invoke(File f, VirtualChannel channel) throws IOException {
424 List<String> r = new ArrayList<String>();
425 String[] kids = f.list(); // no need to sort
426 for (String kid : kids) {
427 if (new File(f, kid).isDirectory()) {
428 r.add(kid + "/");
429 } else {
430 r.add(kid);
431 }
432 }
433 return r;
434 }
435 }
436
437 /**
438 * Builds a list of list of {@link Path}. The inner
439 * list of {@link Path} represents one child item to be shown
440 * (this mechanism is used to skip empty intermediate directory.)
441 */
442 private static final class ChildPathBuilder implements FileCallable<List<List<Path>>> {
443 private Locale locale;
444
445 public ChildPathBuilder(Locale locale) {
446 this.locale = locale;
447 }
448
449 public List<List<Path>> invoke(File cur, VirtualChannel channel) throws IOException {
450 List<List<Path>> r = new ArrayList<List<Path>>();
451
452 File[] files = cur.listFiles();
453 if (files != null) {
454 Arrays.sort(files,new FileComparator(this.locale));
455
456 for( File f : files ) {
457 Path p = new Path(Util.rawEncode(f.getName()),f.getName(),f.isDirectory(),f.length(), f.canRead());
458 if(!f.isDirectory()) {
459 r.add(Collections.singletonList(p));
460 } else {
461 // find all empty intermediate directory
462 List<Path> l = new ArrayList<Path>();
463 l.add(p);
464 String relPath = Util.rawEncode(f.getName());
465 while(true) {
466 // files that don't start with '.' qualify for 'meaningful files', nor SCM related files
467 File[] sub = f.listFiles(new FilenameFilter() {
468 public boolean accept(File dir, String name) {
469 return !name.startsWith(".") && !name.equals("CVS") && !name.equals(".svn");
470 }
471 });
472 if(sub==null || sub.length!=1 || !sub[0].isDirectory())
473 break;
474 f = sub[0];
475 relPath += '/'+Util.rawEncode(f.getName());
476 l.add(new Path(relPath,f.getName(),true,0, f.canRead()));
477 }
478 r.add(l);
479 }
480 }
481 }
482
483 return r;
484 }
485
486 private static final long serialVersionUID = 1L;
487 }
488
489 /**
490 * Runs ant GLOB against the current {@link FilePath} and returns matching
491 * paths.
492 */
493 private static class PatternScanner implements FileCallable<List<List<Path>>> {
494 private final String pattern;
495 /**
496 * String like "../../../" that cancels the 'rest' portion. Can be "./"
497 */
498 private final String baseRef;
499
500 public PatternScanner(String pattern,String baseRef) {
501 this.pattern = pattern;
502 this.baseRef = baseRef;
503 }
504
505 public List<List<Path>> invoke(File baseDir, VirtualChannel channel) throws IOException {
506 FileSet fs = Util.createFileSet(baseDir,pattern);
507 DirectoryScanner ds = fs.getDirectoryScanner();
508 String[] files = ds.getIncludedFiles();
509
510 if (files.length > 0) {
511 List<List<Path>> r = new ArrayList<List<Path>>(files.length);
512 for (String match : files) {
513 List<Path> file = buildPathList(baseDir, new File(baseDir,match));
514 r.add(file);
515 }
516 return r;
517 }
518
519 return null;
520 }
521
522 /**
523 * Builds a path list from the current workspace directory down to the specified file path.
524 */
525 private List<Path> buildPathList(File baseDir, File filePath) throws IOException {
526 List<Path> pathList = new ArrayList<Path>();
527 StringBuilder href = new StringBuilder(baseRef);
528
529 buildPathList(baseDir, filePath, pathList, href);
530 return pathList;
531 }
532
533 /**
534 * Builds the path list and href recursively top-down.
535 */
536 private void buildPathList(File baseDir, File filePath, List<Path> pathList, StringBuilder href) throws IOException {
537 File parent = filePath.getParentFile();
538 if (!baseDir.equals(parent)) {
539 buildPathList(baseDir, parent, pathList, href);
540 }
541
542 href.append(Util.rawEncode(filePath.getName()));
543 if (filePath.isDirectory()) {
544 href.append("/");
545 }
546
547 Path path = new Path(href.toString(), filePath.getName(), filePath.isDirectory(), filePath.length(), filePath.canRead());
548 pathList.add(path);
549 }
550
551 private static final long serialVersionUID = 1L;
552 }
553
554 private static final Logger LOGGER = Logger.getLogger(DirectoryBrowserSupport.class.getName());
555 }
556