INNORIX
Transfer BuilderTransfer FinderDevelopersResourcesCustomers
Start Free
INNORIX

LET FILES
MOVE THEMSELVES

INNORIX provides enterprise file infrastructure for moving and automating files across every system and environment.
Trusted by more than 5,000 enterprise and government agencies.

START HERE

  • Build the Transfer You Need
  • Find the Transfer You Need

POPULAR TRANSFERS

  • Sync Team Folders
  • Send Large Files to Clients
  • Explore Files Across Systems
  • Migrate FTP, SFTP, SCP & rsync
  • Add Transfer to Any App
  • Add Web Upload & Download
  • Build AI & Data Workflows
  • Browse All Transfers→

DEVELOPERS

  • Developer Center
  • Examples
  • API Quickstart
  • Developer Guide
  • API Reference
  • GitHub

RESOURCES

  • Resource Center
  • Product Guide
  • Integrations
  • Deploy & Manage
  • Help Center

CUSTOMERS

  • Government
  • Public Sector
  • Manufacturing
  • Engineering
  • Finance
  • Distribution
  • IT/Telecom
  • Media
  • Healthcare
  • Education

PLANS

  • Pricing

COMPANY

About Us

OTHER INNORIX PRODUCT

Al.bert — Smart Traffic AI

GLOBAL OFFICES

  • New York, USA
  • Seoul, South Korea
  • Ho Chi Minh City, Vietnam
  • View Office Locations→

(C)2026 INNORIX. All rights reserved.

  • Security
  • Status
  • Terms
  • Privacy
  • Cookies
  1. Developers
  2. Examples
  3. Web Upload & Download

Web Upload & Download

Build web upload and download file transfer flows with the INNORIX Transfer Builder and generate the configuration for your application.

Deploy & Manage
Exabyter
  • Send Files to One
  • Send Files to Many
  • Collect Files from Many
  • Sync Files Across Systems
  • Web Upload & Download

Web Upload & Download allows end users to upload and download files directly through a web browser. External users without an installed agent can also transfer large files, while the web control handles chunked transfers, resume, and progress display.

There are three components.

Component Role
exabyter.js · exabyter.css Transfer control embedded in the browser. Handles the file selection UI, chunked transfers, progress, and resume
upload.jsp / download.jsp Server receive/send endpoints. This is where the actual storage path is configured
WEB-INF/lib/InnorixJAVA.jar Transfer library used by the endpoints

⚠️ Review the security requirements before deployment. The server endpoints accept values sent by the browser as-is. In particular, downloads append fileName to the file path, so without path validation, files outside the storage root may be exposed. Read the Required security measures section below first.

ℹ️ Code tabs — Because this configuration consists of a browser script + server endpoint, the examples are provided in two tabs: JavaScript (client) and JSP (server). The bundle generated by the builder also consists of these two file types.

Getting started#

What the builder generates#

Choose options in the Web Upload & Download tab of the builder and download them to generate a zip with the following structure. Extract it directly under webapps/ in Tomcat (or another servlet container) and it will run immediately.

exabyter-web-upload/
  upload.html            <- page built from the options you picked + exabyter.create({...})
  upload.jsp             <- receiving endpoint (set the storage path here)
  exabyter.js
  exabyter.css
  img/  font/            <- assets referenced by the css
  WEB-INF/lib/InnorixJAVA.jar
  README.txt

If you choose download mode, the same structure includes download.html · download.jsp.

After deployment, the main setting you need to change is the endpoint's storage path.

  • upload.jsp — directory (upload storage root), maxPostSize (maximum request body size)
  • download.jsp — filePath (download source root)

Required security measures#

The bundle generated by the builder is a minimal configuration for verifying functionality. Before exposing it to external users, you must apply the following four measures.

Item If left unchanged Required action
Endpoint authentication upload.jsp · download.jsp have no authentication, so anyone who knows the URL can call them Check the session/token at the beginning of the page and return 401 on failure
Download path validation fileName becomes part of the file path directly, so ../ could read files outside the storage root Verify that the canonical path remains inside the storage root. More securely, perform file ID → actual path mapping on the server and do not accept the path itself
Upload storage path The default is a data folder next to the JSP, which may be exposed through the web Change it to an absolute path outside the web-accessible area
CORS Reflecting the request Origin directly allows arbitrary sites to call the endpoint Restrict it to an allowlist of domains

The implementation examples below already include authentication checks, path validation, and domain restrictions. Change only the values to match your service.

Implementation#

Upload#

The control is rendered inside a single empty <div>.

html
<link rel="stylesheet" type="text/css" href="./exabyter.css">
<script src="./exabyter.js"></script>

<div id="fileControl"></div>

<div class="toolbar">
  <button onclick="control.openFileDialog()">Attach File</button>
  <button onclick="control.directoryDialog()">Attach Folder</button>
  <button onclick="control.upload()">Start Upload</button>
