Mark,

On 4/29/25 3:50 AM, ma...@apache.org wrote:
This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch 11.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/11.0.x by this push:
      new fab7247d2f Refactor CGI servlet to access resources via WebResources
fab7247d2f is described below

commit fab7247d2f0e3a29d5daef565f829f383e10e5e2
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Mon Apr 28 12:58:21 2025 +0100

     Refactor CGI servlet to access resources via WebResources
---
  java/org/apache/catalina/servlets/CGIServlet.java  | 233 ++++++++-------------
  .../catalina/servlets/LocalStrings.properties      |   1 -
  .../catalina/servlets/LocalStrings_fr.properties   |   1 -
  .../catalina/servlets/LocalStrings_ja.properties   |   1 -
  .../catalina/servlets/LocalStrings_ko.properties   |   1 -
  .../servlets/LocalStrings_zh_CN.properties         |   1 -
  webapps/docs/changelog.xml                         |   4 +
  7 files changed, 97 insertions(+), 145 deletions(-)

diff --git a/java/org/apache/catalina/servlets/CGIServlet.java 
b/java/org/apache/catalina/servlets/CGIServlet.java
index 510d2782e7..db03ae1955 100644
--- a/java/org/apache/catalina/servlets/CGIServlet.java
+++ b/java/org/apache/catalina/servlets/CGIServlet.java
@@ -25,6 +25,8 @@ import java.io.InputStreamReader;
  import java.io.OutputStream;
  import java.io.Serial;
  import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URL;
  import java.net.URLDecoder;
  import java.nio.file.Files;
  import java.util.ArrayList;
