Upload File Helper Method Documentation
Overview
The handleMultipartFormData method is a private helper method that parses multipart/form-data
encoded HTTP request bodies and extracts uploaded files along with associated metadata. It
performs low-level byte array manipulation to parse the multipart structure, validate file
types, and save allowed files to the appropriate user directory.
Purpose
This method handles the complex task of parsing multipart form data without relying on external
libraries. It extracts file content and metadata (filename, path) from the raw byte array,
validates file types against allowed extensions, and delegates file saving to the saveFile()
method. It provides Japanese-language success/error messages for user feedback.
Method Signature
private static String handleMultipartFormData(
byte[] body,
String contentType,
String user
) throws IOException
Parameters
- body: The raw byte array containing the complete multipart form data
- contentType: The Content-Type header value (e.g., "multipart/form-data; boundary=----WebKitFormBoundary...")
- user: The authenticated username, used to determine the target save directory
Return Value
Returns a Japanese-language status message:
- "アップロードが成功しました。" (Upload succeeded) - File was found and validated
- "アップロードのエラー!" (Upload error) - No valid file was found or processed
Note: The success message indicates file validation passed, but doesn't guarantee
the file was saved successfully (that happens after the return).
Core Functionality
This method performs the following key operations:
1. Boundary Extraction
- Extracts the boundary marker from the Content-Type header by splitting on "boundary="
- Constructs boundary bytes with "--" prefix for normal boundaries
- Constructs closing boundary bytes with "--" prefix and "--" suffix
2. Multipart Part Iteration
- Iterates through the body searching for boundary markers
- Extracts each part between boundaries
- Stops when the closing boundary is found or no more boundaries exist
3. Header and Content Separation
For each part:
- Searches for the "\r\n\r\n" sequence that separates headers from content
- Extracts headers as a UTF-8 string
- Extracts content as a byte array (preserving binary data)
- Trims trailing newlines from parts
4. Part Type Identification
Identifies two types of form parts:
- File parts: Headers contain 'filename="..."' - contains uploaded file data
- Text fields: Headers contain 'name="path"' - contains the target directory path
5. File Extension Validation
When a file is found:
- Extracts the filename using extractFileName() helper method
- Converts filename to lowercase for case-insensitive comparison
- Checks against three extension lists: TEXT_EXTENSIONS, IMAGE_EXTENSIONS, VIDEO_EXTENSIONS
- Sets allowed flag to true if the file extension matches any allowed type
6. File Saving
- After processing all parts, checks if valid file data was found and allowed flag is true
- Calls saveFile() with filename, file data bytes, path, and username
- saveFile() handles the actual disk write operation
Processing Flow
- Extract boundary marker from Content-Type header
- Initialize tracking variables (data, allowed, fileName, path, msg)
- Loop through body searching for boundaries:
- Find next boundary marker
- Check if it's the closing boundary (exit if so)
- Extract part between current and next boundary
- Trim trailing newlines from part
- Log preview of first 200 bytes
- Split part into headers and content
- If headers contain filename: extract filename, store file data, validate extension
- If headers contain name="path": extract path value
- Move to next boundary
- After loop: if data exists and is allowed, call saveFile()
- Return success or error message
Allowed File Extensions
The method validates files against three extension sets:
- TEXT_EXTENSIONS: Text-based files (likely .html, .css, .js, .txt, .php, etc.)
- IMAGE_EXTENSIONS: Image files (likely .jpg, .png, .gif, .svg, etc.)
- VIDEO_EXTENSIONS: Video files (likely .mp4, .webm, .mov, etc.)
Files with extensions not in any of these lists are rejected (allowed remains false).
Helper Methods
This method relies on several utility methods:
- indexOf(byte[] array, byte[] target, int start): Finds the index of a byte sequence in an array
- startsWith(byte[] array, int offset, byte[] prefix): Checks if array starts with prefix at given offset
- extractFileName(String headers): Parses the filename from Content-Disposition header
- saveFile(String fileName, byte[] data, String path, String user): Writes file data to disk
Error Handling
- Missing header separator: Logs error and skips the part (continues processing)
- No boundary found: Breaks loop and returns current message state
- Invalid file extension: File data is collected but not saved (allowed remains false)
- IOException: Thrown to caller if file operations fail
Logging
The method provides extensive console logging:
- Logs preview of first 200 bytes of each part
- Logs extracted filename when found
- Logs received path value when found
- Logs error when header/content separator not found
Security Features
- Extension whitelist: Only allows files with approved extensions
- Case-insensitive matching: Prevents bypass via uppercase extensions
- User-scoped saving: Files saved to user-specific directories
- Binary-safe processing: Handles file content as raw bytes until validation
- Path validation: Path parameter is passed to saveFile() for scoping
Behavior Notes
- Default message is error (Japanese: "アップロードのエラー!"), changed to success only when valid file found
- Success message indicates file was found and validated, not necessarily saved successfully
- Only saves the last valid file found if multiple files are in the upload (data is overwritten in loop)
- Path field can be empty - defaults to root of user's static directory
- Continues processing even if one part fails to parse (logs error and continues)
- Trailing newlines are stripped from all parts before processing
- Preview logging shows first 200 bytes of each part in UTF-8 (may show garbled text for binary files)
Performance Considerations
- Memory usage: Creates multiple byte array copies during processing
- Linear search: Uses indexOf() which scans byte arrays linearly
- No streaming: Entire body must be in memory before processing
- Multiple iterations: Extension validation loops through multiple sets
Limitations
- Only processes the last file if multiple files are uploaded
- Success message doesn't confirm actual file save (saveFile() called after return)
- No size limits enforced within this method
- Error messages in Japanese may not be suitable for all user bases
- No validation of filename for path traversal characters
- Cannot handle malformed multipart data gracefully (may throw exceptions)
Potential Improvements
- Support multiple file uploads (store all files, not just last one)
- Return more specific error codes (invalid extension, parse error, etc.)
- Add filename sanitization to prevent path traversal
- Make success/error messages configurable or internationalized
- Validate file content matches extension (magic number checking)
- Add file size validation
- Improve error recovery for malformed parts
- Return success based on actual saveFile() result
- Use more efficient byte searching algorithms
flowchart TD
A(["Start handleMultipartFormData"]) --> B["Extract boundary from contentType
Split on boundary="]
B --> C["Construct boundary bytes:
boundaryBytes = -- + boundary
closingBoundaryBytes = -- + boundary + --"]
C --> D["Initialize variables:
msg = Upload error
pos = 0, data = null
allowed = false, fileName, path = empty"]
D --> E["Start infinite loop"]
E --> F["Find next boundary:
boundaryIndex = indexOf body boundaryBytes pos"]
F --> G{"boundaryIndex
== -1?"}
G -->|Yes| SAVE["AFTER LOOP"]
G -->|No| H{"Is closing
boundary?"}
H -->|Yes| SAVE
H -->|No| I["Move pos after boundary + CRLF"]
I --> J["Find next boundary:
nextBoundary = indexOf body boundaryBytes pos"]
J --> K{"nextBoundary
== -1?"}
K -->|Yes| SAVE
K -->|No| L["Extract part:
part = body pos:nextBoundary"]
L --> M["Trim trailing newlines from part"]
M --> N["Log first 200 bytes preview"]
N --> O["Find header/content separator:
headerEnd = indexOf part CRLF CRLF 0"]
O --> P{"headerEnd
== -1?"}
P -->|Yes| Q["Log error: Failed to find separator
Continue to next iteration"]
P -->|No| R["Split part:
headers = part 0:headerEnd
fileContent = part headerEnd+4:end"]
Q --> S["Set pos = nextBoundary"]
S --> E
R --> T{"Headers contain
filename=?"}
T -->|Yes| U["Extract fileName using extractFileName
Log: File name: fileName"]
T -->|No| V{"Headers contain
name=path?"}
U --> W["Store file data:
data = fileContent"]
W --> X["Set msg = Upload succeeded"]
X --> Y["Convert fileName to lowercase"]
Y --> Z["Initialize allowed = false"]
Z --> AA["Create extension list:
TEXT IMAGE VIDEO extensions"]
AA --> AB["Loop through extension sets"]
AB --> AC["Loop through extensions in set"]
AC --> AD{"fileName ends with
extension case-insensitive?"}
AD -->|Yes| AE["Set allowed = true
Break inner loop"]
AD -->|No| AF["Continue to next extension"]
AE --> AG{"allowed
is true?"}
AF --> AC
AG -->|Yes| AH["Break outer loop"]
AG -->|No| AB
AH --> S
V -->|Yes| AI["Extract path from fileContent:
path = new String fileContent trim
Log: Recived path: path"]
V -->|No| S
AI --> S
SAVE{"data != null
AND allowed == true?"}
SAVE -->|Yes| CALL["Call saveFile fileName data path user"]
SAVE -->|No| RETURN
CALL --> RETURN["Return msg"]
RETURN --> ZZ(["End"])