</div>
// upload.html - same shape as the bootstrap code the builder generates.
var control = exabyter.create({
    setElementID: '#fileControl',
    setViewType: 'ListView',
    transferMode: 'upload',
    setUploadURL: './upload.jsp',          // receiving endpoint
    setTransferStart: { upload: 'manual' }, // 'auto' = start as soon as files are selected

    // How files are attached - the builder's Method
    enableDropZone: true,                   // Drag & drop
    addFolder: true,                        // allow folder attachment (Files and folders)

    // File policy - the builder's File & storage rules
    allowType: ['pdf', 'docx', 'xlsx', 'png'],  // omit to allow every type
    maxFileCount: 50,
    maxFileSize: 1073741824,                // 1 GB (bytes)
    maxTotalSize: 10737418240,              // 10 GB (bytes)
    resumeType: 'numbering',                // duplicate name: overwrite | numbering | relay (resume)

    // Appearance
    showTransferWindow: true,
    showTransferStatusIcon: true,
    enableComplexFolders: false,            // hide the folder tree on the left (flat list)
    hideClientPath: true,
    transferWindowTitle: 'Exabyter',
    controlLang: 'ko',                      // control UI language
});

// Control is ready
control.on('loadComplete', function () {
    setStatus('Ready.', 'ready');
});

// All files attached - call upload() here to start automatically
control.on('afterAddFiles', function () {
    // control.upload();
});

control.on('uploadStart', function () {
    // Upload started
});

control.on('uploadProgress', function () {
    // Progress changed
});

control.on('uploadComplete', function () {
    setStatus('Upload complete.', 'done');
    // Call your service API on completion (the builder's Call API)
    fetch('/your/api', { method: 'POST', credentials: 'include' });
});

control.on('uploadError', function () {
    setStatus('Upload error - see the console for details.');
});

control.on('uploadCancel', function () {
    // Canceled by the user
});
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.innorix.transfer.InnorixUpload" %>
<%
/* ============================================================================
 * Exabyter - upload receiving endpoint
 * The control slices the file and POSTs each slice; this page writes them into server storage.
 * ==========================================================================*/

// --- Authentication (required) ------------------------------------------
// This page has no authentication of its own. Check your session/token first.
// The preflight (OPTIONS) arrives without credentials, so only POST is checked.
String userId = (String) session.getAttribute("userId");
if (request.getMethod().equals("POST") && userId == null) {
    response.setStatus(401);
    return;
}

// The control sends a CORS preflight (OPTIONS) before POST. Handle uploads on POST only.
if (request.getMethod().equals("POST"))
{
    // --- Storage directory --------------------------------------------------
    // Default: the "data" folder next to this JSP. Use an absolute path in production.
    //   directory = "C:/exabyter/data";     // Windows
    //   directory = "/storage/exabyter";    // Unix
    String directory = InnorixUpload.getServletAbsolutePath(request);
    directory = directory.substring(0, directory.lastIndexOf("/") + 1) + "data";

    int maxPostSize = 2147482624;  // max request body size in bytes - match your proxy limits
    InnorixUpload uploader = new InnorixUpload(request, response, maxPostSize, directory);

    // --- Storage method (the builder's Storage method) ----------------------
    // One folder per transfer:
    //   uploader.setDirectory(directory + "/" + uploader.getParameter("_transferId"));
    // One folder per user:
    //   uploader.setDirectory(directory + "/" + userId);
    // One folder per user and per transfer:
    //   uploader.setDirectory(directory + "/" + userId + "/" + uploader.getParameter("_transferId"));

    /*
     * _action flags sent by the control:
     *   speedCheck          - transfer speed measurement
     *   getServerInfo       - server info probe
     *   getFileInfo         - file info probe
     *   attachFile          - slice upload in progress
     *   attachFileCompleted - file upload complete
     */
    String _action        = uploader.getParameter("_action");
    String _orig_filename = uploader.getParameter("_orig_filename");
    String _filesize      = uploader.getParameter("_filesize");
    String _transferId    = uploader.getParameter("_transferId");

    // Process the incoming slice.
    String _run_retval = uploader.run();

    uploader = null;
}

// --- CORS headers -----------------------------------------------------------
// List the allowed origins. Reflecting the request Origin lets any site call this endpoint.
java.util.List<String> allowedOrigins = java.util.Arrays.asList(
        "https://portal.example.com",     // <- replace with your service domain
        "http://localhost:8080");         // <- development only, remove in production
String origin = request.getHeader("Origin");
if (origin != null && allowedOrigins.contains(origin)) {
    response.setHeader("Access-Control-Allow-Origin", origin);
    response.setHeader("Access-Control-Allow-Credentials", "true");
}
response.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "Authorization,DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type, html5");
%>

Download#

