RFC 7578 - Returning Values from Forms: multipart/form-data
Publication Date: July 2015
Status: Standards Track
Author: L. Masinter (Adobe)
Obsoletes: RFC 2388
Abstract
This specification defines the multipart/form-data media type, which can be used by a wide variety of applications and transported by a wide variety of protocols as a way of returning a set of values as the result of a user filling out a form. This document obsoletes RFC 2388.
Contents
Why multipart/form-data?
Limitations of application/x-www-form-urlencoded
Default form encoding:
Content-Type: application/x-www-form-urlencoded
Data format:
name=Alice&age=30&city=New%20York
Problems:
❌ Text only
❌ Binary data requires Base64 encoding (33% size increase)
❌ Inefficient for large files
❌ No multi-file support
Advantages of multipart/form-data
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
Benefits:
✅ Supports binary data (no encoding needed)
✅ Supports file uploads
✅ Supports multiple files
✅ Independent encoding per field
✅ Efficient large file transfer
Basic Format
Message Structure
POST /upload HTTP/1.1
Host: example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: 345
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="username"
Alice
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="avatar"; filename="photo.jpg"
Content-Type: image/jpeg
[Binary image data]
------WebKitFormBoundary7MA4YWxkTrZu0gW--
Key Components
1. Boundary (Delimiter)
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
Purpose: Separates different fields
Requirement: Must not appear in data
Format: Usually a random string
2. Part
Each field is a Part:
------WebKitFormBoundary
Content-Disposition: form-data; name="fieldname"
[blank line]
[field value]
3. Content-Disposition
Required header:
Content-Disposition: form-data; name="field_name"
For file fields:
Content-Disposition: form-data; name="file"; filename="example.txt"
HTML Form Examples
Basic File Upload
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="text" name="username" value="Alice">
<input type="file" name="avatar">
<button type="submit">Upload</button>
</form>
Generated HTTP Request:
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----Boundary123
------Boundary123
Content-Disposition: form-data; name="username"
Alice
------Boundary123
Content-Disposition: form-data; name="avatar"; filename="photo.jpg"
Content-Type: image/jpeg
[Binary image data]
------Boundary123--
Multiple File Upload
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="files" multiple>
<button type="submit">Upload</button>
</form>
Generated Request:
------Boundary123
Content-Disposition: form-data; name="files"; filename="file1.txt"
Content-Type: text/plain
[File 1 content]
------Boundary123
Content-Disposition: form-data; name="files"; filename="file2.pdf"
Content-Type: application/pdf
[File 2 content]
------Boundary123--
Programming Implementation
JavaScript (Fetch API)
// Method 1: Using FormData (Recommended)
const formData = new FormData();
formData.append('username', 'Alice');
formData.append('avatar', fileInput.files[0]);
fetch('/upload', {
method: 'POST',
body: formData
// Don't set Content-Type manually! Browser will add boundary automatically
});
// Method 2: Multiple file upload
const files = document.querySelector('input[type="file"]').files;
const formData = new FormData();
for (let file of files) {
formData.append('files', file);
}
fetch('/upload', {
method: 'POST',
body: formData
});
Python (requests)
import requests
# Single file upload
files = {'avatar': open('photo.jpg', 'rb')}
data = {'username': 'Alice'}
response = requests.post('https://example.com/upload',
files=files,
data=data)
# Multiple file upload
files = [
('files', ('file1.txt', open('file1.txt', 'rb'), 'text/plain')),
('files', ('file2.pdf', open('file2.pdf', 'rb'), 'application/pdf'))
]
response = requests.post('https://example.com/upload', files=files)
Node.js (Express)
const express = require('express');
const multer = require('multer');
const app = express();
const upload = multer({ dest: 'uploads/' });
// Single file
app.post('/upload', upload.single('avatar'), (req, res) => {
console.log(req.file); // File info
console.log(req.body); // Other fields
res.send('Upload successful');
});
// Multiple files
app.post('/upload-multi', upload.array('files', 10), (req, res) => {
console.log(req.files); // File array
res.send(`Uploaded ${req.files.length} files`);
});
PHP
<?php
// Handle upload
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Regular field
$username = $_POST['username'];
// File field
if (isset($_FILES['avatar'])) {
$file = $_FILES['avatar'];
$filename = $file['name'];
$tmpPath = $file['tmp_name'];
$size = $file['size'];
$type = $file['type'];
// Move file
move_uploaded_file($tmpPath, "uploads/$filename");
echo "Upload successful: $filename";
}
}
?>
Field Details
Content-Disposition
Syntax:
Content-Disposition: form-data; name="field_name"; filename="file_name"
Parameters:
| Parameter | Required | Description |
|---|---|---|
| name | ✅ | Form field name |
| filename | For files | Original filename |
| filename* | ❌ | RFC 2231 encoded filename |
Examples:
Regular field:
Content-Disposition: form-data; name="username"
File field:
Content-Disposition: form-data; name="file"; filename="report.pdf"
Internationalized filename (RFC 2231):
Content-Disposition: form-data; name="file";
filename*=UTF-8''%E6%8A%A5%E5%91%8A.pdf
Content-Type
Optional header (defaults to text/plain):
Text field (usually omitted):
Content-Disposition: form-data; name="message"
Hello World
File field (recommended):
Content-Disposition: form-data; name="file"; filename="image.png"
Content-Type: image/png
[PNG binary data]
Common MIME Types:
text/plain
image/jpeg, image/png, image/gif
application/pdf
application/zip
video/mp4
audio/mpeg
Boundary (Delimiter)
Generation Rules
Requirements:
1. Must not appear in data
2. 70 characters or less (recommended)
3. Composed of ASCII printable characters
Common formats:
----WebKitFormBoundary[random characters]
----Boundary[timestamp][random number]
Examples:
----WebKitFormBoundary7MA4YWxkTrZu0gW
----1234567890123456789012345678
Usage
In data:
------WebKitFormBoundary (start)
[Part content]
------WebKitFormBoundary (next Part)
[Part content]
------WebKitFormBoundary-- (end, note the two dashes)
Filename Handling
Special Characters
Problem: Filename contains non-ASCII characters
Solution 1: RFC 2231 encoding
Content-Disposition: form-data; name="file";
filename*=UTF-8''%E6%96%87%E4%BB%B6.txt
Solution 2: URL encoding (not recommended)
filename="%E6%96%87%E4%BB%B6.txt"
Solution 3: ASCII only (most compatible)
filename="file.txt"
Security Considerations
⚠️ Dangerous filenames:
filename="../../../etc/passwd" (path traversal)
filename="evil.php" (executable file)
filename="file<script>.txt" (injection attack)
✅ Safe handling:
1. Remove path separators (/ \)
2. Whitelist extensions
3. Rename to UUID
4. Limit length (e.g., 255 characters)
5. Virus scan
Example:
filename = secure_filename(original_filename)
# "../../evil.php" → "evil.php" → "f81d4fae.dat"
Large File Upload
Stream Processing
// Node.js (Busboy)
const Busboy = require('busboy');
app.post('/upload', (req, res) => {
const busboy = Busboy({ headers: req.headers });
busboy.on('file', (name, file, info) => {
const { filename, encoding, mimeType } = info;
const saveTo = `uploads/${filename}`;
// Stream write, no memory usage
file.pipe(fs.createWriteStream(saveTo));
});
busboy.on('finish', () => {
res.send('Upload complete');
});
req.pipe(busboy);
});
Chunked Upload
// Client: Upload large file in chunks
const CHUNK_SIZE = 1024 * 1024; // 1MB
const file = fileInput.files[0];
const chunks = Math.ceil(file.size / CHUNK_SIZE);
for (let i = 0; i < chunks; i++) {
const start = i * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, file.size);
const chunk = file.slice(start, end);
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('chunkIndex', i);
formData.append('totalChunks', chunks);
formData.append('filename', file.name);
await fetch('/upload-chunk', {
method: 'POST',
body: formData
});
}
Debugging Tips
Chrome DevTools
1. Network tab
2. Select upload request
3. View Request Payload
- See complete multipart/form-data structure
- boundary delimiter
- Headers and content of each Part
curl Testing
# Simple upload
curl -F "username=Alice" \
-F "[email protected]" \
https://example.com/upload
# Specify Content-Type
curl -F "[email protected];type=application/pdf" \
https://example.com/upload
# Multiple files
curl -F "[email protected]" \
-F "[email protected]" \
https://example.com/upload
# Verbose output
curl -v -F "[email protected]" https://example.com/upload
Common Questions
Q: When to use multipart/form-data?
Use cases:
✅ File uploads
✅ Binary data transfer
✅ Mixed text and files
Don't use for:
❌ Plain text forms (use application/x-www-form-urlencoded)
❌ JSON APIs (use application/json)
❌ Many small fields (inefficient)
Q: Performance impact?
Pros:
+ No encoding needed for binary data
Cons:
- Boundary adds overhead
- Extra headers per Part
- More complex parsing than JSON
Recommendation:
- Use when uploading files
- Consider JSON for data only
Q: Maximum file size?
Limits from:
1. Server configuration
- Nginx: client_max_body_size 100M;
- Apache: LimitRequestBody 104857600
2. Language/framework
- PHP: upload_max_filesize = 100M
- Node.js: multer({ limits: { fileSize: 100MB } })
3. Browser
- Usually no limit
- Very large files may cause memory issues
multipart/form-data is the standard for web file uploads.
Key Points:
- 📋 Form Encoding: HTML form file uploads
- 📦 Binary Support: No Base64 encoding needed
- 🔀 Mixed Data: Text and files together
- 🎯 Efficient Transfer: Large file friendly
Quick Usage:
<form enctype="multipart/form-data">
<input type="file" name="file">
</form>
const formData = new FormData();
formData.append('file', file);
fetch('/upload', { method: 'POST', body: formData });
Security Tips:
- Validate file types
- Limit file size
- Sanitize filenames
- Virus scan
Related RFCs:
- RFC 2046 - MIME Media Types
- RFC 2231 - Parameter Value Encoding
- RFC 7233 - Range Requests (resumable uploads)