11

大きなファイル (>100M) をサーバーにアップロードする場合、PHP は常に最初にブラウザーからの POST データ全体を受け入れます。アップロードのプロセスに挿入することはできません。

たとえば、tokenサーバーに送信されるデータ全体がPHP コードでIMPOSSIBLEになる前に、" " の値を確認します。

<form enctype="multipart/form-data" action="upload.php?token=XXXXXX" method="POST">
    <input type="hidden" name="MAX_FILE_SIZE" value="3000000" />
    Send this file: <input name="userfile" type="file" />
    <input type="submit" value="Send File" />
</form>

だから私はmod_rewriteこのように使用しようとしました:

RewriteEngine On
RewriteMap mymap prg:/tmp/map.php
RewriteCond %{QUERY_STRING} ^token=(.*)$ [NC]
RewriteRule ^/upload/fake.php$ ${mymap:%1} [L]

map.php

#!/usr/bin/php
<?php
define("REAL_TARGET", "/upload/real.php\n");
define("FORBIDDEN", "/upload/forbidden.html\n");

$handle = fopen ("php://stdin","r");
while($token = trim(fgets($handle))) {
file_put_contents("/tmp/map.log", $token."\n", FILE_APPEND);
    if (check_token($token)) {
        echo REAL_TARGET;
    } else {
        echo FORBIDDEN;
    }
}

function check_token ($token) {//do your own security check
    return substr($token,0,4) === 'alix';
}

しかし・・・また失敗mod_rewriteこの状況では遅すぎるように見えます。データは引き続き完全に転送されます。

次に、次のように試しNode.jsました(コードスニップ):

