Tomcat Multipart类型参数处理

admin 2025年5月27日09:35:28评论1 views字数 11643阅读38分48秒阅读模式
前言
水一篇学习笔记,看这个话题最近挺火的。

请求解析

Tomcat在处理请求的时候会调用org.apache.catalina.connector.Request#parseParameters方法处理请求参数

Tomcat Multipart类型参数处理

如果当前请求的Content-Typemultipart/form-data则会调用org.apache.catalina.connector.Request#parseParts方法处理请求参数

private void parseParts(boolean explicit) {if (this.parts == null && this.partsParseException == null) {        Context context = this.getContext();        MultipartConfigElement mce = this.getWrapper().getMultipartConfigElement();if (mce == null) {if (!context.getAllowCasualMultipartParsing()) {if (explicit) {this.partsParseException = new IllegalStateException(sm.getString("coyoteRequest.noMultipartConfig"));return;                }this.parts = Collections.emptyList();return;            }            mce = new MultipartConfigElement((String)null, (long)this.connector.getMaxPostSize(), (long)this.connector.getMaxPostSize(), this.connector.getMaxPostSize());        }        int maxParameterCount = this.getConnector().getMaxParameterCount();        Parameters parameters = this.coyoteRequest.getParameters();        parameters.setLimit(maxParameterCount);        boolean success = false;try {// 获取临时目录,用于存放参数临时文件及其对应值            String locationStr = mce.getLocation();            File location;if (locationStr != null && locationStr.length() != 0) {                location = new File(locationStr);if (!location.isAbsolute()) {                    location = (new File((File)context.getServletContext().getAttribute("javax.servlet.context.tempdir"), locationStr)).getAbsoluteFile();                }            } else {                location = (File)context.getServletContext().getAttribute("javax.servlet.context.tempdir");            }if (!location.exists() && context.getCreateUploadTargets()) {                log.warn(sm.getString("coyoteRequest.uploadCreate", new Object[]{location.getAbsolutePath(), this.getMappingData().wrapper.getName()}));if (!location.mkdirs()) {                    log.warn(sm.getString("coyoteRequest.uploadCreateFail", new Object[]{location.getAbsolutePath()}));                }            }if (location.isDirectory()) {                DiskFileItemFactory factory = new DiskFileItemFactory();try {                    factory.setRepository(location.getCanonicalFile());                } catch (IOException var34) {                    IOException ioe = var34;                    parameters.setParseFailedReason(FailReason.IO_ERROR);this.partsParseException = ioe;return;                }                factory.setSizeThreshold(mce.getFileSizeThreshold());                ServletFileUpload upload = new ServletFileUpload();                upload.setFileItemFactory(factory);                upload.setFileSizeMax(mce.getMaxFileSize());                upload.setSizeMax(mce.getMaxRequestSize());if (maxParameterCount > -1) {                    upload.setFileCountMax((long)(maxParameterCount - parameters.size()));                }this.parts = new ArrayList();try {// 关键在此处,解析multipart请求,内部会对请求的每个参数在临时目录生成一个临时文件内容为该参数的值                    List<FileItem> items = upload.parseRequest(new ServletRequestContext(this));                    int maxPostSize = this.getConnector().getMaxPostSize();                    int postSize = 0;                    Charset charset = this.getCharset();                    Iterator var15 = items.iterator();while(var15.hasNext()) {                        FileItem item = (FileItem)var15.next();                        ApplicationPart part = new ApplicationPart(item, location);this.parts.add(part);if (part.getSubmittedFileName() == null) {                            String name = part.getName();if (maxPostSize >= 0) {                                postSize += name.getBytes(charset).length;                                ++postSize;                                postSize = (int)((long)postSize + part.getSize());                                ++postSize;if (postSize > maxPostSize) {                                    parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);throw new IllegalStateException(sm.getString("coyoteRequest.maxPostSizeExceeded"));                                }                            }                            String value = null;try {                                value = part.getString(charset.name());                            } catch (UnsupportedEncodingException var29) {                            }                            parameters.addParameter(name, value);                        }                    }                    success = true;                } catch (InvalidContentTypeException var30) {                    parameters.setParseFailedReason(FailReason.INVALID_CONTENT_TYPE);this.partsParseException = new ServletException(var30);                } catch (SizeException var31) {                    parameters.setParseFailedReason(FailReason.POST_TOO_LARGE);this.checkSwallowInput();this.partsParseException = new IllegalStateException(var31);                } catch (IOException var32) {                    parameters.setParseFailedReason(FailReason.IO_ERROR);this.partsParseException = new IOException(var32);                } catch (IllegalStateException var33) {                    IllegalStateException e = var33;this.checkSwallowInput();this.partsParseException = e;                }return;            }            parameters.setParseFailedReason(FailReason.MULTIPART_CONFIG_INVALID);this.partsParseException = new IOException(sm.getString("coyoteRequest.uploadLocationInvalid", new Object[]{location}));        } finally {if (this.partsParseException != null || !success) {                parameters.setParseFailedReason(FailReason.UNKNOWN);            }        }    }}

在以上代码逻辑中核心在这里的org.apache.tomcat.util.http.fileupload.FileUploadBase#parseRequest方法调用,其针对于每个Multipart请求参数在临时目录生成一个对应的临时文件,内容就是该参数的值

Tomcat Multipart类型参数处理

临时文件生成

public List<FileItem> parseRequest(RequestContext ctx) throws FileUploadException {    List<FileItem> items = new ArrayList();    boolean successful = false;    boolean var22 = false;    ArrayList var32;    FileItem fileItem;try {        var22 = true;// 解析获取Multipart中所有参数条目        FileItemIterator iter = this.getItemIterator(ctx);        FileItemFactory fileItemFactory = (FileItemFactory)Objects.requireNonNull(this.getFileItemFactory(), "No FileItemFactory has been set.");        byte[] buffer = new byte[8192];// 循环迭代为每个参数Item创建一个临时文件并调用Streams.copy将这个参数的请求值拷贝到这个临时文件中。while(true) {if (!iter.hasNext()) {                successful = true;                var32 = items;                var22 = false;break;            }if ((long)items.size() == this.fileCountMax) {throw new FileCountLimitExceededException("attachment"this.getFileCountMax());            }            FileItemStream item = iter.next();            String fileName = item.getName();            fileItem = fileItemFactory.createItem(item.getFieldName(), item.getContentType(), item.isFormField(), fileName);            items.add(fileItem);try {                Streams.copy(item.openStream(), fileItem.getOutputStream(), true, buffer);            } catch (FileUploadIOException var25) {                FileUploadIOException e = var25;throw (FileUploadException)e.getCause();            } catch (IOException var26) {                IOException e = var26;throw new IOFileUploadException(String.format("Processing of %s request failed. %s""multipart/form-data", e.getMessage()), e);            }            FileItemHeaders fih = item.getHeaders();            fileItem.setHeaders(fih);        }    } catch (FileUploadException var27) {        FileUploadException e = var27;throw e;    } catch (IOException var28) {        IOException e = var28;throw new FileUploadException(e.getMessage(), e);    } finally {if (var22) {if (!successful) {                Iterator var12 = items.iterator();while(var12.hasNext()) {                    FileItem fileItem = (FileItem)var12.next();try {                        fileItem.delete();                    } catch (Exception var23) {                    }                }            }        }    }if (!successful) {        Iterator var33 = items.iterator();while(var33.hasNext()) {            fileItem = (FileItem)var33.next();try {                fileItem.delete();            } catch (Exception var24) {            }        }    }return var32;}org.apache.tomcat.util.http.fileupload.disk.DiskFileItemFactory#createItempublic FileItem createItem(String fieldName, String contentType, boolean isFormField, String fileName) {    DiskFileItem result = new DiskFileItem(fieldName, contentType, isFormField, fileName, this.sizeThreshold, this.repository);    result.setDefaultCharset(this.defaultCharset);return result;}org.apache.tomcat.util.http.fileupload.disk.DiskFileItem#getOutputStreampublic OutputStream getOutputStream() {if (this.dfos == null) {        File outputFile = this.getTempFile();this.dfos = new DeferredFileOutputStream(this.sizeThreshold, outputFile);    }returnthis.dfos;}org.apache.tomcat.util.http.fileupload.disk.DiskFileItem#getTempFileprotected File getTempFile() {if (this.tempFile == null) {        File tempDir = this.repository;if (tempDir == null) {            tempDir = new File(System.getProperty("java.io.tmpdir"));        }        String tempFileName = String.format("upload_%s_%s.tmp", UID, getUniqueId());this.tempFile = new File(tempDir, tempFileName);    }returnthis.tempFile;}org.apache.tomcat.util.http.fileupload.disk.DiskFileItem#getUniqueIdprivate static String getUniqueId() {    int limit = 100000000;    int current = COUNTER.getAndIncrement();    String id = Integer.toString(current);if (current < 100000000) {        id = ("00000000" + id).substring(id.length());    }return id;}
Tomcat Multipart类型参数处理

目录位于当前底层Tomcat的运行时临时文件存储目录

Tomcat Multipart类型参数处理
Tomcat Multipart类型参数处理
Tomcat Multipart类型参数处理
Tomcat Multipart类型参数处理

临时文件删除

org.apache.tomcat.util.http.fileupload.disk.DiskFileItem#delete方法下断点,发现调用栈如下

delete:426, DiskFileItem (org.apache.tomcat.util.http.fileupload.disk)delete:53, ApplicationPart (org.apache.catalina.core)cleanupMultipart:134, StandardServletMultipartResolver (org.springframework.web.multipart.support)cleanupMultipart:1251, DispatcherServlet (org.springframework.web.servlet)doDispatch:1108, DispatcherServlet (org.springframework.web.servlet)doService:965, DispatcherServlet (org.springframework.web.servlet)processRequest:1006, FrameworkServlet (org.springframework.web.servlet)doPost:909, FrameworkServlet (org.springframework.web.servlet)service:555, HttpServlet (javax.servlet.http)service:883, FrameworkServlet (org.springframework.web.servlet)service:623, HttpServlet (javax.servlet.http)internalDoFilter:209, ApplicationFilterChain (org.apache.catalina.core)doFilter:153, ApplicationFilterChain (org.apache.catalina.core)doFilter:51, WsFilter (org.apache.tomcat.websocket.server)internalDoFilter:178, ApplicationFilterChain (org.apache.catalina.core)doFilter:153, ApplicationFilterChain (org.apache.catalina.core)doFilterInternal:28, SecurityFilter (com.ar3h.postgresqljdbcattack.filter)doFilter:117, OncePerRequestFilter (org.springframework.web.filter)internalDoFilter:178, ApplicationFilterChain (org.apache.catalina.core)doFilter:153, ApplicationFilterChain (org.apache.catalina.core)doFilterInternal:100, RequestContextFilter (org.springframework.web.filter)doFilter:117, OncePerRequestFilter (org.springframework.web.filter)internalDoFilter:178, ApplicationFilterChain (org.apache.catalina.core)doFilter:153, ApplicationFilterChain (org.apache.catalina.core)doFilterInternal:93, FormContentFilter (org.springframework.web.filter)doFilter:117, OncePerRequestFilter (org.springframework.web.filter)internalDoFilter:178, ApplicationFilterChain (org.apache.catalina.core)doFilter:153, ApplicationFilterChain (org.apache.catalina.core)doFilterInternal:201, CharacterEncodingFilter (org.springframework.web.filter)doFilter:117, OncePerRequestFilter (org.springframework.web.filter)internalDoFilter:178, ApplicationFilterChain (org.apache.catalina.core)doFilter:153, ApplicationFilterChain (org.apache.catalina.core)invoke:167, StandardWrapperValve (org.apache.catalina.core)invoke:90, StandardContextValve (org.apache.catalina.core)invoke:481, AuthenticatorBase (org.apache.catalina.authenticator)invoke:130, StandardHostValve (org.apache.catalina.core)invoke:93, ErrorReportValve (org.apache.catalina.valves)invoke:74, StandardEngineValve (org.apache.catalina.core)service:343, CoyoteAdapter (org.apache.catalina.connector)service:390, Http11Processor (org.apache.coyote.http11)process:63, AbstractProcessorLight (org.apache.coyote)process:926, AbstractProtocol$ConnectionHandler (org.apache.coyote)doRun:1791, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)run:52, SocketProcessorBase (org.apache.tomcat.util.net)runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)run:750, Thread (java.lang)

可以看到在org.springframework.web.servlet.DispatcherServlet#doDispatch方法处理完毕请求最后响应收尾的时候进行处理的

Tomcat Multipart类型参数处理
Tomcat Multipart类型参数处理
Tomcat Multipart类型参数处理
Tomcat Multipart类型参数处理
Tomcat Multipart类型参数处理
Tomcat Multipart类型参数处理
Tomcat Multipart类型参数处理
Tomcat Multipart类型参数处理

至此完成临时文件清除,不过这也证明了一点,在我们的控制器处理请求的时候这些临时文件是存在于磁盘上的。

PS:以上逻辑是Spring内部实现,Tomcat相关处理逻辑可按相同思路分析即可。

临时文件名特性

其实生成的临时文件我们是有办法区分对应的参数的,原理如下

Tomcat Multipart类型参数处理
Tomcat Multipart类型参数处理
Tomcat Multipart类型参数处理

可以看到这里是自增计数的。

Tomcat Multipart类型参数处理

原文始发于微信公众号(安全之道):Tomcat Multipart类型参数处理

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年5月27日09:35:28
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Tomcat Multipart类型参数处理https://cn-sec.com/archives/4002388.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息