kinokijuf
kinokijuf

Reputation: 997

How to allow only opening files in current directory in Python3?

I am writing a simple file server in Python. The filename is provided by the client and should be considered untrusted. How to verify that it corresponds to a file inside the current directory (within it or any of its subdirectories)? Will something like:

pwd=os.getcwd()
if os.path.commonpath((pwd,os.path.abspath(filename))) == pwd:
    open(filename,'rb')

suffice?

Upvotes: 1

Views: 555

Answers (1)

PM 2Ring
PM 2Ring

Reputation: 55499

Convert the filename to a canonical path using os.path.realpath, get the directory portion, and see if the current directory (in canonical form) is a prefix of that:

import os, os.path

def in_cwd(fname):
    path = os.path.dirname(os.path.realpath(fname))
    return path.startswith(os.getcwd())

By converting fname to a canonical path we handle symbolic links and paths containing ../.


Update

Unfortunately, the above code has a little problem. For example,

'/a/b/cd'.startswith('/a/b/c')

returns True, but we definitely don't want that behaviour here! Fortunately, there's an easy fix: we just need to append os.sep to the paths before performing the prefix test. The new version also handles any OS pathname case-insensitivity issues via os.path.normcase.

import os, os.path

def clean_dirname(dname):
    dname = os.path.normcase(dname)
    return os.path.join(dname, '')

def in_cwd(fname):
    cwd = clean_dirname(os.getcwd())
    path = os.path.dirname(os.path.realpath(fname))
    path = clean_dirname(path)
    return path.startswith(cwd)

Thanks to DSM for pointing out the flaw in the previous code.


Here's a version that's a little more efficient. It uses os.path.commonpath, which is more robust than appending os.sep and doing a string prefix test.

def in_cwd(fname):
    cwd = os.path.normcase(os.getcwd())
    path = os.path.normcase(os.path.dirname(os.path.realpath(fname)))
    return os.path.commonpath((path, cwd)) == cwd

Upvotes: 3

Related Questions