問題描述
當(dāng)上傳大文件(>100M)到服務(wù)器時(shí),PHP 總是首先接受來自瀏覽器的整個(gè)數(shù)據(jù) POST.我們無法注入上傳過程.
When uploading big file (>100M) to server, PHP always accept entire data POST from browser first. We cannot inject into the process of uploading.
例如,在我的 PHP 代碼中,在將整個(gè)數(shù)據(jù)發(fā)送到服務(wù)器之前檢查token
"的值是IMPOSSIBLE:
For example, check the value of "token
" before entire data send to server is IMPOSSIBLE in my PHP code:
<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
:
So I've try to use mod_rewrite
like this:
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
");
define("FORBIDDEN", "/upload/forbidden.html
");
$handle = fopen ("php://stdin","r");
while($token = trim(fgets($handle))) {
file_put_contents("/tmp/map.log", $token."
", 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
在這種情況下看起來工作太晚了.數(shù)據(jù)仍然完全傳輸.
But ... It fails again. mod_rewrite
looks working too late in this situation. Data still transfer entirely.
然后我嘗試了 Node.js
,就像這樣(代碼片段):
Then I tried Node.js
, like this (code snip):
var stream = new multipart.Stream(req);
stream.addListener('part', function(part) {
sys.print(req.uri.params.token+"
");
if (req.uri.params.token != "xxxx") {//check token
res.sendHeader(200, {'Content-Type': 'text/plain'});
res.sendBody('Incorrect token!');
res.finish();
sys.puts("
=> Block");
return false;
}
結(jié)果是...失敗再次.
所以請(qǐng)幫我找到解決這個(gè)問題的正確路徑或者告訴我沒有辦法.
So please help me to find the correct path to resolve this issue or tell me there is no way.
相關(guān)問題:
PHP (使用 Apache 或 Nginx)在 POST 請(qǐng)求完成之前檢查 HTTP 標(biāo)頭?
有人能告訴我如何讓這個(gè)腳本在開始上傳過程之前而不是在上傳文件之后檢查密碼嗎?
推薦答案
首先,你可以試試這段代碼自己使用我為此創(chuàng)建的 GitHub 存儲(chǔ)庫.只需克隆存儲(chǔ)庫并運(yùn)行 node header
.
First of all, you can try this code yourself using the GitHub repo I created for this. Just clone the repository and run node header
.
(劇透,如果您正在閱讀本文,并且在時(shí)間壓力下需要做某事而沒有心情學(xué)習(xí)(:(),最后有一個(gè)更簡(jiǎn)單的解決方案)
(Spoiler, if you're reading this and are under time pressure to get something to work and not in the mood to learn ( :( ), there is a simpler solution at the end)
這是一個(gè)很好的問題.您所要求的是很有可能并且不需要客戶端,只需更深入地了解 HTTP 協(xié)議的工作原理,同時(shí)展示 node.js 的運(yùn)行方式:)
This is a great question. What you are asking for is very possible and no clientside is needed, just a deeper understanding of how the HTTP protocol works while showing how node.js rocks :)
如果我們深入了解底層 TCP 協(xié)議 和針對(duì)這種特定情況,我們自己處理 HTTP 請(qǐng)求.Node.js 讓您可以使用內(nèi)置的 net 模塊輕松完成此操作.
This can be made easy if we go one level deeper to the underlying TCP protocol and process the HTTP requests ourselves for this specific case. Node.js lets you do this easily using the built in net module.
首先,讓我們看看 HTTP 請(qǐng)求是如何工作的.
First, let's look at how HTTP requests work.
一個(gè) HTTP 請(qǐng)求包含在由 CRLF (
) 分隔的鍵值對(duì)的一般格式.我們知道,當(dāng)我們到達(dá)一個(gè)雙 CRLF(即
)時(shí),header 部分就結(jié)束了.
An HTTP request consists of a headers section in the general format of key:value pairs seperated by CRLF (
). We know that the header section ended when we reach a double CRLF (that is
).
典型的 HTTP GET 請(qǐng)求可能如下所示:
A typical HTTP GET request might look something like this:
GET /resource HTTP/1.1
Cache-Control: no-cache
User-Agent: Mozilla/5.0
Hello=World&stuff=other
空行"之前的頂部是標(biāo)題部分,底部是請(qǐng)求的正文.您的請(qǐng)求在 body 部分看起來會(huì)有所不同,因?yàn)樗怯?multipart/form-data
編碼的,但標(biāo)頭將保持相似讓我們探索這如何適用于我們.
The top part before the 'empty line' is the headers section and the bottom part is the body of the request. Your request will look a bit differently in the body section since it is encoded with multipart/form-data
but the header will remain similarLet's explore how this applies to us.
我們可以在 TCP 中監(jiān)聽原始請(qǐng)求并讀取我們得到的數(shù)據(jù)包,直到我們讀取我們談到的雙重 crlf.然后我們將檢查我們已經(jīng)擁有的短標(biāo)題部分,以進(jìn)行我們需要的任何驗(yàn)證.在我們這樣做之后,如果驗(yàn)證沒有通過(例如通過簡(jiǎn)單地結(jié)束 TCP 連接),我們可以結(jié)束請(qǐng)求,或者通過它.這允許我們不接收或讀取請(qǐng)求正文,而只接收更小的標(biāo)頭.
We can listen to the raw request in TCP and read the packets we get until we read that double crlf we talked about. Then we will check the short header section which we already have for whatever validation we need. After we do that, we can either end the request if validation did not pass (For example by simply ending the TCP connection), or pass it through. This allows us to not receive or read the request body, but just the headers which are much smaller.
將其嵌入到現(xiàn)有應(yīng)用程序中的一種簡(jiǎn)單方法是將來自它的請(qǐng)求代理到特定用例的實(shí)際 HTTP 服務(wù)器.
One easy way to embed this into an already existing application is to proxy requests from it to the actual HTTP server for the specific use case.
這個(gè)解決方案是最簡(jiǎn)單的.這只是一個(gè)建議.
This solution is as bare bones as it gets. It is just a suggestion.
這是工作流程:
我們需要 node.js 中的
net
模塊,它允許我們?cè)?node.js 中創(chuàng)建 tcp 服務(wù)器
We require the
net
module in node.js which allows us to create tcp servers in node.js
使用 net
模塊創(chuàng)建一個(gè) TCP 服務(wù)器,它將監(jiān)聽數(shù)據(jù):var tcpServer = net.createServer(function (socket) {...
.別忘了告訴它監(jiān)聽正確的端口
Create a TCP server using the net
module which will listen to data: var tcpServer = net.createServer(function (socket) {...
. Don't forget to tell it to listen to the correct port
- 在該回調(diào)中,偵聽數(shù)據(jù)事件
socket.on("data",function(data){
,每當(dāng)數(shù)據(jù)包到達(dá)時(shí)就會(huì)觸發(fā). - 從 'data' 事件中讀取傳遞緩沖區(qū)的數(shù)據(jù),并將其存儲(chǔ)在一個(gè)變量中
- 檢查雙 CRLF,這確保請(qǐng)求 HEADER 部分已經(jīng)結(jié)束 根據(jù)HTTP 協(xié)議
- 假設(shè)驗(yàn)證是一個(gè)標(biāo)頭(用你的話來說是令牌)在解析只是標(biāo)頭后檢查它,(也就是說,我們得到了雙 CRLF).這在檢查內(nèi)容長(zhǎng)度標(biāo)頭時(shí)也有效.
- 如果您發(fā)現(xiàn)標(biāo)頭未檢出,請(qǐng)調(diào)用
socket.end()
以關(guān)閉連接. - Inside that callback, listen to data events
socket.on("data",function(data){
, which will trigger whenever a packet arrives. - read the data of the passed buffer from the 'data' event, and store that in a variable
- check for double CRLF, this ensures that the request HEADER section has ended according to the HTTP protocol
- Assuming that the validation is a header (token in your words) check it after parsing just the headers , (that is, we got the double CRLF). This also works when checking for the content-length header.
- If you notice that the headers don't check out, call
socket.end()
which will close the connection.
讀取標(biāo)題的方法:
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;
};
一種檢查數(shù)據(jù)事件緩沖區(qū)中雙 CRLF 的方法,如果它存在于對(duì)象中,則返回其位置:
A method for checking double CRLF in a buffer you get on a data event, and return its location if it exists in an object:
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) { //
if (data[i + 1] === 10) { //
if (i + 3 < data.length && data[i + 2] === 13 && data[i + 3] === 10) {
return { loc: i, after: i + 4 };
}
}
} else if (data[i] === 10) { //
if (data[i + 1] === 10) { //
return { loc: i, after: i + 2 };
}
}
}
return { loc: -1, after: -1337 };
};
還有這個(gè)小實(shí)用方法:
function isContinuation(str) {
return str.charAt(0) === " " || str.charAt(0) === " ";
}
實(shí)施
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("
");
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
如果您有任何問題,請(qǐng)隨時(shí)提問:)
If you have any questions feel free to ask :)
但這有什么好玩的?如果您最初跳過此處,您將不會(huì)了解 HTTP 的工作原理:)
But what's the fun in that? If you skipped here initially, you wouldn't learn how HTTP works :)
Node.js 有一個(gè)內(nèi)置的 http
模塊.由于 node.js 中的請(qǐng)求本質(zhì)上是分塊的,尤其是長(zhǎng)請(qǐng)求,因此您無需更深入地了解協(xié)議即可實(shí)現(xiàn)相同的內(nèi)容.
Node.js has a built in http
module. Since requests are chunked by nature in node.js, especially long requests, you can implement the same thing without the more advanced understanding of the protocol.
這次我們使用http
模塊來創(chuàng)建一個(gè)http服務(wù)器
This time, let's use the http
module to create an http server
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);
這是基于這樣一個(gè)事實(shí),nodejs http
模塊中的 request
句柄在默認(rèn)情況下實(shí)際上是在發(fā)送標(biāo)頭后掛鉤的(但沒有執(zhí)行任何其他操作).(在服務(wù)器模塊中 , 這個(gè)在解析器模塊中)
This is based on the fact the request
handle in a nodejs http
module actually hooks on after the headers were sent (but nothing else was performed) by default. (this in the server module , this in the parser module)
用戶 igorw 建議使用 100 Continue
標(biāo)題假設(shè)您的目標(biāo)瀏覽器支持它.100 Continue 是一種狀態(tài)代碼,旨在完全按照您的意圖執(zhí)行:
User igorw suggested a somewhat cleaner solution using the 100 Continue
header assuming browsers you're targeting supports it. 100 Continue is a status code designed to do exactly what you're attempting to:
100(繼續(xù))狀態(tài)(見第 10.1.1 節(jié))的目的是為了允許發(fā)送帶有請(qǐng)求正文的請(qǐng)求消息的客戶端確定源服務(wù)器是否愿意接受請(qǐng)求(基于請(qǐng)求頭)在客戶端發(fā)送請(qǐng)求之前身體.在某些情況下,它可能不合適或高度如果服務(wù)器拒絕,客戶端發(fā)送正文的效率低下不看正文的消息.
The purpose of the 100 (Continue) status (see section 10.1.1) is to allow a client that is sending a request message with a request body to determine if the origin server is willing to accept the request (based on the request headers) before the client sends the request body. In some cases, it might either be inappropriate or highly inefficient for the client to send the body if the server will reject the message without looking at the body.
這里是:
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);
您可以在此處查看示例輸入/輸出.這將要求您使用適當(dāng)?shù)?Expect:
標(biāo)頭觸發(fā)請(qǐng)求.
You can see sample input/output here. This would require your request to fire with the appropriate Expect:
header.
這篇關(guān)于通過 PHP 或 Apache 從服務(wù)器端上傳 HTTP 文件的文章就介紹到這了,希望我們推薦的答案對(duì)大家有所幫助,也希望大家多多支持html5模板網(wǎng)!