NetBurner 3.5.8
PDF Version
Multipart Form Post

Example Path: examples/Web/MultipartFormPost

Multipart Form Post Example

Overview

This NetBurner example demonstrates how to handle multipart/form-data HTTP POST requests on embedded devices. Unlike standard URL-encoded forms, multipart forms can transmit both file content and regular form fields in a single request, making them essential for file upload functionality.

Features

  • File upload handling with multipart/form-data encoding
  • Mixed content processing (files, text fields, and checkboxes)
  • Safe file data extraction from POST requests
  • Dynamic response generation with file content display
  • Text and binary/hex dump display modes
  • HTML entity encoding for safe content display
  • Configurable maximum file size (default 500KB)

Key Concepts

Multipart vs URL-Encoded Forms

URL-encoded forms (application/x-www-form-urlencoded):

  • Used for simple form data (text fields, selections)
  • Data is encoded as key=value pairs separated by &
  • Not suitable for file uploads
  • Smaller overhead for simple forms

Multipart forms (multipart/form-data):

  • Required for file uploads
  • Each field is sent as a separate "part" with headers
  • Can mix files and regular form fields in one request
  • Set with: enctype="multipart/form-data"

POST Event Sequence

When a multipart form is submitted, the NetBurner HTTP server calls your callback function multiple times with different events:

+----------------+
| Form Submitted |
+-------v--------+
|
+-------v--------+
| eStartingPost | Called once at the beginning
| (Initialize) | - Clear variables
+-------v--------+ - Reset state
|
+-------v--------+
| eVariable | Called for each text field
| (name, value) | - Process text inputs
+-------v--------+ - Handle checkboxes
| (may be called multiple times)
+-------v--------+
| eFile | Called for each file input
| (name, struct) | - Read file data from fd
+-------v--------+ - Store filename
| (may be called multiple times)
+-------v--------+
| eEndOfPost | Called once at the end
| (Send response)| - Generate HTML response
+----------------+ - Redirect to results page

Project Structure

MultipartFormPost/
|
+-- src/
| +-- main.cpp Application entry point
| +-- formcode.cpp POST handling and file processing
| +-- htmldata.cpp Auto-generated from html/ directory
|
+-- html/
| +-- index.html Upload form with multipart encoding
| +-- images/
| +-- netburner-logo.gif
|
+-- makefile Build configuration
+-- ReadMe.txt This file

Architecture

System Flow

+----------------+ +----------------+ +------------------+
| | | | | |
| Web Browser | -----> | HTTP Server | -----> | POST Callback |
| | | | | |
| [Upload Form] | | Parse multipart| | MultipartPost |
| enctype= | | boundaries | | CallBack() |
| multipart/ | | Extract parts | | |
| form-data | | | | - eStartingPost |
| | | | | - eVariable |
+----------------+ +----------------+ | - eFile |
| - eEndOfPost |
+--------v---------+
|
+--------v---------+
| |
| GenerateResults |
| Page() |
| |
| - Show fields |
| - Display file |
| |
+------------------+

File Upload Processing

When the eFile event is received, the pValue parameter points to a FilePostStruct containing information about the uploaded file:

+------------------+
| FilePostStruct |
+------------------+
| fd: int | ---> File descriptor for reading content
| pFileName: char* | ---> Original filename from browser
+------------------+
|
v
+------------------+ +------------------+ +------------------+
| | | | | |
| Check dataavail | -----> | read(fd, buf, n) | -----> | Store in buffer |
| (fd) | | | | FileRxBuffer[] |
| | | Returns bytes | | |
+------------------+ +------------------+ +------------------+
| |
| +----------------------------+
v v
+------------------+ +------------------+
| | | |
| close(fd) | | Track FileSize |
| When done | | |
| | | |
+------------------+ +------------------+
int dataavail(int fd)
Check the specified file descriptor to detmeine if data is available to be read.
int read(int fd, char *buf, int nbytes)
Read data from a file descriptor (fd).
int close(int fd)
Close the specified file descriptor and free the associated resources.

Implementation Details

HTML Form Setup

The HTML form MUST include enctype="multipart/form-data" for file uploads:

<form action="upload" enctype="multipart/form-data" method="POST">
<!-- Text fields generate eVariable events -->
<input type="text" name="description" value="">
<!-- File input generates eFile event -->
<input type="file" name="userfile">
<!-- Checkboxes only sent when checked -->
<input type="checkbox" name="show_binary">
<input type="submit" value="Upload">
</form>