@@ -628,9 +630,6 @@ public final class CGIServlet extends HttpServlet {
          /** pathInfo for the current request */
          private String pathInfo = null;
- /** real file system directory of the enclosing servlet's web app */
-        private String webAppRootDir = null;
-
          /** tempdir for context - used to expand scripts in unexpanded wars */
          private File tmpDir = null;
@@ -684,7 +683,6 @@ public final class CGIServlet extends HttpServlet {
           */
          protected void setupFromContext(ServletContext context) {
              this.context = context;
-            this.webAppRootDir = context.getRealPath("/");
              this.tmpDir = (File) context.getAttribute(ServletContext.TEMPDIR);
          }
@@ -788,10 +786,9 @@ public final class CGIServlet extends HttpServlet {
           * cgiPathPrefix is defined by setting this servlet's cgiPathPrefix 
init parameter
           * </p>
           *
-         * @param pathInfo      String from HttpServletRequest.getPathInfo()
-         * @param webAppRootDir String from context.getRealPath("/")
           * @param contextPath   String as from 
HttpServletRequest.getContextPath()
           * @param servletPath   String as from 
HttpServletRequest.getServletPath()
+         * @param pathInfo      String from HttpServletRequest.getPathInfo()
           * @param cgiPathPrefix subdirectory of webAppRootDir below which the 
web app's CGIs may be stored; can be null.
           *                          The CGI search path will start at 
webAppRootDir + File.separator + cgiPathPrefix (or
           *                          webAppRootDir alone if cgiPathPrefix is 
null). cgiPathPrefix is defined by setting
@@ -808,59 +805,108 @@ public final class CGIServlet extends HttpServlet {
           *             found
           *             </ul>
           */
-        protected String[] findCGI(String pathInfo, String webAppRootDir, 
String contextPath, String servletPath,
-                String cgiPathPrefix) {
-            String path;
-            String name;
-            String scriptname;
-
-            if (webAppRootDir.lastIndexOf(File.separator) == 
(webAppRootDir.length() - 1)) {
-                // strip the trailing "/" from the webAppRootDir
-                webAppRootDir = webAppRootDir.substring(0, 
(webAppRootDir.length() - 1));
-            }
+        protected String[] findCGI(String contextPath, String servletPath, 
String pathInfo, String cgiPathPrefix) {

I know it wasn't your goal to clean any of this up, but I think a custom class would be better for this application than String[].

Any objections to be re-factoring this to use a new class?

- if (cgiPathPrefix != null) {
-                webAppRootDir = webAppRootDir + File.separator + cgiPathPrefix;
-            }
+            StringBuilder cgiPath = new StringBuilder();
+            StringBuilder urlPath = new StringBuilder();
- if (log.isTraceEnabled()) {
-                log.trace(sm.getString("cgiServlet.find.path", pathInfo, 
webAppRootDir));
-            }
+            URL cgiScriptURL = null;
- File currentLocation = new File(webAppRootDir);
-            StringTokenizer dirWalker = new StringTokenizer(pathInfo, "/");
-            if (log.isTraceEnabled()) {
-                log.trace(sm.getString("cgiServlet.find.location", 
currentLocation.getAbsolutePath()));
+            if (cgiPathPrefix == null || cgiPathPrefix.isEmpty()) {
+                cgiPath.append(servletPath);
+            } else {
+                cgiPath.append('/');
+                cgiPath.append(cgiPathPrefix);
              }
-            StringBuilder cginameBuilder = new StringBuilder();
-            while (!currentLocation.isFile() && dirWalker.hasMoreElements()) {
-                String nextElement = (String) dirWalker.nextElement();
-                currentLocation = new File(currentLocation, nextElement);
-                cginameBuilder.append('/').append(nextElement);
+            urlPath.append(servletPath);
+
+            StringTokenizer pathWalker = new StringTokenizer(pathInfo, "/");
+
+            while (pathWalker.hasMoreElements() && cgiScriptURL == null) {
+                String urlSegment = pathWalker.nextToken();
+                cgiPath.append('/');
+                cgiPath.append(urlSegment);
+                urlPath.append('/');
+                urlPath.append(urlSegment);
                  if (log.isTraceEnabled()) {
-                    log.trace(sm.getString("cgiServlet.find.location", 
currentLocation.getAbsolutePath()));
+                    log.trace(sm.getString("cgiServlet.find.location", 
cgiPath.toString()));
+                }
+                try {
+                    cgiScriptURL = context.getResource(cgiPath.toString());
+                } catch (MalformedURLException e) {
+                    // Ignore - should never happen
                  }
              }
-            String cginame = cginameBuilder.toString();
-            if (!currentLocation.isFile()) {
+
+            // No script was found
+            if (cgiScriptURL == null) {
                  return new String[] { null, null, null, null };
              }
- path = currentLocation.getAbsolutePath();
-            name = currentLocation.getName();
+            // Set-up return values
+            String path = null;
+            String scriptName = null;
+            String cgiName = null;
+            String name = null;
- if (servletPath.startsWith(cginame)) {
-                scriptname = contextPath + cginame;
-            } else {
-                scriptname = contextPath + servletPath + cginame;
+            path = context.getRealPath(cgiPath.toString());
+            if (path == null) {
+                /*
+                 * The script doesn't exist directly on the file system. It 
might be located in an archive or similar.
+                 * Such scripts are extracted to the web application's 
temporary file location.
+                 */
+                File tmpCgiFile = new File(tmpDir + cgiPath.toString());
+                if (!tmpCgiFile.exists()) {
+
+                    // Create directories
+                    File parent = tmpCgiFile.getParentFile();
+                    if (!parent.mkdirs() && !parent.isDirectory()) {
+                        
log.warn(sm.getString("cgiServlet.expandCreateDirFail", 
parent.getAbsolutePath()));
+                        return new String[] { null, null, null, null };
+                    }
+
+                    try (InputStream is = 
context.getResourceAsStream(cgiPath.toString())) {
+                        synchronized (expandFileLock) {
+                            // Check if file was created by concurrent request
+                            if (!tmpCgiFile.exists()) {
+                                try {
+                                    Files.copy(is, tmpCgiFile.toPath());
+                                } catch (IOException ioe) {
+                                    
log.warn(sm.getString("cgiServlet.expandFail", cgiScriptURL,
+                                            tmpCgiFile.getAbsolutePath()), 
ioe);
+                                    if (tmpCgiFile.exists()) {
+                                        if (!tmpCgiFile.delete()) {
+                                            
log.warn(sm.getString("cgiServlet.expandDeleteFail",
+                                                    
tmpCgiFile.getAbsolutePath()));
+                                        }
+                                    }
+                                    return new String[] { null, null, null, 
null };
+                                }
+                                if (log.isDebugEnabled()) {
+                                    
log.debug(sm.getString("cgiServlet.expandOk", cgiScriptURL,
+                                            tmpCgiFile.getAbsolutePath()));
+                                }
+                            }
+                        }
+                    } catch (IOException ioe) {
+                        log.warn(sm.getString("cgiServlet.expandCloseFail", 
cgiScriptURL), ioe);
+                    }
+                }
+                path = tmpCgiFile.getAbsolutePath();
              }
+ scriptName = urlPath.toString();
+            cgiName = scriptName.substring(servletPath.length());
+            name = scriptName.substring(scriptName.lastIndexOf('/') + 1);
+
              if (log.isTraceEnabled()) {
-                log.trace(sm.getString("cgiServlet.find.found", name, path, 
scriptname, cginame));
+                log.trace(sm.getString("cgiServlet.find.found", name, path, 
scriptName, cgiName));
              }
-            return new String[] { path, scriptname, cginame, name };
+
+            return new String[] { path, scriptName, cgiName, name };
          }
+
          /**
           * Constructs the CGI environment to be supplied to the invoked CGI 
script; relies heavily on Servlet API
           * methods and findCGI
@@ -895,13 +941,7 @@ public final class CGIServlet extends HttpServlet {
              sPathInfoOrig = this.pathInfo;
              sPathInfoOrig = sPathInfoOrig == null ? "" : sPathInfoOrig;
- if (webAppRootDir == null) {
-                // The app has not been deployed in exploded form
-                webAppRootDir = tmpDir.toString();
-                expandCGIScript();
-            }
-
-            sCGINames = findCGI(sPathInfoOrig, webAppRootDir, contextPath, 
servletPath, cgiPathPrefix);
+            sCGINames = findCGI(contextPath, servletPath, sPathInfoOrig, 
cgiPathPrefix);
sCGIFullPath = sCGINames[0];
              sCGIScriptName = sCGINames[1];
@@ -1027,93 +1067,6 @@ public final class CGIServlet extends HttpServlet {
              return true;
          }
- /**
-         * Extracts requested resource from web app archive to context work 
directory to enable CGI script to be
-         * executed.
-         */
-        protected void expandCGIScript() {

I know it would have to be written a little differently, but why remove this separate method? I think using the separate method makes the calling code easier to read.

-            StringBuilder srcPath = new StringBuilder();
-            StringBuilder destPath = new StringBuilder();
-            InputStream is = null;
-
-            // paths depend on mapping
-            if (cgiPathPrefix == null) {
-                srcPath.append(pathInfo);
-                is = context.getResourceAsStream(srcPath.toString());
-                destPath.append(tmpDir);
-                destPath.append(pathInfo);
-            } else {
-                // essentially same search algorithm as findCGI()
-                srcPath.append(cgiPathPrefix);
-                StringTokenizer pathWalker = new StringTokenizer(pathInfo, 
"/");
-                // start with first element
-                while (pathWalker.hasMoreElements() && (is == null)) {
-                    srcPath.append('/');
-                    srcPath.append(pathWalker.nextElement());
-                    is = context.getResourceAsStream(srcPath.toString());
-                }
-                destPath.append(tmpDir);
-                destPath.append('/');
-                destPath.append(srcPath);
-            }
-
-            if (is == null) {
-                // didn't find anything, give up now
-                log.warn(sm.getString("cgiServlet.expandNotFound", srcPath));
-                return;
-            }
-
-            try {
-                File f = new File(destPath.toString());
-                if (f.exists()) {
-                    // Don't need to expand if it already exists
-                    return;
-                }
-
-                // create directories
-                File dir = f.getParentFile();
-                if (!dir.mkdirs() && !dir.isDirectory()) {
-                    log.warn(sm.getString("cgiServlet.expandCreateDirFail", 
dir.getAbsolutePath()));
-                    return;
-                }
-
-                try {
-                    synchronized (expandFileLock) {
-                        // make sure file doesn't exist
-                        if (f.exists()) {
-                            return;
-                        }
-
-                        // create file
-                        if (!f.createNewFile()) {
-                            return;
-                        }
-
-                        Files.copy(is, f.toPath());
-
-                        if (log.isDebugEnabled()) {
-                            log.debug(sm.getString("cgiServlet.expandOk", 
srcPath, destPath));
-                        }
-                    }
-                } catch (IOException ioe) {
-                    log.warn(sm.getString("cgiServlet.expandFail", srcPath, 
destPath), ioe);
-                    // delete in case file is corrupted
-                    if (f.exists()) {
-                        if (!f.delete()) {
-                            
log.warn(sm.getString("cgiServlet.expandDeleteFail", f.getAbsolutePath()));
-                        }
-                    }
-                }
-            } finally {
-                try {
-                    is.close();
-                } catch (IOException e) {
-                    log.warn(sm.getString("cgiServlet.expandCloseFail", 
srcPath), e);
-                }
-            }
-        }
-
-
          /**
           * Returns important CGI environment information in a multi-line text 
format.
           *
@@ -1409,9 +1362,9 @@ public final class CGIServlet extends HttpServlet {
           * <LI><u>Allowed characters in path segments</u>: This 
implementation does not allow non-terminal NULL segments
           * in the path -- IOExceptions may be thrown;
           * <LI><u>"<code>.</code>" and "<code>..</code>" path segments</u>: 
This implementation does not allow
-         * "<code>.</code>" and "<code>..</code>" in the path, and such 
characters will result in an IOException
-         * being thrown (this should never happen since Tomcat normalises the 
requestURI before determining the
-         * contextPath, servletPath and pathInfo);
+         * "<code>.</code>" and "<code>..</code>" in the path, and such 
characters will result in an IOException being
+         * thrown (this should never happen since Tomcat normalises the 
requestURI before determining the contextPath,
+         * servletPath and pathInfo);
           * <LI><u>Implementation limitations</u>: This implementation does 
not impose any limitations except as
           * documented above. This implementation may be limited by the 
servlet container used to house this
           * implementation. In particular, all the primary CGI variable values 
are derived either directly or indirectly
@@ -1449,7 +1402,7 @@ public final class CGIServlet extends HttpServlet {
              Runtime rt;
              BufferedReader cgiHeaderReader = null;
              InputStream cgiOutput = null;
-            BufferedReader commandsStdErr ;
+            BufferedReader commandsStdErr;
              Thread errReaderThread = null;
              BufferedOutputStream commandsStdIn;
              Process proc = null;
diff --git a/java/org/apache/catalina/servlets/LocalStrings.properties 
b/java/org/apache/catalina/servlets/LocalStrings.properties
index c0c92ea88d..28f7b87bac 100644
--- a/java/org/apache/catalina/servlets/LocalStrings.properties
+++ b/java/org/apache/catalina/servlets/LocalStrings.properties
@@ -21,7 +21,6 @@ cgiServlet.expandCloseFail=Failed to close input stream for 
script at path [{0}]
  cgiServlet.expandCreateDirFail=Failed to create destination directory [{0}] 
for script expansion
  cgiServlet.expandDeleteFail=Failed to delete file at [{0}] after IOException 
during expansion
  cgiServlet.expandFail=Failed to expand script at path [{0}] to [{1}]
-cgiServlet.expandNotFound=Unable to expand [{0}] as it could not be found
  cgiServlet.expandOk=Expanded script at path [{0}] to [{1}]
  cgiServlet.find.found=Found CGI: name [{0}], path [{1}], script name [{2}] 
and CGI name [{3}]
  cgiServlet.find.location=Looking for a file at [{0}]
diff --git a/java/org/apache/catalina/servlets/LocalStrings_fr.properties 
b/java/org/apache/catalina/servlets/LocalStrings_fr.properties
index 3577389b2b..452e450620 100644
--- a/java/org/apache/catalina/servlets/LocalStrings_fr.properties
+++ b/java/org/apache/catalina/servlets/LocalStrings_fr.properties
@@ -21,7 +21,6 @@ cgiServlet.expandCloseFail=Impossible de fermer le flux 
d''entrée du script ave
  cgiServlet.expandCreateDirFail=Echec de la création du répertoire de 
destination [{0}] pour la décompression du script
  cgiServlet.expandDeleteFail=Impossible d''effacer le fichier [{0}] suite à 
une IOException pendant la décompression
  cgiServlet.expandFail=Impossible de faire l''expansion du script au chemin 
[{0}] vers [{1}]
-cgiServlet.expandNotFound=Impossible de décompresser [{0}] car il n''a pas été 
trouvé
  cgiServlet.expandOk=Extrait le script du chemin [{0}] vers [{1}]
  cgiServlet.find.found=Trouvé le CGI : nom [{0}], chemin [{1}], nom de script 
[{2}] et nom du CGI [{3}]
  cgiServlet.find.location=Recherche d''un fichier en [{0}]
diff --git a/java/org/apache/catalina/servlets/LocalStrings_ja.properties 
b/java/org/apache/catalina/servlets/LocalStrings_ja.properties
index 91de715170..eacaccab42 100644
--- a/java/org/apache/catalina/servlets/LocalStrings_ja.properties
+++ b/java/org/apache/catalina/servlets/LocalStrings_ja.properties
@@ -21,7 +21,6 @@ cgiServlet.expandCloseFail=パス [{0}] のスクリプトの入力ストリー
  cgiServlet.expandCreateDirFail=スクリプトの展開先ディレクトリ[{0}]の作成に失敗しました。
  cgiServlet.expandDeleteFail=拡張中にIOExceptionの後に [{0}] でファイルを削除できませんでした
  cgiServlet.expandFail=パス [{0}] のスクリプトを [{1}] に展開できませんでした
-cgiServlet.expandNotFound=見つけることができなかったため [{0}] を展開できません
  cgiServlet.expandOk=パス [{0}] の [{1}] に展開されたスクリプト
  cgiServlet.find.found=見つかったCGI: 名前 [{0}]、パス [{1}]、スクリプト名 [{2}]、CGI名 [{3}]
  cgiServlet.find.location=ファイル [{0}] を探しています。
diff --git a/java/org/apache/catalina/servlets/LocalStrings_ko.properties 
b/java/org/apache/catalina/servlets/LocalStrings_ko.properties
index aeb3d720d6..547c842722 100644
--- a/java/org/apache/catalina/servlets/LocalStrings_ko.properties
+++ b/java/org/apache/catalina/servlets/LocalStrings_ko.properties
@@ -21,7 +21,6 @@ cgiServlet.expandCloseFail=경로 [{0}]에 위치한 스크립트를 위한, 입
  cgiServlet.expandCreateDirFail=스크립트를 압축해제 하기 위한 대상 디렉토리 [{0}]을(를) 생성하지 못했습니다.
  cgiServlet.expandDeleteFail=압축해제 중 IOException이 발생한 후, [{0}]에 위치한 해당 파일을 삭제하지 
못했습니다.
  cgiServlet.expandFail=경로 [{0}]의 스크립트를 [{1}](으)로 압축해제 하지 못했습니다.
-cgiServlet.expandNotFound=[{0}]을(를) 찾을 수 없어서 압축해제 할 수 없습니다.
  cgiServlet.expandOk=[{0}] 경로에 있는 스트립트가 [{1}](으)로 압축 해제되었습니다.
  cgiServlet.find.found=CGI 발견: 이름 [{0}], 경로 [{1}], 스크립트 이름 [{2}], CGI 이름 [{3}]
  cgiServlet.find.location=[{0}]에 위치한 파일을 찾는 중
diff --git a/java/org/apache/catalina/servlets/LocalStrings_zh_CN.properties 
b/java/org/apache/catalina/servlets/LocalStrings_zh_CN.properties
index a2bdf51ba3..efce343f1f 100644
--- a/java/org/apache/catalina/servlets/LocalStrings_zh_CN.properties
+++ b/java/org/apache/catalina/servlets/LocalStrings_zh_CN.properties
@@ -21,7 +21,6 @@ cgiServlet.expandCloseFail=无法关闭路径[{0}]处脚本的输入流
  cgiServlet.expandCreateDirFail=无法为脚本扩展创建目标目录[{0}]
  cgiServlet.expandDeleteFail=扩展期间,发生IOException异常后删除文件[{0}]失败
  cgiServlet.expandFail=在路径[{0}] 到[{1}] 展开脚本失败.
-cgiServlet.expandNotFound=无法展开[{0}],因为找不到它。
  cgiServlet.expandOk=从路径[{0}]到[{1}]扩展脚本
  cgiServlet.find.found=找到CGI:name[{0}]、path[{1}]、script name[{2}]和CGI name[{3}]
  cgiServlet.find.location=在 [{0}] 查找文件
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 44563fb95f..0b08c61b64 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -129,6 +129,10 @@
          <pr>843</pr>: Fix off by one validation logic for partial PUT ranges
          and associated test case. Submitted by Chenjp. (remm)
        </fix>
+      <scode>
+        Refactor GCI servlet to access resources via the
+        <code>WebResource</code> API. (markt)
+      </scode>
      </changelog>
    </subsection>
    <subsection name="Jasper">

-chris


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to