var stream = new multipart.Stream(req);
stream.addListener('part', function(part) {
    sys.print(req.uri.params.token+"\n");
    if (req.uri.params.token != "xxxx") {//check token
      res.sendHeader(200, {'Content-Type': 'text/plain'});
      res.sendBody('Incorrect token!');
      res.finish();
      sys.puts("\n=> Block");
      return false;
    }

結果は...また失敗。

したがって、この問題を解決するための正しいパスを見つけるのを手伝ってください。または、方法がないことを教えてください。

関連する質問:

PHP (Apache または Nginx を使用) は、POST 要求が完了する前に HTTP ヘッダーをチェックできますか?

ファイルのアップロード後ではなく、アップロード プロセスを開始する前に、このスクリプトでパスワードをチェックする方法を教えてください。

4

7 に答える 7

23

まず、この 用に作成した GitHub リポジトリを使用して、このコードを自分で試すことができます。リポジトリをクローンして実行するだけnode headerです。

(スポイラー、これを読んでいて、何かを機能させるために時間のプレッシャーにさらされていて、学ぶ気分ではない場合( :( )、最後にもっと簡単な解決策があります)

一般的な考え方

これは素晴らしい質問です。あなたが求めていることは非常に可能であり、クライアント側は必要ありません。node.jsがどのように機能するかを示しながら、HTTPプロトコルがどのように機能するかをより深く理解するだけです:)

これは、基盤となるTCP プロトコルを 1 レベル深く掘り下げて、この特定のケースの HTTP リクエストを自分で処理することで、簡単に行うことができます。Node.js では、組み込みのnet モジュールを使用してこれを簡単に行うことができます。

HTTP プロトコル

まず、HTTP リクエストがどのように機能するかを見てみましょう。

HTTP 要求は、CRLF ( \r\n) で区切られた key:value ペアの一般的な形式のヘッダー セクションで構成されます。二重の CRLF (つまり\r\n\r\n) に到達すると、ヘッダー セクションが終了したことがわかります。

典型的な HTTP GET リクエストは次のようになります。

GET /resource HTTP/1.1  
Cache-Control: no-cache  
User-Agent: Mozilla/5.0 

Hello=World&stuff=other

「空行」の前の上部はヘッダー セクションで、下部はリクエストの本文です。リクエストは でエンコードされているため、本文セクションでは少し異なって見えますmultipart/form-dataが、ヘッダーは同じままです。これがどのように適用されるかを見てみましょう。

Node.js の TCP

TCP で生のリクエストをリッスンし、取得したパケットを読み取ることができます。これは、先ほど説明した二重の crlf を読み取るまで続きます。次に、必要な検証のために既に持っている短いヘッダー セクションをチェックします。それを行った後、検証が通らなかった場合 (たとえば、単純に TCP 接続を終了することによって) 要求を終了するか、通過させることができます。これにより、リクエスト本文を受信したり読み取ったりするのではなく、はるかに小さいヘッダーのみを受信または読み取ることができます。

これを既存のアプリケーションに組み込む簡単な方法の 1 つは、特定のユース ケースでアプリケーションから実際の HTTP サーバーにリクエストをプロキシすることです。

実装の詳細

このソリューションは、必要最小限のものです。それは単なる提案です。

ワークフローは次のとおりです。

  1. netnode.js で tcp サーバーを作成できるようにする node.jsのモジュールが必要です。

  2. netデータをリッスンするモジュールを 使用して TCP サーバーを作成しますvar tcpServer = net.createServer(function (socket) {...。正しいポートをリッスンするように指示することを忘れないでください

  • socket.on("data",function(data){そのコールバック内で、パケットが到着するたびにトリガーされるdata events をリッスンします。
  • 「data」イベントから渡されたバッファのデータを読み取り、それを変数に格納します
  • 二重の CRLF をチェックします。これにより、リクエストの HEADER セクションがHTTP プロトコルに従って終了したことが保証されます
  • 検証がヘッダー (あなたの言葉ではトークン) であると仮定すると、ヘッダーのみを解析した後にチェックします (つまり、二重の CRLF を取得しました)。これは、コンテンツ長ヘッダーをチェックするときにも機能します。
  • ヘッダーがチェックアウトされていないことに気付いた場合はsocket.end()、接続を閉じる呼び出しを行います。

ここに私たちが使用するいくつかのものがあります

ヘッダーを読み取る方法:

function readHeaders(headers) {
    var parsedHeaders = {};
    var previous = "";    
    headers.forEach(function (val) {
        // check if the next line is actually continuing a header from previous line
        if (isContinuation(val)) {
            if (previous !== "") {
                parsedHeaders[previous] += decodeURIComponent(val.trimLeft());
                return;
            } else {
                throw new Exception("continuation, but no previous header");
            }
        }

        // parse a header that looks like : "name: SP value".
        var index = val.indexOf(":");

        if (index === -1) {
            throw new Exception("bad header structure: ");
        }

        var head = val.substr(0, index).toLowerCase();
        var value = val.substr(index + 1).trimLeft();

        previous = head;
        if (value !== "") {
            parsedHeaders[head] = decodeURIComponent(value);
        } else {
            parsedHeaders[head] = null;
        }
    });
    return parsedHeaders;
};

データ イベントで取得したバッファ内の二重 CRLF をチェックし、オブジェクト内に存在する場合はその場所を返すメソッド:

function checkForCRLF(data) {
    if (!Buffer.isBuffer(data)) {
        data = new Buffer(data,"utf-8");
    }
    for (var i = 0; i < data.length - 1; i++) {
        if (data[i] === 13) { //\r
            if (data[i + 1] === 10) { //\n
                if (i + 3 < data.length && data[i + 2] === 13 && data[i + 3] === 10) {
                    return { loc: i, after: i + 4 };
                }
            }
        } else if (data[i] === 10) { //\n

            if (data[i + 1] === 10) { //\n
                return { loc: i, after: i + 2 };
            }
        }
    }    
    return { loc: -1, after: -1337 };
};

そして、この小さなユーティリティメソッド:

function isContinuation(str) {
    return str.charAt(0) === " " || str.charAt(0) === "\t";
}

実装

var net = require("net"); // To use the node net module for TCP server. Node has equivalent modules for secure communication if you'd like to use HTTPS

//Create the server
var server = net.createServer(function(socket){ // Create a TCP server
    var req = []; //buffers so far, to save the data in case the headers don't arrive in a single packet
    socket.on("data",function(data){
        req.push(data); // add the new buffer
        var check = checkForCRLF(data);
        if(check.loc !== -1){ // This means we got to the end of the headers!
            var dataUpToHeaders= req.map(function(x){
                return x.toString();//get buffer strings
            }).join("");
            //get data up to /r/n
            dataUpToHeaders = dataUpToHeaders.substring(0,check.after);
            //split by line
            var headerList = dataUpToHeaders.trim().split("\r\n");
            headerList.shift() ;// remove the request line itself, eg GET / HTTP1.1
            console.log("Got headers!");
            //Read the headers
            var headerObject = readHeaders(headerList);
            //Get the header with your token
            console.log(headerObject["your-header-name"]);

            // Now perform all checks you need for it
            /*
            if(!yourHeaderValueValid){
                socket.end();
            }else{
                         //continue reading request body, and pass control to whatever logic you want!
            }
            */


        }
    });
}).listen(8080); // listen to port 8080 for the sake of the example

ご不明な点がございましたら、お気軽にお問い合わせください:)

わかりました、私は嘘をつきました、もっと簡単な方法があります!

しかし、それの何が楽しいのですか?最初にここをスキップした場合、HTTP の仕組みを理解できません :)

Node.js にはhttpモジュールが組み込まれています。node.js では、リクエストは本質的にチャンク化されているため、特に長いリクエストは、プロトコルの高度な理解がなくても同じことを実装できます。

今回はhttpモジュールを使ってhttpサーバーを作ってみましょう

server = http.createServer( function(req, res) { //create an HTTP server
    // The parameters are request/response objects
    // check if method is post, and the headers contain your value.
    // The connection was established but the body wasn't sent yet,
    // More information on how this works is in the above solution
    var specialRequest = (req.method == "POST") && req.headers["YourHeader"] === "YourTokenValue";
    if(specialRequest ){ // detect requests for special treatment
      // same as TCP direct solution add chunks
      req.on('data',function(chunkOfBody){
              //handle a chunk of the message body
      });
    }else{
        res.end(); // abort the underlying TCP connection, since the request and response use the same TCP connection this will work
        //req.destroy() // destroy the request in a non-clean matter, probably not what you want.
    }
}).listen(8080);

requestこれは、nodejsモジュールのハンドルが、デフォルトでヘッダーが送信された後に実際にフックされるという事実に基づいていhttpます (他には何も実行されませんでした)。(これはサーバーモジュールで、これはパーサーモジュールで)

ユーザーigorw100 Continueは、対象のブラウザーがヘッダーをサポートしていると仮定して、ヘッダーを使用したややクリーンなソリューションを提案しました。100 Continue は、あなたがしようとしていることを正確に実行するように設計されたステータス コードです。

100 (Continue) ステータス (セクション 10.1.1 を参照) の目的は、リクエスト本文を含むリクエスト メッセージを送信するクライアントが、オリジン サーバーがリクエストを受け入れるかどうかを (リクエスト ヘッダーに基づいて) 判断できるようにすることです。クライアントがリクエストボディを送信する前。場合によっては、サーバーが本文を確認せずにメッセージを拒否する場合、クライアントが本文を送信することは不適切または非常に非効率的である可能性があります。

ここにあります :

var http = require('http');
 
function handle(req, rep) {
    req.pipe(process.stdout); // pipe the request to the output stream for further handling
    req.on('end', function () {
        rep.end();
        console.log('');
    });
}
 
var server = new http.Server();
 
server.on('checkContinue', function (req, rep) {
    if (!req.headers['x-foo']) {
        console.log('did not have foo');
        rep.writeHead(400);
        rep.end();
        return;
    }
 
    rep.writeContinue();
    handle(req, rep);
});
 
server.listen(8080);

サンプルの入力/出力はこちらで確認できます。Expect:これには、適切なヘッダーでリクエストを発行する必要があります。

于 2013-05-03T00:38:38.300 に答える
0

アップロードをストリーミングしようとしていて、処理する前に検証する必要があるようです: これは役に立ちますか? http://debuggable.com/posts/streaming-file-uploads-with-node-js:4ac094b2-b6c8-4a7f-bd07-28accbdd56cb

http://www.componentix.com/blog/13/file-uploads-using-nodejs-once-again

于 2013-04-23T11:55:48.590 に答える
0

APC ファイルのアップロードの進行状況を使用して、APC ファイルのアップロードのキーとして進行状況キーを設定しないでください。その場合、フォームが送信され、アップロードの進行状況が最初に開始されますが、最初の進行状況チェックでキーを確認します。それが正しくない場合は、すべてを中断します。

http://www.johnboy.com/blog/a-useful-php-file-upload-progress-meter http://www.ultramegatech.com/2008/12/creating-upload-progress-bar-php/

これは、よりネイティブなアプローチです。ほぼ同じです。非表示の入力のキーをトークンに変更して検証し、エラーが発生した場合は接続を中断するだけです。多分それはさらに良いです。 http://php.net/manual/en/session.upload-progress.php

于 2013-05-02T02:23:18.723 に答える
0

ファイルをアップロードするには、クライアント側のプラグインを使用することをお勧めします。あなたが使用することができます

http://www.plupload.com/

また

https://github.com/blueimp/jQuery-File-Upload/

どちらのプラグインにも、アップロード前にファイル サイズをチェックする機能があります。

独自のスクリプトを使用する場合は、これをチェックしてください。これはあなたを助けるかもしれません

        function readfile()
        {
            var files = document.getElementById("fileForUpload").files;
            var output = [];
            for (var i = 0, f; f = files[i]; i++) 
            {
                    if(f.size < 100000) // Check file size of file
                    {
                        // Your code for upload
                    }
                    else
                    {
                        alert('File size exceeds upload size limit');
                    }

            }
        }
于 2013-04-26T09:30:45.287 に答える