POST Callback Registration

Register a callback function to handle POST requests matching a URL pattern:

// Register callback for URLs matching "upload*"
// Matches: /upload, /upload.html, /upload_file, etc.
HtmlPostVariableListCallback uploadPostHandler("upload*", MultipartPostCallBack);
Implements the HtmlPostVariableListHandler class as a function pointer callback for HTTP POST submiss...
Definition httppost.h:131

Callback Function Structure

int MultipartPostCallBack(int sock, PostEvents event,
const char *pName, const char *pValue)
{
switch (event)
{
case eStartingPost:
// Initialize variables - called once at start
FileSize = 0;
memset(description, 0, sizeof(description));
break;
case eVariable:
// Process text fields and checkboxes
// pName = field name, pValue = field value
if (strcmp("description", pName) == 0)
{
strncpy(description, pValue, MAX_LEN - 1);
}
else if (strcmp("show_binary", pName) == 0)
{
// Checkbox is present, so it was checked
showBinary = true;
}
break;
case eFile:
// Process file upload
// pValue points to FilePostStruct
ProcessPostFile(pValue);
break;
case eEndOfPost:
// All data received - generate response
GenerateResultsPage(sock);
break;
}
return 0;
}

Reading File Data

The eFile event provides a FilePostStruct with file information:

void ProcessPostFile(const char *pValue)
{
FilePostStruct *pFps = (FilePostStruct *)pValue;
// Get original filename if available
if (pFps->pFileName != nullptr)
{
strncpy(uploadedFileName, pFps->pFileName, MAX_LEN - 1);
}
// Read file data from the file descriptor
while (dataavail(pFps->fd))
{
int remaining = BUFFER_SIZE - FileSize;
int bytesRead = read(pFps->fd, buffer + FileSize, remaining);
if (bytesRead > 0)
{
FileSize += bytesRead;
}
else
{
break; // Error or end of data
}
if (FileSize >= BUFFER_SIZE)
{
break; // Buffer full
}
}
// Always close the file descriptor when done
close(pFps->fd);
}

Generating the Response

After processing, generate an HTML response in eEndOfPost:

void GenerateResultsPage(int sock)
{
// Send HTTP header first
// Write HTML content
writestring(sock, "<html><body>");
writestring(sock, "<h1>Upload Complete</h1>");
// Display form fields
fdprintf(sock, "<p>Description: %s</p>", description);
// Display file info
fdprintf(sock, "<p>Filename: %s</p>", uploadedFileName);
fdprintf(sock, "<p>Size: %d bytes</p>", FileSize);
// Display file content
ShowFileData(sock, showBinary);
writestring(sock, "</body></html>");
}
int fdprintf(int fd, const char *format,...)
Print formatted output to a file descriptor.
int writestring(int fd, const char *str)
Write a null terminated ascii string to the stream associated with a file descriptor (fd)....
void SendHTMLHeader(int sock)
Send a HTML response header.

Comparison with HtmlFormPost Example

+----------------------+---------------------------+---------------------------+
| Feature | HtmlFormPost | MultipartFormPost |
+----------------------+---------------------------+---------------------------+
| Encoding | application/x-www- | multipart/form-data |
| | form-urlencoded | |
+----------------------+---------------------------+---------------------------+
| File uploads | No | Yes |
+----------------------+---------------------------+---------------------------+
| Form attribute | method=post | enctype="multipart/ |
| | | form-data" method="POST" |
+----------------------+---------------------------+---------------------------+
| File event | Not used | eFile with FilePostStruct |
+----------------------+---------------------------+---------------------------+
| Use case | Simple text forms | File uploads, mixed |
| | | content forms |
+----------------------+---------------------------+---------------------------+

Memory Considerations

+---------------------------+------------------+
| Component | Size |
+---------------------------+------------------+
| File buffer | 500 KB (config) |
| Text field buffers | 128 bytes each |
| Filename buffer | 128 bytes |
+---------------------------+------------------+
Total static allocation: ~500 KB + overhead
  • No dynamic memory allocation used
  • Large files are truncated to buffer size
  • Adjust FILE_BUFFER_SIZE in formcode.cpp for different limits

Console Output

When a file is uploaded, the console displays:

Application: Multipart Form Post Example
NNDK Revision: 3.x.x Build xxxx
Access the web interface at: http://<device_ip>/
--- Starting new multipart POST ---
Variable: uploader_name = "John Doe"
Variable: description = "Test file upload"
Variable: show_binary = "on"
File upload received: userfile
Uploaded file name: example.txt
File size: 1234 bytes
--- End of POST ---
Summary: Uploader=John Doe, Description=Test file upload, FileSize=1234

