4

I've got a question that I could really use some guidance with. I am simply trying to serve HTML from a directory that is not the same directory as the server itself. Ideally, I'd like to move one level above the server's directory, and serve a file located there.

When I try that, I get a 404.

class Server(SimpleHTTPRequestHandler):

    def do_GET(self):
        ... some other stuff ...        
        elif self.path == "/manage":
            self.send_response(200)
            self.path = "/manage.html"
            return SimpleHTTPRequestHandler.do_GET(self)
        else:            
            f = self.send_head()
            if f:
                try:
                    self.copyfile(f, self.wfile)
                finally:
                    f.close()

^this code serves the file, if it's in the same directory as the server.

If I then move manage.html upward (where I'd like it to live), and try stuff like '../manage.html', or an absolute path, I get a 404.

Am I bumping into like, some built-in directory traversal mitigation? If so, is there a way I can disable it? All of this is local, security isn't really a problem. I could try a subdirectory, but if I start down that road, I'd have to rename & rearrange the entire directory structure, because the naming won't make sense.

Thanks in advance! (Python 3.10.2) 64-bit, Windows.

1 Answers1

3

This is a security feature as you mentioned. You wouldn't want users to be able to see all files of the server, would you?

Starting with Python 3.7, the constructor of SimpleHTTPRequestHandler has a directory parameter (see docs: https://docs.python.org/3/library/http.server.html#http.server.SimpleHTTPRequestHandler).

With that you can tell SimpleHTTPRequestHandler from where to server the files. You can either change that wherever you instantiate your server, i.e. Server(directory=...) or you can alter the init method of you Server class

class Server(SimpleHTTPRequestHandler):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, directory=..., **kwargs)

EDIT: I dug a little deeper, and here is where the sanitization happens https://github.com/python/cpython/blob/9a95fa9267590c6cc66f215cd9808905fda1ee25/Lib/http/server.py#L839-L847

# ...
path = posixpath.normpath(path)
words = path.split('/')
words = filter(None, words)
path = self.directory
for word in words:
    if os.path.dirname(word) or word in (os.curdir, os.pardir):
        # Ignore components that are not a simple file/directory name
        continue
    path = os.path.join(path, word)
# ...
Robsdedude
  • 1,292
  • 16
  • 25
  • I would upvote this, but I apparently cannot until I have more rep. – VerteronParticle Sep 19 '22 at 15:22
  • Tested and confirmed working, thank you so much! I wound up overriding the init function of the request handler, hardcoding the route for testing, and saving modified copy in the repo and importing as a module. – VerteronParticle Sep 20 '22 at 02:18