diff --git a/dist/lib/ext/commons-fileupload.jar b/dist/lib/ext/commons-fileupload.jar deleted file mode 100644 index 5e608753c1b..00000000000 Binary files a/dist/lib/ext/commons-fileupload.jar and /dev/null differ diff --git a/dist/lib/ext/commons-fileupload2-javax.jar b/dist/lib/ext/commons-fileupload2-javax.jar new file mode 100644 index 00000000000..286076d19ce Binary files /dev/null and b/dist/lib/ext/commons-fileupload2-javax.jar differ diff --git a/dist/lib/ext/commons-io.jar b/dist/lib/ext/commons-io.jar index 177e58dc65d..eb316f40209 100644 Binary files a/dist/lib/ext/commons-io.jar and b/dist/lib/ext/commons-io.jar differ diff --git a/release.gradle b/release.gradle index dbf9ab79065..218cbcc2145 100644 --- a/release.gradle +++ b/release.gradle @@ -151,7 +151,7 @@ task prepareBin() { include "**/bsh.jar" include "**/closure-compiler-unshaded.jar" include "**/commons-collections4.jar" - include "**/commons-fileupload.jar" + include "**/commons-fileupload2-javax.jar" include "**/commons-io.jar" include "**/commons-logging.jar" include "**/Filters.jar" diff --git a/zhtml/build.gradle b/zhtml/build.gradle index 6d6d5ab4120..deb738a2624 100644 --- a/zhtml/build.gradle +++ b/zhtml/build.gradle @@ -14,7 +14,7 @@ idea { dependencies { api project(':zul') - api 'commons-io:commons-io:2.8.0' + api 'commons-io:commons-io:2.13.0' api 'org.zkoss:zsoup:1.8.2.5' } diff --git a/zk-parent/pom.xml b/zk-parent/pom.xml index 0beb8cc26d5..33dba19958a 100644 --- a/zk-parent/pom.xml +++ b/zk-parent/pom.xml @@ -106,14 +106,14 @@ ${slf4j.version} - commons-fileupload - commons-fileupload - 1.5 + org.apache.commons + commons-fileupload2-javax + 2.0.0-M1 commons-io commons-io - 2.8.0 + 2.13.0 commons-lang diff --git a/zk/build.gradle b/zk/build.gradle index fe0ba49895e..38f272cc562 100644 --- a/zk/build.gradle +++ b/zk/build.gradle @@ -22,8 +22,8 @@ java { dependencies { api project(':zweb') api project(':zkwebfragment') - api 'commons-fileupload:commons-fileupload:1.5' - api 'commons-io:commons-io:2.8.0' + api 'org.apache.commons:commons-fileupload2-javax:2.0.0-M1' + api 'commons-io:commons-io:2.13.0' compileOnly "javax.servlet:servlet-api:${servletVersion}" compileOnly "javax.portlet:portlet-api:${portletVersion}" compileOnly 'javax.websocket:javax.websocket-api:1.1' diff --git a/zk/src/main/java/org/zkoss/zk/au/http/AuMultipartUploader.java b/zk/src/main/java/org/zkoss/zk/au/http/AuMultipartUploader.java index 0d2604443fd..eacad407120 100644 --- a/zk/src/main/java/org/zkoss/zk/au/http/AuMultipartUploader.java +++ b/zk/src/main/java/org/zkoss/zk/au/http/AuMultipartUploader.java @@ -21,7 +21,9 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.net.URLDecoder; +import java.nio.charset.Charset; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; @@ -36,14 +38,15 @@ import java.util.Set; import java.util.stream.Collectors; +import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; -import org.apache.commons.fileupload.FileItem; -import org.apache.commons.fileupload.FileUploadBase; -import org.apache.commons.fileupload.FileUploadException; -import org.apache.commons.fileupload.disk.DiskFileItemFactory; -import org.apache.commons.fileupload.servlet.ServletFileUpload; +import org.apache.commons.fileupload2.core.AbstractFileUpload; +import org.apache.commons.fileupload2.core.DiskFileItemFactory; +import org.apache.commons.fileupload2.core.FileItem; +import org.apache.commons.fileupload2.core.FileUploadException; +import org.apache.commons.fileupload2.core.FileUploadSizeException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -77,12 +80,57 @@ public class AuMultipartUploader { private static final String FILE_DATA = AuMultipartUploader.class.getName() + ".FILE_DATA"; private static final Logger log = LoggerFactory.getLogger(AuMultipartUploader.class); + private static final String JAVAX_UPLOAD_CLASS = "org.apache.commons.fileupload2.javax.JavaxServletFileUpload"; + private static final String JAKARTA_UPLOAD_CLASS = "org.apache.commons.fileupload2.jakarta.JakartaServletFileUpload"; + private static final String JAVAX_DISK_UPLOAD_CLASS = "org.apache.commons.fileupload2.javax.JavaxServletDiskFileUpload"; + private static final String JAKARTA_DISK_UPLOAD_CLASS = "org.apache.commons.fileupload2.jakarta.JakartaServletDiskFileUpload"; + + private static Class getServletFileUploadClass() { + try { + return Class.forName(JAVAX_UPLOAD_CLASS); + } catch (ClassNotFoundException ex0) { + try { + return Class.forName(JAKARTA_UPLOAD_CLASS); + } catch (ClassNotFoundException ex1) { + throw new RuntimeException("Failed to find " + JAVAX_UPLOAD_CLASS + " or " + JAKARTA_UPLOAD_CLASS); + } + } + } + + public static boolean isMultipartContent(HttpServletRequest request) { + Class clazz = getServletFileUploadClass(); + try { + Method method = clazz.getMethod("isMultipartContent", HttpServletRequest.class); + return (boolean) method.invoke(null, request); + } catch (Exception ex) { + throw new RuntimeException("Failed to invoke " + clazz.getName() + "#isMultipartContent(HttpServletRequest)", ex); + } + } + + private static AbstractFileUpload newServletDiskFileUpload(DiskFileItemFactory factory) { + Class clazz; + try { + clazz = Class.forName(JAVAX_DISK_UPLOAD_CLASS); + } catch (ClassNotFoundException ex0) { + try { + clazz = Class.forName(JAKARTA_DISK_UPLOAD_CLASS); + } catch (ClassNotFoundException ex1) { + throw new RuntimeException("Failed to find " + JAVAX_DISK_UPLOAD_CLASS + " or " + JAKARTA_DISK_UPLOAD_CLASS); + } + } + try { + return (AbstractFileUpload) clazz.getDeclaredConstructor(DiskFileItemFactory.class).newInstance(factory); + } catch (Exception ex) { + throw new RuntimeException("Failed to create a new instance of " + clazz.getName(), ex); + } + } + public static AuDecoder parseRequest(HttpServletRequest request, AuDecoder decoder) { Map params = getFileuploadMetaPerWebApp( WebApps.getCurrent()); - ServletFileUpload upload = new ServletFileUpload( - new DiskFileItemFactory((Integer) params.get("sizeThreadHold"), - (File) params.get("repository"))); + AbstractFileUpload upload = newServletDiskFileUpload(new DiskFileItemFactory.Builder() + .setBufferSize((Integer) params.get("sizeThreadHold")) + .setPath(((File) params.get("repository")).toPath()).get()); try { List fileItems = upload.parseRequest(request); Map dataMap = new HashMap<>(fileItems.size()); @@ -176,15 +224,15 @@ private static Map getFileuploadMetaPerWebApp(WebApp webApp) { params.put("sizeThreadHold", sizeThreadHold); - File repository = null; - - if (conf.getFileRepository() != null) { + ServletContext context = webApp.getServletContext(); + File repository = (File) context.getAttribute("javax.servlet.context.tempdir"); + if (repository == null) + repository = (File) context.getAttribute("jakarta.servlet.context.tempdir"); + if (conf.getFileRepository() != null) repository = new File(conf.getFileRepository()); - if (!repository.isDirectory()) { - log.warn("The file repository is not a directory! [" + repository + "]"); - } - params.put("repository", repository); - } + if (!repository.isDirectory()) + log.warn("The file repository is not a directory! [" + repository + "]"); + params.put("repository", repository); org.zkoss.zk.ui.sys.DiskFileItemFactory dfiFactory = null; if (conf.getFileItemFactoryClass() != null) { @@ -269,8 +317,8 @@ public List decode(Object request, Desktop desktop) { reconstructPacket(auRequest.getData(), _reqData, desktop, params); Long fileSize = (Long) params.get("fileSize"); if (maxSizeLong >= 0 && fileSize > maxSizeLong) { - String errorMessage = uploadErrorMessage(new FileUploadBase.SizeLimitExceededException(null, fileSize, maxSizeLong)); - throw new FileUploadBase.SizeLimitExceededException(errorMessage, fileSize, maxSizeLong); + String errorMessage = uploadErrorMessage(new FileUploadSizeException(null, fileSize, maxSizeLong)); + throw new FileUploadSizeException(errorMessage, fileSize, maxSizeLong); } } catch (Exception e) { throw UiException.Aide.wrap(e); @@ -285,11 +333,11 @@ public boolean isIgnorable(Object request, WebApp wapp) { } private static String uploadErrorMessage(Throwable ex) { log.error("Failed to upload", ex); - if (ex instanceof FileUploadBase.SizeLimitExceededException) { + if (ex instanceof FileUploadSizeException) { try { - FileUploadBase.SizeLimitExceededException fex = (FileUploadBase.SizeLimitExceededException) ex; + FileUploadSizeException fex = (FileUploadSizeException) ex; long size = fex.getActualSize(); - long limit = fex.getPermittedSize(); + long limit = fex.getPermitted(); final Class msgClass = Classes.forNameByThread("org.zkoss.zul.mesg.MZul"); Field msgField = msgClass.getField("UPLOAD_ERROR_EXCEED_MAXSIZE"); int divisor1 = 1024; @@ -529,7 +577,7 @@ private static final Media processItem(Desktop desktop, FileItem fi, boolean alw if (charset == null) charset = conf.getUploadCharset(); } - return fi.isInMemory() ? new AMedia(name, null, ctype, fi.getString(charset)) + return fi.isInMemory() ? new AMedia(name, null, ctype, fi.getString(Charset.forName(charset))) : new ReaderMedia(name, null, ctype, fi, charset); } } diff --git a/zk/src/main/java/org/zkoss/zk/au/http/DHtmlUpdateServlet.java b/zk/src/main/java/org/zkoss/zk/au/http/DHtmlUpdateServlet.java index cf89bf09f67..642a42da87c 100644 --- a/zk/src/main/java/org/zkoss/zk/au/http/DHtmlUpdateServlet.java +++ b/zk/src/main/java/org/zkoss/zk/au/http/DHtmlUpdateServlet.java @@ -33,7 +33,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -446,7 +445,7 @@ public void process(Session sess, HttpServletRequest request, HttpServletRespons final WebApp wapp = sess.getWebApp(); final WebAppCtrl wappc = (WebAppCtrl) wapp; AuDecoder audec = getAuDecoder(wapp); - boolean multipartContent = ServletFileUpload.isMultipartContent(request); + boolean multipartContent = AuMultipartUploader.isMultipartContent(request); if (multipartContent) { audec = AuMultipartUploader.parseRequest(request, audec); } diff --git a/zk/src/main/java/org/zkoss/zk/au/http/ServletRequestContext.java b/zk/src/main/java/org/zkoss/zk/au/http/ServletRequestContext.java index d667b8c1af2..0ff2d2dfca2 100644 --- a/zk/src/main/java/org/zkoss/zk/au/http/ServletRequestContext.java +++ b/zk/src/main/java/org/zkoss/zk/au/http/ServletRequestContext.java @@ -32,8 +32,8 @@ import javax.servlet.http.HttpServletRequest; -import org.apache.commons.fileupload.FileUploadBase; -import org.apache.commons.fileupload.UploadContext; +import org.apache.commons.fileupload2.core.AbstractFileUpload; +import org.apache.commons.fileupload2.core.RequestContext; /** * An implementation of RequestContext, for commons-fileupload. @@ -42,7 +42,7 @@ * @author rudyhuang * @since 9.6.0 */ -class ServletRequestContext implements UploadContext { +class ServletRequestContext implements RequestContext { private final HttpServletRequest _request; public ServletRequestContext(HttpServletRequest request) { @@ -60,16 +60,10 @@ public String getContentType() { } @Override - @Deprecated - public int getContentLength() { - return _request.getContentLength(); - } - - @Override - public long contentLength() { + public long getContentLength() { long size; try { - size = Long.parseLong(_request.getHeader(FileUploadBase.CONTENT_LENGTH)); + size = Long.parseLong(_request.getHeader(AbstractFileUpload.CONTENT_LENGTH)); } catch (NumberFormatException e) { size = _request.getContentLength(); } @@ -84,7 +78,7 @@ public InputStream getInputStream() throws IOException { @Override public String toString() { return String.format("ContentLength=%s, ContentType=%s", - this.contentLength(), + this.getContentLength(), this.getContentType()); } } diff --git a/zk/src/main/java/org/zkoss/zk/ui/sys/DiskFileItemFactory.java b/zk/src/main/java/org/zkoss/zk/ui/sys/DiskFileItemFactory.java index 5ecbdbda6f2..139262b1a24 100644 --- a/zk/src/main/java/org/zkoss/zk/ui/sys/DiskFileItemFactory.java +++ b/zk/src/main/java/org/zkoss/zk/ui/sys/DiskFileItemFactory.java @@ -13,7 +13,7 @@ import java.io.File; -import org.apache.commons.fileupload.FileItem; +import org.apache.commons.fileupload2.core.FileItem; import org.zkoss.util.media.Media; @@ -22,7 +22,7 @@ * Factories can provide their own custom configuration, over and above that provided * by the default file upload implementation. *
- * Unlike {@link org.apache.commons.fileupload.FileItemFactory}, this factory + * Unlike {@link org.apache.commons.fileupload2.core.FileItemFactory}, this factory * needs two extra information, sizeThreshold and repository. *

* @author jumperchen diff --git a/zkdoc/release-note b/zkdoc/release-note index 5dcdf2639cf..dbd9ea056c1 100644 --- a/zkdoc/release-note +++ b/zkdoc/release-note @@ -2,8 +2,10 @@ ZK 10.0.0 * Features * Bugs + ZK-5393: Update ZK jars to jakarta-friendly uploads * Upgrade Notes + + Upgrade commons-fileupload to commons-fileupload2-javax 2.0.0-M1 and commons-io to 2.13.0 to support jakarta-friendly uploads -------- ZK 10.0.0-Beta diff --git a/zksandbox/build.gradle b/zksandbox/build.gradle index 27286177fcc..79277609a11 100644 --- a/zksandbox/build.gradle +++ b/zksandbox/build.gradle @@ -41,9 +41,9 @@ repositories { } dependencies { - implementation 'commons-io:commons-io:2.8.0' + implementation 'commons-io:commons-io:2.13.0' implementation 'commons-logging:commons-logging:1.1.1' - implementation 'commons-fileupload:commons-fileupload:1.5' + implementation 'org.apache.commons:commons-fileupload2-javax:2.0.0-M1' implementation "org.zkoss.theme:breeze:${version}" implementation "org.zkoss.theme:sapphire:${version}" implementation "org.zkoss.theme:silvertail:${version}" diff --git a/zktest/build.gradle b/zktest/build.gradle index 797241d135d..db2cf70e1a6 100644 --- a/zktest/build.gradle +++ b/zktest/build.gradle @@ -61,8 +61,8 @@ dependencies { implementation "org.zkoss.theme:sapphire:${version}" implementation "org.zkoss.theme:atlantic:${version}" implementation 'commons-logging:commons-logging:1.1.1' - implementation 'commons-fileupload:commons-fileupload:1.5' - implementation 'commons-io:commons-io:2.8.0' + implementation 'org.apache.commons:commons-fileupload2-javax:2.0.0-M1' + implementation 'commons-io:commons-io:2.13.0' implementation "org.zkoss.zk:zk:${version}" implementation "org.zkoss.zk:zkplus:${version}" implementation "org.zkoss.zk:zkplus-legacy:${version}" diff --git a/zktest/src/main/java/org/zkoss/zktest/test2/tree/PackageData.java b/zktest/src/main/java/org/zkoss/zktest/test2/tree/PackageData.java index 7fae52dd686..9dab201ae71 100644 --- a/zktest/src/main/java/org/zkoss/zktest/test2/tree/PackageData.java +++ b/zktest/src/main/java/org/zkoss/zktest/test2/tree/PackageData.java @@ -42,7 +42,7 @@ public class PackageData { // opened new DirectoryTreeNode(new PackageDataUnit("/ext",null), new DirectoryTreeNode[] { - new DirectoryTreeNode(new PackageDataUnit("commons-fileupload.jar", "Upload Features")), + new DirectoryTreeNode(new PackageDataUnit("commons-fileupload2-javax.jar", "Upload Features")), new DirectoryTreeNode(new PackageDataUnit("commons-io.jar", "Upload Features")), new DirectoryTreeNode(new PackageDataUnit("jcommon.jar", "Chart Component")), new DirectoryTreeNode(new PackageDataUnit("jfreechar.jar", "Chart Component")), diff --git a/zktest/src/main/webapp/test2/B100-ZK-5393.zul b/zktest/src/main/webapp/test2/B100-ZK-5393.zul new file mode 100644 index 00000000000..9c3a523f919 --- /dev/null +++ b/zktest/src/main/webapp/test2/B100-ZK-5393.zul @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/zktest/src/main/webapp/test2/config.properties b/zktest/src/main/webapp/test2/config.properties index 9f50fabafa1..794f711f2a4 100644 --- a/zktest/src/main/webapp/test2/config.properties +++ b/zktest/src/main/webapp/test2/config.properties @@ -3161,6 +3161,7 @@ B90-ZK-4431.zul=A,E,Multislider ##zats##B100-ZK-5035.zul=A,E,Listbox,Column,AddChild,Checkmark ##zats##B100-ZK-5453.zul=A,E,Chosenbox,XSS ##zats##B100-ZK-5025.zul=A,E,Menu,Menupopup,Menuitem,focus,hover +##zats##B100-ZK-5393.zul=A,E,FileUpload,JakartaEE ## # Features - 3.0.x version diff --git a/zktest/src/test/java/org/zkoss/zktest/zats/test2/B100_ZK_5393Test.java b/zktest/src/test/java/org/zkoss/zktest/zats/test2/B100_ZK_5393Test.java new file mode 100644 index 00000000000..cf64f82cd38 --- /dev/null +++ b/zktest/src/test/java/org/zkoss/zktest/zats/test2/B100_ZK_5393Test.java @@ -0,0 +1,32 @@ +/* B100_ZK_5393Test.java + + Purpose: + + Description: + + History: + Fri Dec 01 17:33:57 CST 2023, Created by rebeccalai + +Copyright (C) 2023 Potix Corporation. All Rights Reserved. +*/ +package org.zkoss.zktest.zats.test2; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Paths; + +import org.junit.jupiter.api.Test; + +import org.zkoss.test.webdriver.WebDriverTestCase; + +public class B100_ZK_5393Test extends WebDriverTestCase { + @Test + public void test() throws Exception { + connect(); + waitResponse(); + dropUploadFile(jq("@dropupload"), Paths.get("src/main/webapp/test2/img/sun.jpg")); + waitResponse(); + assertNoAnyError(); + assertEquals("sun.jpg", getZKLog()); + } +}