请求解析
Tomcat在处理请求的时候会调用org.apache.catalina.connector.Request#parseParameters方法处理请求参数
如果当前请求的Content-Type为multipart/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请求参数在临时目录生成一个对应的临时文件,内容就是该参数的值
临时文件生成
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#createItem
public 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#getOutputStream
public 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#getTempFile
protected 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#getUniqueId
private 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的运行时临时文件存储目录
临时文件删除
在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方法处理完毕请求最后响应收尾的时候进行处理的
至此完成临时文件清除,不过这也证明了一点,在我们的控制器处理请求的时候这些临时文件是存在于磁盘上的。
PS:以上逻辑是Spring内部实现,Tomcat相关处理逻辑可按相同思路分析即可。
临时文件名特性
其实生成的临时文件我们是有办法区分对应的参数的,原理如下
可以看到这里是自增计数的。
原文始发于微信公众号(安全之道):Tomcat Multipart类型参数处理
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论