Common Issues and Solutions

File Not Uploading

Symptom: eFile event never fires, only eVariable events received

Cause: Missing enctype attribute on form

Solution: Add enctype="multipart/form-data" to the form tag

<!-- Wrong -->
<form action="upload" method="POST">
<!-- Correct -->
<form action="upload" enctype="multipart/form-data" method="POST">

File Data Empty

Symptom: eFile fires but FileSize is 0

Causes:

  • No file was selected in the browser
  • File read error occurred
  • File descriptor was closed prematurely

Solution: Check that a file is selected before submitting

Checkbox Value Not Received

Symptom: Checkbox field not seen in eVariable events

Note: This is normal browser behavior - unchecked checkboxes are NOT sent in the form data

Solution: Initialize checkbox variables to false in eStartingPost, then set to true only when the variable is received

case eStartingPost:
showBinary = false; // Default to unchecked
break;
case eVariable:
if (strcmp("show_binary", pName) == 0)
{
showBinary = true; // Checkbox was checked
}
break;

Response Not Displayed

Symptom: Browser shows error or blank page after upload

Causes:

  • No response sent in eEndOfPost
  • Missing HTTP header
  • Response sent in wrong event

Solution: Always send response in eEndOfPost:

case eEndOfPost:
SendHTMLHeader(sock); // Must call first
writestring(sock, "<html>...</html>");
break;

Large File Truncation

Symptom: Only part of large file is received

Cause: File exceeds FILE_BUFFER_SIZE

Solution: Increase FILE_BUFFER_SIZE in formcode.cpp or inform user of the limit. Current limit: 500KB

Security Considerations

  • HTML special characters are encoded to prevent XSS attacks
  • File size is limited to prevent memory exhaustion
  • No persistent storage - uploaded data is temporary
  • Implement authentication for production deployments
  • Consider validating file types if needed

Dependencies

Required NetBurner headers:

+----------------+----------------------------------------+
| Header | Purpose |
+----------------+----------------------------------------+
| <init.h> | System initialization |
| <nbrtos.h> | RTOS functions (OSTimeDly, etc.) |
| <http.h> | HTTP server functions |
| <httppost.h> | POST handling, FilePostStruct |
| <iosys.h> | File I/O (read, close, dataavail) |
| <fdprintf.h> | Socket printf functions |
| <string.h> | String manipulation |
+----------------+----------------------------------------+
void OSTimeDly(uint32_t to_count)
Delay the task until the specified value of the system timer ticks. The number of system ticks per se...
Definition nbrtos.h:1855
void init()
System initialization. Ideally called at the beginning of all applications, since the easiest Recover...

API Reference

PostEvents Enumeration

+----------------+------------------------------------------------+
| Event | Description |
+----------------+------------------------------------------------+
| eStartingPost | Beginning of POST, initialize state |
| eVariable | Form field received (name/value pair) |
| eFile | File upload field (pValue = FilePostStruct*) |
| eEndOfPost | All data received, send response now |
+----------------+------------------------------------------------+

FilePostStruct Members

+----------------+---------------+--------------------------------+
| Member | Type | Description |
+----------------+---------------+--------------------------------+
| fd | int | File descriptor for reading |
| pFileName | const char* | Original filename from browser |
+----------------+---------------+--------------------------------+

Key Functions

+----------------------------------+----------------------------------+
| Function | Description |
+----------------------------------+----------------------------------+
| HtmlPostVariableListCallback | Register POST handler for URL |
| SendHTMLHeader(sock) | Send HTTP 200 OK with HTML type |
| RedirectResponse(sock, url) | Send HTTP redirect |
| dataavail(fd) | Check if data available on fd |
| read(fd, buf, len) | Read bytes from file descriptor |
| close(fd) | Close file descriptor |
| writestring(sock, str) | Write string to socket |
| fdprintf(sock, fmt, ...) | Formatted write to socket |
+----------------------------------+----------------------------------+
int write(int fd, const char *buf, int nbytes)
Write data to the stream associated with a file descriptor (fd). Can be used to write data to stdio,...
void RedirectResponse(int sock, PCSTR new_page)
Redirect a HTTP request to a different page.

Related Examples

  • HtmlFormPost - URL-encoded form handling (simpler, no files)
  • FilePost - Basic file upload example
  • FilePost_Handoff - File upload with file system storage