The key to downloads is passing the list of files to offer to the control. Items passed to setDownloadList() become the on-screen list, and only the items selected by the user are downloaded.

ℹ️ The JSP below contains the core logic for authentication checks, path validation, and integrity responses. The complete file, including byte-range calculations and streaming, is included in the bundle's download.jsp (downloaded through Get API Code). This code block alone does not constitute a complete download server.

// download.html
var control = exabyter.create({
    setElementID: '#fileControl',
    setViewType: 'ListView',
    transferMode: 'download',
    setTransferStart: { download: 'manual' },   // 'auto' = start as soon as the list is set

    downloadDuplicate: 'numbering',  // duplicate name: overwrite | numbering | resume
    downloadIntegrity: true,         // verify file integrity
    setVerification: 'enable',

    showTransferWindow: true,
    showTransferStatusIcon: true,
    enableComplexFolders: false,
    hideClientPath: true,
    transferWindowTitle: 'Exabyter',
    controlLang: 'ko',
});

control.on('loadComplete', function () {
    setStatus('Ready.', 'ready');
});

control.on('downloadStart', function () { /* transfer started */ });
control.on('downloadProgress', function () { /* progress changed */ });
control.on('downloadComplete', function () {
    setStatus('Download complete.', 'done');
});
control.on('downloadError', function () {
    setStatus('Download error - see the console for details.');
});
control.on('downloadCancel', function () { /* canceled by the user */ });

// Files to offer - usually mapped straight from your service API response.
control.setDownloadList([
    {
        downloadURL: './download.jsp?fileName=report_2026Q3.pdf',
        displayFileName: 'report_2026Q3.pdf',   // name shown to the user
        fileSize: 15728640,                     // bytes
        isFolder: false,
    },
    {
        downloadURL: './download.jsp?fileName=training_video.mp4',
        displayFileName: 'training_video.mp4',
        fileSize: 5368709120,
        isFolder: false,
    },
]);

// Wire up your buttons
// control.downloadSelectedFiles();  // selected items only
// control.download();               // everything
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.innorix.transfer.InnorixTransfer" %>
<%@ page import="com.innorix.integrity.Integrity" %>
<%@ page import="java.io.*" %>
<%
/* ============================================================================
 * Exabyter - download sending endpoint
 * The control requests byte ranges in parallel; this page streams the requested range.
 * Integrity (MD5) checks and the start/complete signals arrive at the same endpoint.
 * ==========================================================================*/

out.clear();
out = pageContext.pushBody();

// --- Authentication (required) ----------------------------------------------
// This page has no authentication of its own. Check your session/token first.
if (session.getAttribute("userId") == null) {
    response.setStatus(401);
    return;
}

// --- Storage root -----------------------------------------------------------
// Use an absolute path in production.
//   filePath = "C:/exabyter/data/";   // Windows (use "/" as the separator)
//   filePath = "/storage/exabyter/";  // Unix
String saveDir = request.getSession().getServletContext()
        .getRealPath(request.getServletPath()).replace("\\", "/");
String filePath = saveDir.substring(0, saveDir.lastIndexOf("/") + 1) + "data/";

// Parameters the control adds automatically
String szStartOffset   = request.getParameter("_StartOffset");
String szEndOffset     = request.getParameter("_EndOffset");
String szPrintFileName = request.getParameter("fileName");
String szIntegrity     = request.getParameter("_Integrity");
String action          = request.getParameter("_Action");

// Start/complete signals - return with no body.
if (action != null && (action.equals("DownloadStart") || action.equals("DownloadComplete"))) {
    return;
}

// Decode the file name
String sysFileName = szPrintFileName;
if (szPrintFileName != null) {
    sysFileName = new String(szPrintFileName.getBytes("8859_1"), "UTF-8");
}

// --- Path validation (required) ---------------------------------------------
// fileName comes from the browser. Concatenating it directly lets "../" reach files
// outside the storage root, so always check that the canonical path stays inside it.
//
// Safer still: hand out a server-issued ID per file when you build the list
// (e.g. ./download.jsp?fileId=a1b2c3) and map ID -> real path here, never taking a path.
File root   = new File(filePath).getCanonicalFile();
File target = new File(root, sysFileName).getCanonicalFile();
if (!target.getPath().startsWith(root.getPath() + File.separator) || !target.isFile()) {
    response.setStatus(403);
    return;
}

// --- Integrity check: return the MD5 of the requested range -----------------
if (szIntegrity != null && szIntegrity.equalsIgnoreCase("true")) {
    long startOffset = szStartOffset != null ? Long.parseLong(szStartOffset) : 0;
    long endOffset   = szEndOffset   != null ? Long.parseLong(szEndOffset)   : 0;

    Integrity integrity = new Integrity();
    String _md5 = "";
    try {
        _md5 = integrity.getMD5FromFileSlice(target.getPath(), startOffset, endOffset);
    } catch (Exception e) { }

    response.setStatus(200);
    response.getWriter().write(_md5);
    response.getWriter().flush();
    response.getWriter().close();
    return;
}

