Implementing the Server

Unable to find a simple example of a Javascript client and Python server to implement a multipart upload, I decided to create my own.

This was due to the way that Squid web proxy expects multipart uploads to be implemented for larger files, which is useful for sending files to be AV scanned or CDR applied via ICAP.

This is a short example to show it working in 2022.

See the following code that uses the do_POST function with CGI to implement the processing of a multipart upload:

def do_POST(self):
    """Save a file following a HTTP POST request"""

    ctype, pdict = cgi.parse_header(self.headers['content-type'])
    if ctype == 'multipart/form-data':
        form = cgi.FieldStorage( fp=self.rfile, headers=self.headers, environ={'REQUEST_METHOD':'POST', 'CONTENT_TYPE':self.headers['Content-Type'], })
        filename = form['file'].filename
        data = form['file'].file.read()
        open("./uploads/%s"%filename, "wb").write(data)



    self.send_response(200, 'OK')
    self.send_header('Access-Control-Allow-Origin','*')
    self.end_headers()

The code uses the CGI library to correctly accept the file as a multipart upload. finally, it opens a filename with the name of the uploaded file into the uploads folder.

Disclaimer: It is not recommended to simply open a file with the parameter of the filename as this may be suseptible to path traversal attacks

Complete Example

Please see below for a complete example with both client and server. This code is not recommended to be run in the production and adjustments should be made accordingly to secure the code:

Server.py

import os
import uuid
from http.server import BaseHTTPRequestHandler, HTTPServer, SimpleHTTPRequestHandler
import glob
import json
import cgi
import pathlib
import urllib.parse
hostName = "0.0.0.0"
serverPort = 8080


class HTTPRequestHandler(SimpleHTTPRequestHandler):
    def do_OPTIONS(self):
        self.send_header('Access-Control-Allow-Origin', '*')
        self.send_response(200, "OK")
        self.end_headers()

    def do_POST(self):
        print(self.path)
        if self.path == "/upload":

            """Save a file following a HTTP POST multipart request"""

            ctype, pdict = cgi.parse_header(self.headers['content-type'])
            if ctype == 'multipart/form-data':
                form = cgi.FieldStorage( fp=self.rfile, headers=self.headers, environ={'REQUEST_METHOD':'POST', 'CONTENT_TYPE':self.headers['Content-Type'], })
                filename = form['file'].filename
                data = form['file'].file.read()
                open("./uploads/%s"%filename, "wb").write(data)



            self.send_response(200, 'OK')
            self.send_header('Access-Control-Allow-Origin','*')
            self.end_headers()

    def _set_200_ok_headers(self):
        self.send_response(200)
        self.send_header('Content-type', 'application/json')
        self.send_header('Access-Control-Allow-Origin','*')
        self.end_headers()

    
    def do_GET(self):
        if self.path == "/get_all_files":
            all_files = glob.glob("uploads/*")
            return_val = list()
            
            for one_file in all_files:
                single_file = dict()
                single_file_size = os.path.getsize(one_file)
                single_file_size_kb = single_file_size / 1024
                single_file['file_size_kb'] = single_file_size_kb
                single_file['file_path'] = one_file.replace("uploads/","")
                return_val.append(single_file)
            print(return_val)
            self._set_200_ok_headers()
            self.wfile.write(bytes(json.dumps(return_val), 'utf-8'))

        elif '/get_single_file' in self.path:
            print(self.path)
            filename = self.path.replace("/get_single_file/", "")
            filename = urllib.parse.unquote(filename)

            if os.path.exists('uploads/'+ filename):
                content_type = "application/octet-stream"
                self.send_response(200)
                self.send_header('Access-Control-Allow-Origin','*')
                self.send_header('Content-Type',content_type)
                self.end_headers()
                with open('uploads/'+ filename, 'rb') as file: 
                    self.wfile.write(file.read()) # Read the file and send the contents 
            else:
                self.send_response(404, 'Not Found')
                self.end_headers()
        elif '/delete' in self.path:
            all_files = glob.glob("uploads/*")
            for f in all_files:
                os.remove(f)
            self.send_response(200, 'OK')
            self.send_header('Access-Control-Allow-Origin','*')
            self.end_headers()



        else:
            self.send_response(404, 'Not Found')
            self.end_headers()


