Skip to main content

httpserver.py (Source)

#!/usr/bin/env python3
import html
from http import HTTPStatus
from http.server import HTTPServer, SimpleHTTPRequestHandler, test
import io
from pathlib import Path
import sys
import urllib.parse
class CORSRequestHandler (SimpleHTTPRequestHandler):
    def end_headers(self):
        self.send_header('Access-Control-Allow-Origin', '*')
        SimpleHTTPRequestHandler.end_headers(self)
    def list_directory(self, path):
        """Helper to produce a directory listing (absent index.html).
        Return value is either a file object, or None (indicating an
        error).  In either case, the headers are sent, making the
        interface the same as for send_head().
        """
        path = Path(path)
        try:
            entries = list(path.iterdir())
        except OSError:
            self.send_error(
                HTTPStatus.NOT_FOUND,
                "No permission to list directory")
            return None
        entries.sort(key=lambda a: (not a.is_dir(), a.name.lower()))
        r = []
        try:
            displaypath = urllib.parse.unquote(self.path,
                                               errors='surrogatepass')
        except UnicodeDecodeError:
            displaypath = urllib.parse.unquote(self.path)
        escape = lambda x: html.escape(x, quote=False)
        root = Path(self.directory)
        displaypath = '/'.join(reversed([escape(path.name)]+[f'''<a href="{'/'.join(['..']*(i+1))}">{escape(d.name)}</a>''' if d.is_relative_to(root) else escape(d.name) for i,d in enumerate(path.parents)]))
        enc = sys.getfilesystemencoding()
        title = f''
        out = f'''<!DOCTYPE HTML>
<html lang="en">
    <head>
        <meta charset="{enc}">
        <title>Directory listing for {displaypath}</title>
        <style>
''' + '''
body {
    font-family: sans-serif;
    font-size: 16px;
}
ul {
    display: grid;
    grid-auto-flow: column;
    grid-template-columns: repeat(3,1fr);
    list-style: none;
    gap: 0 1em;
}
li {
    font-family: monospace;
    line-height: 1.5;
    grid-column: 3;
    &.dir {
        font-weight: bold;
        grid-column: 2;
        &.parent {
            margin-bottom: 1em;
            grid-column: 1;
        }
    }
    & + li.file {
        margin-top: 1em;
    }
}
''' + f'''
        </style>
    </head>
    <body>
        <h1>Static server at <code>{displaypath}</code></h1>
        <ul>
'''
        if path.parent.is_relative_to(self.directory):
            out += f'''<li class="dir parent"><a href="..">← {path.parent.name}</a></li>'''
        for f in entries:
            name = f.name
            displayname = linkname = f.name
            # Append / for directories or @ for symbolic links
            if f.is_dir():
                displayname = name + "/"
                linkname = name + "/"
            if f.is_symlink():
                displayname = name + "@"
                # Note: a link to a directory displays with @ and links with /
            url = urllib.parse.quote(linkname, errors='surrogatepass')
            text = html.escape(displayname, quote=False)
            css_class = 'dir' if f.is_dir() else 'file'
            out += f'<li class="{css_class}"><a href="{url}">{text}</a></li>'
        out += f'''
</ul>
</body>
</html>
'''
        encoded = out.encode(enc, 'surrogateescape')
        f = io.BytesIO()
        f.write(encoded)
        f.seek(0)
        self.send_response(HTTPStatus.OK)
        self.send_header("Content-type", "text/html; charset=%s" % enc)
        self.send_header("Content-Length", str(len(encoded)))
        self.end_headers()
        return f
if __name__ == '__main__':
    port = int(sys.argv[1]) if len(sys.argv) > 1 else 8000
    print(f"Using port {port}")
    test(CORSRequestHandler, HTTPServer, port=port)