// --- Stream the file (or range) ---------------------------------------------
// Only the validated target is used (never the raw user-supplied path).
response.setContentType("application/octet-stream");
response.setHeader("Accept-Ranges", "bytes");
// This is the core part. Range math for _StartOffset/_EndOffset and the actual streaming
// are implemented in the bundle's download.jsp - use that file as-is.
%>

Server settings you must update#

Location Value Description
upload.jsp directory Upload storage root. The default is the data folder next to the JSP, so change it to the actual storage path.
upload.jsp maxPostSize Maximum request body size (bytes). This must be aligned with limits on the front-end proxy (nginx, etc.).
upload.jsp CORS The default code reflects the request Origin directly. In production, restrict it to allowed domains.
download.jsp filePath Download source root.
Common Authentication · path validation · CORS Before deployment, confirm again that the four Required security measures above have been applied.

To separate folders by user/transfer, configure the upload endpoint as follows.

java
// Per transfer
uploader.setDirectory(directory + "/" + uploader.getParameter("_transferId"));

// Per user (take the user id from the session)
String userId = (String) session.getAttribute("userId");
uploader.setDirectory(directory + "/" + userId);

// Per user and per transfer
uploader.setDirectory(directory + "/" + userId + "/" + uploader.getParameter("_transferId"));

Reference#

Builder options ↔ generated code mapping#

Upload tab

Builder item Option Where it is applied
Method Drag & drop enableDropZone: true
File select button enableDropZone: false + attach button
Connect to existing UI Connect control.upload() directly to the existing UI without a button
Start method Click the upload button setTransferStart: { upload: 'manual' }
Auto-start after selecting files Call control.upload() in afterAddFiles
Selection target Single file maxFileCount: 1
Files and folders addFolder: true + attach folder button
File type Images / Documents / Videos / Custom allowType: [...]
Max file count 1 / 10 / 50 / 100 / Custom maxFileCount
Max file size 100MB / 1GB / 5GB / 10GB / Custom maxFileSize (bytes)
Max total size 1GB / 5GB / 10GB / 50GB / Custom maxTotalSize (bytes)
Duplicate file name Overwrite resumeType: 'overwrite'
Save with a new name / Don't overwrite resumeType: 'numbering'
Storage method Fixed folder Use directory in upload.jsp as-is
Per-user / Per-upload / Per-user & per-upload Branch with uploader.setDirectory(...) in upload.jsp
Completion message Enter message Status message in the uploadComplete handler
Call API URL + POST/GET fetch(url, { method }) in uploadComplete
Developer event hooks Selected items control.on('uploadStart' | 'uploadProgress' | 'uploadComplete' | 'uploadError' | 'uploadCancel', …)

Download tab

Builder item Where it is applied
Files to offer control.setDownloadList([{ downloadURL, displayFileName, fileSize, isFolder }])
Start method setTransferStart: { download: 'manual' | 'auto' }
Duplicate name downloadDuplicate: 'overwrite' | 'numbering' | 'resume'
Integrity downloadIntegrity: true + setVerification: 'enable'
Event hooks control.on('downloadStart' | 'downloadProgress' | 'downloadComplete' | 'downloadError' | 'downloadCancel', …)

ℹ️ Items such as screen configuration (displayed file-list fields), file naming rules, access permissions, and availability period are determined by your service when building the list. Pass only the final list to the control. Files are transferred one by one exactly as listed.

Common errors#

Symptom Cause and solution
Control does not appear The <div> referenced by setElementID does not exist when the script runs, or create() was called before exabyter.js loaded.
Upload fails immediately after starting The setUploadURL path is incorrect or upload.jsp has not been deployed. Check for a 404 in the browser Network tab first.
CORS error The control sends OPTIONS before POST. The Access-Control-Allow-* headers must also be included in the OPTIONS response.
413 on large files Increase both maxPostSize and the request body size limit on the front-end proxy.
Files are saved to the wrong location The default directory is the data folder next to the JSP. Change it to an absolute path.
Korean filenames are corrupted Check the JSP's pageEncoding="UTF-8" and the container's URI encoding setting (URIEncoding=UTF-8).
Extension restriction is not applied allowType is a lowercase array without dots (['pdf','png']). Because this is client-side validation, validate it again on the server.
Download list is empty Confirm that setDownloadList() is called after loadComplete and that each item has displayFileName.
PreviousSync Files Across Systems

On this page

  • Getting started
  • What the builder generates
  • Required security measures
  • Implementation
  • Upload
  • Download
  • Server settings you must update
  • Reference
  • Builder options ↔ generated code mapping
  • Common errors