diff --git a/README.adoc b/README.adoc index 8768b7d..3d7a241 100644 --- a/README.adoc +++ b/README.adoc @@ -74,3 +74,19 @@ http://support.quest.com/technical-documents/toad-edge/2.1/user-guide/continuous https://support.quest.com/technical-documents/toad-edge/2.2/user-guide/continuous-integration-and-delivery |=== + +[[ContinuousIntegrationWithToadEdgePlugin-Documentation]] +== Troubleshooting +If you are having issues viewing Jenkins HTML comparison report, it could be due to your browser's Content Security Policy. You can check your browser console to confirm the same. +Also, the reports will not get rendered if Jenkins's https://www.jenkins.io/doc/book/security/user-content/#resource-root-url[resource root url] is configured. + +To view Jenkins HTML reports, you would have to relax the CSP. This can be done by going to _Jenkins > Manage Jenkins > Script Console_ + +*Execute this script to relax CSP:* + +`System.setProperty("hudson.model.DirectoryBrowserSupport.CSP", "default-src 'self'; script-src * 'self' 'unsafe-inline'; img-src 'self'; style-src 'self' 'unsafe-inline'; font-src * data:");` + +This will be reverted once Jenkins restarts + +_Please note that we would also suggest you to review https://www.jenkins.io/doc/book/security/configuring-content-security-policy/ and https://content-security-policy.com/ to understand the nature of CSP and the protection which it offers before changing CSP._ + diff --git a/src/main/java/ci/with/toad/edge/DirectoryBrowserSupport.java b/src/main/java/ci/with/toad/edge/DirectoryBrowserSupport.java deleted file mode 100644 index 660085d..0000000 --- a/src/main/java/ci/with/toad/edge/DirectoryBrowserSupport.java +++ /dev/null @@ -1,641 +0,0 @@ -/* - * Copyright 2021 Quest Software Inc. - * ALL RIGHTS RESERVED. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressor implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ci.with.toad.edge; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.Serializable; -import java.text.Collator; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Locale; -import java.util.StringTokenizer; -import java.util.logging.Level; -import java.util.logging.Logger; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.io.IOUtils; -import org.apache.tools.zip.ZipEntry; -import org.apache.tools.zip.ZipOutputStream; -import org.kohsuke.accmod.Restricted; -import org.kohsuke.accmod.restrictions.NoExternalUse; -import org.kohsuke.stapler.HttpResponse; -import org.kohsuke.stapler.StaplerRequest; -import org.kohsuke.stapler.StaplerResponse; -import hudson.FilePath; -import hudson.Util; -import hudson.model.ModelObject; -import jenkins.model.Jenkins; -import jenkins.security.MasterToSlaveCallable; -import jenkins.util.VirtualFile; - -/** - * Has convenience methods to serve file system. - * - *
- * This object can be used in a mix-in style to provide a directory browsing
- * capability to a {@link ModelObject}.
- *
- * @author Kohsuke Kawaguchi
- */
-public final class DirectoryBrowserSupport implements HttpResponse {
-
- public final ModelObject owner;
-
- public final String title;
-
- private final VirtualFile base;
- private final String icon;
- private final boolean serveDirIndex;
- private String indexFileName = "index.html";
-
- /**
- * @param owner The parent model object under which the directory browsing is added.
- * @param title Used in the HTML caption.
- *
- * @deprecated as of 1.297 Use
- * {@link #DirectoryBrowserSupport(ModelObject, FilePath, String, String, boolean)}
- */
- @Deprecated
- public DirectoryBrowserSupport(ModelObject owner, String title) {
- this(owner, (VirtualFile) null, title, null, false);
- }
-
- /**
- * @param owner
- * The parent model object under which the directory browsing is
- * added.
- * @param base
- * The root of the directory that's bound to URL.
- * @param title
- * Used in the HTML caption.
- * @param icon
- * The icon file name, like "folder.gif"
- * @param serveDirIndex
- * True to generate the directory index. False to serve
- * "index.html"
- */
- public DirectoryBrowserSupport(ModelObject owner, FilePath base, String title, String icon, boolean serveDirIndex) {
- this(owner, base.toVirtualFile(), title, icon, serveDirIndex);
- }
-
- /**
- * @param owner
- * The parent model object under which the directory browsing is
- * added.
- * @param base
- * The root of the directory that's bound to URL.
- * @param title
- * Used in the HTML caption.
- * @param icon
- * The icon file name, like "folder.gif"
- * @param serveDirIndex
- * True to generate the directory index. False to serve
- * "index.html"
- * @since 1.532
- */
- public DirectoryBrowserSupport(ModelObject owner, VirtualFile base, String title, String icon,
- boolean serveDirIndex) {
- this.owner = owner;
- this.base = base;
- this.title = title;
- this.icon = icon;
- this.serveDirIndex = serveDirIndex;
- }
-
- public void generateResponse(StaplerRequest req, StaplerResponse rsp, Object node)
- throws IOException, ServletException {
- try {
- serveFile(req, rsp, base, icon, serveDirIndex);
- } catch (InterruptedException e) {
- throw new IOException("interrupted", e);
- }
- }
-
- /**
- * If the directory is requested but the directory listing is disabled, a
- * file of this name is served. By default it's "index.html".
- *
- * @param fileName
- * - name of the file to use as index
- *
- * @since 1.312
- */
- public void setIndexFileName(String fileName) {
- this.indexFileName = fileName;
- }
-
- /**
- * Serves a file from the file system (Maps the URL to a directory in a file
- * system.)
- *
- * @param req
- * Request
- * @param rsp
- * Response
- * @param root
- * Server root
- * @param icon
- * The icon file name, like "folder-open.gif"
- * @param serveDirIndex
- * True to generate the directory index. False to serve
- * "index.html"
- *
- * @throws IOException IOException
- * @throws ServletException ServletException
- * @throws InterruptedException InterruptedException
- *
- * @deprecated as of 1.297 Instead of calling this method explicitly, just
- * return the {@link DirectoryBrowserSupport} object from the
- * {@code doXYZ} method and let Stapler generate a response for
- * you.
- */
- @Deprecated
- public void serveFile(StaplerRequest req, StaplerResponse rsp, FilePath root, String icon, boolean serveDirIndex)
- throws IOException, ServletException, InterruptedException {
- serveFile(req, rsp, root.toVirtualFile(), icon, serveDirIndex);
- }
-
- 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.isAbsoluteUri(pattern)) {// avoid open
- // redirect
- rsp.sendRedirect2(pattern);
- return;
- }
-
- String path = getPath(req);
- if (path.replace('\\', '/').indexOf("/../") != -1) {
- // 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();
-
- // this is the base file/directory
- VirtualFile baseFile = root.child(base);
-
- if (baseFile.isDirectory()) {
- if (zip) {
- rsp.setContentType("application/zip");
- zip(rsp.getOutputStream(), baseFile, rest);
- return;
- }
- if (plain) {
- rsp.setContentType("text/plain;charset=UTF-8");
- OutputStream os = rsp.getOutputStream();
- try {
- for (VirtualFile kid : baseFile.list()) {
- os.write(kid.getName().getBytes("UTF-8"));
- if (kid.isDirectory()) {
- os.write('/');
- }
- os.write('\n');
- }
- os.flush();
- } finally {
- os.close();
- }
- 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> glob = null;
-
- if (rest.length() > 0) {
- // 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) {
- // serve glob
- req.setAttribute("it", this);
- List
>, IOException> {
- private final VirtualFile cur;
- private final Locale locale;
-
- BuildChildPaths(VirtualFile cur, Locale locale) {
- this.cur = cur;
- this.locale = locale;
- }
-
- @Override
- public List
> call() throws IOException {
- return buildChildPaths(cur, locale);
- }
- }
-
- /**
- * Builds a list of list of {@link Path}. The inner list of {@link Path}
- * represents one child item to be shown (this mechanism is used to skip
- * empty intermediate directory.)
- */
- private static List
> buildChildPaths(VirtualFile cur, Locale locale) throws IOException {
- List
> r = new ArrayList
>();
-
- VirtualFile[] files = cur.list();
- Arrays.sort(files, new FileComparator(locale));
-
- for (VirtualFile f : files) {
- Path p = new Path(Util.rawEncode(f.getName()), f.getName(), f.isDirectory(), f.length(), f.canRead());
- if (!f.isDirectory()) {
- r.add(Collections.singletonList(p));
- } else {
- // find all empty intermediate directory
- List
> patternScan(VirtualFile baseDir, String pattern, String baseRef)
- throws IOException {
- String[] files = baseDir.list(pattern);
-
- if (files.length > 0) {
- List
> r = new ArrayList
>(files.length);
- for (String match : files) {
- List