if __name__ == '__main__':
    webServer = HTTPServer((hostName, serverPort), HTTPRequestHandler)
    print("Server started http://%s:%s" % (hostName, serverPort))

    try:
        webServer.serve_forever()
    except KeyboardInterrupt:
        pass

    webServer.server_close()
    print("Server stopped.")



index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Upload a file here!</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css">
  </head>
  <body>
  <section class="section">
    <div class="container">
      <h1 class="title has-text-link">
        Upload a file here
      </h1>
      <h2 class="subtitle has-text-link">
        Upload a file here to the core system to prove your identity:
        <!-- HTML5 Input Form Elements -->
        </br>
        </br>
        <input id="fileupload" type="file" name="fileupload" /> 
        <button id="upload-button" onclick="uploadFile()"> Upload </button>

      </h2>
    </div>
  </section>
  </body>
  <script>

    const destination = 'http://localhost:8080/upload'

    async function uploadFile() {
      let formData = new FormData(); 
      formData.append("file", fileupload.files[0], fileupload.files[0].name);
      const result = await fetch(destination, {
        method: "POST", 
        body: formData
      }); 
      console.log(result.status);
      if(result.status == 200) {
        alert('The file has been uploaded successfully.');
        location.reload();
      }
      else {
        alert("fatal error... http code: " + string(result.status))
      }
    }
    </script>
</html>

get_all_files.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Upload a file here!</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css">
  </head>
  <body onload="OnBodyLoad();">
  <section class="section">
    <div class="container">
      <h1 class="title has-text-link">
        View uploaded files:
      </h1>
      <h2 class="subtitle has-text-link">
        <h1></br> All Files: </br> ------------------------------------------------------------------ </br></h1>
        <ul id="mainlist">
    
        </ul>
        <button onclick="DeleteAllFiles();">Delete All Files</button>
      </h2>
    </div>
  </section>
  </body>  <!-- Ajax JavaScript File Upload Logic -->
  <script>

    const destination = "http://localhost:8080"

    var OnBodyLoad = function() {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', destination + '/get_all_files', true);
        xhr.responseType = 'json';
        xhr.onload = function() {
        var status = xhr.status;
        if (status === 200) {
            var all_items = xhr.response;
            var element = ['apple', 'orange', 'banana'];
            for (var i = 0; i < all_items.length; i++){ 
                // Create DOM element
                let childNode = document.createElement('li');
                // Set content to current element
                childNode.innerHTML = "<a href=\"" + destination + "/get_single_file/" + all_items[i]['file_path'] + "\" download target=\"_blank\">" + all_items[i]['file_path'] + "</a>" + "<p>File Size (Kilobytes): " + all_items[i]['file_size_kb'] + "</p> </br> ------------------------------------------------------------------ </br>";

                // Add DOM Node to list
                document.getElementById('mainlist').appendChild(childNode);
            }
        } else {
            console.log(xhr.response);
            alert(xhr.response);
        }
        };
        xhr.send();
    };

    var DeleteAllFiles = function() {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', destination + "/delete", true);
        xhr.responseType = 'json';
        xhr.onload = function() {
        var status = xhr.status;
        if (status === 200) {
            alert("Successfully deleted")
            location.reload();
        } else {
            alert(xhr.response);
        }
        };
        xhr.send();
    }

  </script>
</html>

Please note: example tested as working as of Flask-2.1.2. To use the application, start the server.py using:

python3 server.py

Then just click on the index.html, upload a file using the GUI and then click on get_all_files.html to view the uploaded content.

For more tutorials please check out the tag links below: