JP / EN

広告

Flask send_from_directoryで静的ファイルを配信

タグ:linux web

Flaskでpythonコード内で生成する値ではなく、静的ファイルをそのままレスポンス 返したいときがある。 標準的なやり方は起動時に指定したstaticディレクトリ内に配信ファイルを置いておく 方法だが、これだと
  • リクエストのパラメータによって返すファイルを動的に切り替えることはできない
  • Urlパスがファイル名に固定されてしまう
等の問題がある。
この場合send_from_directoryメソッドを用いることで 静的ファイルへのルーティングを動的に行う、 つまりPythonの関数として実装されたエンドポイントから処理に応じて ファイルをそのまま返したり、切り替えたりするようなことができる。

使い方としてはsend_from_directory(directory, filename)のようにすると directoryの下にfilenameというファイルがあれば内容を読み込んでレスポンスとして返す、 ということができる。
    from flask import Flask, send_from_directory

    app = Flask(__name__)

    @app.route('/test')
    def test():
        return send_from_directory("files", "test.txt")

    if __name__ == "__main__":
        app.run(debug=True)
  


send_file vs send_from_directory

よく似た、そしてよりシンプルなflaskの関数としてsend_file もある。こちらはディレクトリとファイル名を別にせず、単にファイルパスを 渡すだけでよくやや短く書ける。しかし結論としては、セキュリティ上の利点が send_from_directoryにはあるのでこちらだけ使うのがおすすめである。

send_fileとsend_from_directoryはファイルパスに親ディレクトリ..が含まれていたときに 異なる動作をする。 send_fileは../../..のように親ディレクトリを反復的に使うことで 想定より上の方のパスまで公開してしまい、パスワードなどの重要な秘密ファイルを うっかりwebで配布してしまう恐れがあるのだ。 パスが定数であればうっかりそのようなパスにアクセスしないようにすればよいのだが、 ユーザ入力によるパラメータからファイル名を動的に生成する場合などではこれを 避けるための工夫がsend_fileを使うなら必要になってしまう。 このようなファイルパスの..を悪用した攻撃はディレクトリトラバーサル と呼ばれる。

send_from_directoryはファイル名として..を含めても、結果として 参照されたファイルがdirectory以下になければ返さないのでその点 やや安全である。 この例はsend_fileだとアプリ本体のpyファイルが読めてしまうケースである。
    from flask import Flask, send_from_directory

    app = Flask(__name__)

    @app.route('/test')  # これは危険なのでちゃんと失敗する
    def test():
        return send_from_directory("files", "../sample.py")

    @app.route('/test2')  # これは成功してしまう
    def test2():
        return send_file("files/../sample.py")


    if __name__ == "__main__":
        app.run(debug=True)
  

特別な理由がない限りはより安全なsend_from_directory を使うことに個人的にはしている。

動作確認したバージョン

  • Python 3.8.0
  • Flask==2.3.2


おすすめ記事

FlaskでWebサーバを立てるサンプル~最小限のコードで

Python + FlaskでWebSocket サンプルコード



このエントリーをはてなブックマークに追加

https://wonderhorn.net/