File Upload Scripts
CGI file upload scripts: parsing, validating, and storing uploaded files safely
How does a CGI script handle file uploads from a browser?
A browser sends a file upload as a multipart/form-data POST request, which encodes each field and each file in a separate boundary-delimited section. A CGI script reads and parses this stream, extracts the filename and content, validates the file type and size, generates a safe storage name, and writes the file to a designated upload directory that is outside the web root or configured to prevent script execution.
What multipart/form-data means for a CGI script
When an HTML form includes an <input type='file'> element, the browser sets the form's enctype to multipart/form-data and sends a body where each part is separated by a unique boundary string. Each part carries its own Content-Disposition header naming the field and, for files, the original filename and MIME type. Parsing this format by hand is error-prone; use a library: CGI.pm in Perl parses it automatically when you call param(), and Python's cgi module does the same with FieldStorage.
The raw content-type header from the browser is informational and user-controlled, so never trust it as the only check on what was uploaded. A PNG with its first four bytes changed to PHP code is still a PHP script if your server is configured to execute anything in the upload directory. The MIME type the browser sends reflects the extension, not the actual file content.
Validating file types and sizes server-side
Check the file's magic bytes (the first few bytes of content) rather than relying on the browser-supplied MIME type or the filename extension. A JPEG starts with FF D8 FF; a PNG starts with 89 50 4E 47. If you open the first sixteen bytes of the upload and they do not match the expected signature for the types you allow, reject the upload before writing it to disk. Extensions and browser-reported types can be spoofed; magic bytes are harder to fake while keeping the file parseable by the target application.
Set a maximum file size and enforce it in the script, not just in the HTML form's accept attribute. The form attribute is a UI hint only; a script posting directly ignores it. Read Content-Length from the environment variable and reject anything over your limit before reading the body, or set a hard limit in your web server configuration to avoid reading a multi-gigabyte upload into memory.
Safe storage: directory permissions and naming
Store uploaded files outside the document root or in a directory explicitly configured to prohibit script execution. If a user uploads a Perl script disguised as an image and your server executes any file in the upload directory with the .pl extension, you have a remote code execution vulnerability. The safest configuration is a directory the web server user can write to but cannot serve directly, combined with a separate download script that reads and proxies the file.
Never use the original filename as the on-disk name. Filenames from browsers can contain path traversal sequences (../), null bytes, control characters, or names that conflict with existing files. Generate a server-side name: a UUID or a hash of the content plus a sanitized extension is common. Store the original filename separately in a database or metadata file if you need to preserve it for display.
Returning the uploaded file to users
If users need to retrieve uploaded files, write a download CGI script that reads the filename from a request parameter, looks it up in your metadata (never constructs the path directly from the parameter), checks permissions, and streams the file contents with the correct Content-Type and Content-Disposition headers. This keeps the actual storage path hidden and lets you add access control without changing how files are stored.
For public-facing files that do not need access control, it is simpler to serve them directly from a subdirectory, but ensure that directory is configured with Options -ExecCGI (Apache) or the equivalent to prevent execution of uploaded scripts. Confirm this with your host, since shared hosting configurations vary.
What to know
Key points
- Check magic bytes, not just the extension or MIME type. The first bytes of the file content reveal the real format; extensions and browser types can be spoofed.
- Store files outside the web root or in a no-execute directory. An upload directory that executes scripts is a remote code execution risk.
- Generate server-side filenames. Never use the browser-supplied filename on disk; it can contain path traversal or conflicting names.
- Enforce size limits before reading the body. Check Content-Length in the environment and reject oversized uploads early.
- Serve downloads through a script, not direct links. A download proxy lets you add access control and hides your storage layout.
Get help
Listings and a local agent, when you are ready
Need a CGI script built, debugged, or a hosting environment configured? Forms use a clearly-marked placeholder endpoint until wired to a real system.
Reserved for an affiliate link to a vetted shared or VPS hosting provider that supports CGI and Perl/Python execution. Operator wires affiliate link here.
Self-hosted lead form for custom CGI development inquiries. Placeholder endpoint until wired to the operator's CRM or email handler.
Open inquiry form →Custom CGI development
Hosting setup help
Questions