Files
2023-12-24 23:44:17 +08:00

330 lines
9.3 KiB
JavaScript

var fs = require('fs');
var extend = require('extend');
var util = require('../util');
var mime = require('mime');
var qs = require('querystring');
var Buffer = require('safe-buffer').Buffer;
var rulesMgr = require('../rules');
var protoMgr = require('../rules/protocols');
var pluginMgr = require('../plugins');
var CRLF_RE = /\r\n|\r|\n/g;
var RAW_FILE_RE = /rawfile/;
var HEADERS_SEP_RE = /(\r?\n(?:\r\n|\r|\n)|\r\r\n?)/;
var MAX_HEADERS_SIZE = 256 * 1024;
var TPL_RE = /(?:dust|tpl|jsonp):$/;
var VAR_RE = /\{\S+\}/;
var QUERY_VAR_RE = /\$?(?:\{\{([\w$-]+)\}\}|\{([\w$-]+)\})/g;
var UTF8_OPTIONS = { encoding: 'utf8' };
var ENABLE_OPTIONS = { enable: true };
function isRawFileProtocol(protocol) {
return RAW_FILE_RE.test(protocol);
}
function readFiles(files, callback) {
var file = files.shift();
var execCallback = function (err, stat) {
if (!err && stat && stat.isFile()) {
callback(null, file, stat.size);
} else if (file && file.originalKey) {
pluginMgr.requestBin(file, function(buffer, err, res) {
file = file.originalKey;
callback(err, file, buffer ? buffer.length : 0, buffer, res);
});
} else if (files.length) {
readFiles(files, callback);
} else {
callback(err || new Error('Not found file ' + file), file);
}
};
!file || typeof file != 'string'
? execCallback()
: fs.stat(file, execCallback);
}
function parseRes(str, rawHeaderNames, fromValue) {
if (!str) {
return {
statusCode: 200,
headers: {}
};
}
var headers = str.split(CRLF_RE);
var statusLine = headers.shift().trim().split(/\s+/g);
headers = util.parseHeaders(headers, rawHeaderNames);
if (fromValue) {
delete headers['content-encoding'];
}
return {
statusCode: statusLine[1] || 200,
headers: headers
};
}
function addLength(reader, length) {
reader.headers['content-length'] = length;
}
function getRawResByValue(body) {
var headers;
if (HEADERS_SEP_RE.test(body)) {
var crlfStr = RegExp.$1;
var index = body.indexOf(crlfStr);
headers = body.substring(0, index);
body = body.substring(index + crlfStr.length);
}
var rawHeaderNames = {};
var reader = parseRes(headers, rawHeaderNames, true);
reader.rawHeaderNames = rawHeaderNames;
reader.body = body;
addLength(reader, body ? Buffer.byteLength(body) : 0);
return reader;
}
function getRawResByPath(protocol, path, req, size, callback, body) {
var isRawFile = isRawFileProtocol(protocol);
var range = isRawFile ? undefined : util.parseRange(req, req.localFileSize);
var reader;
if (body == null) {
reader = fs.createReadStream(path, range);
} else {
reader = util.createTransform();
if (!body || !range) {
reader.end(body);
} else {
reader.end(body.slice(range.start, range.end + 1));
}
}
var rawHeaderNames = (reader.rawHeaderNames = rawHeaderNames);
if (isRawFile) {
var buffer;
var response = function (err, crlf) {
reader.removeAllListeners();
reader.on('error', util.noop);
var stream = reader;
reader = util.createTransform();
if (err) {
var stack = util.getErrorStack(err);
reader.statusCode = 500;
reader.push(stack);
reader.push(null);
size = Buffer.byteLength(stack);
} else {
if (crlf) {
crlf = util.toBuffer(crlf);
var index = util.indexOfList(buffer, crlf);
if (index != -1) {
extend(
reader,
parseRes(buffer.slice(0, index) + '', rawHeaderNames)
);
var headerLen = index + crlf.length;
size -= headerLen;
buffer = buffer.slice(headerLen);
}
}
buffer && reader.push(buffer);
stream.on('error', function (err) {
reader.emit('error', err);
});
stream.pipe(reader);
}
callback(reader, null, size);
};
reader.on('data', function (data) {
buffer = buffer ? Buffer.concat([buffer, data]) : data;
if (HEADERS_SEP_RE.test(buffer + '')) {
response(null, RegExp.$1);
} else if (buffer.length > MAX_HEADERS_SIZE) {
response();
}
});
reader.on('error', response);
reader.on('end', response);
} else {
callback(reader, range, size);
}
}
function addRangeHeaders(res, range, size) {
if (!range) {
return;
}
var headers = res.headers;
headers['content-range'] =
'bytes ' + (range.start + '-' + range.end) + '/' + size;
headers['accept-ranges'] = 'bytes';
headers['content-length'] = range.end - range.start + 1;
res.statusCode = 206;
}
function isAutoCors(req, rule) {
if (rule.lineProps.disableAutoCors || rule.lineProps.disabledAutoCors) {
return false;
}
return !req.disable.autoCors && req.headers.origin;
}
function sendResponse(rule, res, reader, req) {
if (isAutoCors(req, rule)) {
util.setResCors(reader, ENABLE_OPTIONS, req);
}
rule.isLoc = 1;
res.response(reader);
}
module.exports = function (req, res, next) {
var options = req.options;
var config = this.config;
var protocol = options && options.protocol;
if (!protoMgr.isFileProxy(protocol)) {
return next();
}
var rules = req.rules;
var rule = rules.rule;
if (isAutoCors(req, rule) && req.method === 'OPTIONS') {
var optsRes = util.wrapResponse({ statusCode: 200 });
optsRes.realUrl = rule.matcher;
return sendResponse(rule, res, optsRes, req);
}
var isTpl = TPL_RE.test(protocol);
var defaultType = mime.lookup(
req.fullUrl.replace(/[?#].*$/, ''),
'text/html'
);
delete rules.proxy;
delete rules.host;
if (rule.value) {
var body = util.removeProtocol(rule.value, true);
var isRawFile = isRawFileProtocol(protocol);
var reader = isRawFile
? getRawResByValue(body)
: {
statusCode: 200,
body: body,
headers: {
'content-type':
(rule.key ? mime.lookup(rule.key, defaultType) : defaultType) +
'; charset=utf-8'
}
};
if (isTpl) {
reader.realUrl = rule.matcher;
render(reader);
} else {
if (!isRawFile) {
var size = Buffer.byteLength(body);
var range = util.parseRange(req, size);
if (range) {
body = Buffer.from(body);
reader.body = body.slice(range.start, range.end + 1);
addRangeHeaders(reader, range, size);
} else {
addLength(reader, size);
}
}
reader = util.wrapResponse(reader);
reader.realUrl = rule.matcher;
sendResponse(rule, res, reader, req);
}
return;
}
readFiles(util.getRuleFiles(rule, req), function (err, path, size, buffer, svrRes) {
if (err) {
if (/^x/.test(protocol)) {
var fullUrl = /^xs/.test(protocol)
? req.fullUrl.replace(/^http:/, 'https:')
: req.fullUrl;
extend(options, util.parseUrl(fullUrl));
next();
} else {
var is502 = err.code > 0 && err.code != 404;
var notFound = util.wrapResponse({
statusCode: is502 ? 502 : 404,
body: is502 ? err.message : 'Not found file <strong>' + path + '</strong>',
headers: {
'content-type': 'text/html; charset=utf-8'
}
});
notFound.realUrl = rule.matcher;
sendResponse(rule, res, notFound, req);
}
return;
}
var type = buffer != null && svrRes && svrRes.headers && svrRes.headers['x-whistle-response-type'];
var headers = {
server: config.name,
'content-type': type || mime.lookup(rule._suffix || path, defaultType) + '; charset=utf-8'
};
if (isTpl) {
var reader = {
statusCode: 200,
realUrl: path,
headers: headers
};
if (buffer != null) {
reader.body = buffer + '';
render(reader);
} else {
fs.readFile(path, UTF8_OPTIONS, function (err, data) {
if (err) {
return util.emitError(req, err);
}
reader.body = data;
render(reader);
});
}
} else {
req.localFileSize = size;
getRawResByPath(
protocol,
path,
req,
size,
function (reader, range, realSize) {
reader.realUrl = path;
reader.statusCode = reader.statusCode || 200;
reader.headers = reader.headers || headers;
addRangeHeaders(reader, range, size);
!range && addLength(reader, realSize);
sendResponse(rule, res, reader, req);
},
buffer
);
}
});
function render(reader) {
if (reader.body) {
if (VAR_RE.test(reader.body)) {
var data = util.getQueryString(req.fullUrl);
data = data && qs.parse(data);
if (data) {
reader.body = reader.body.replace(QUERY_VAR_RE, function (all, matched1, matched2) {
if (all[0] === '$') {
return all;
}
var value = data[matched1 || matched2];
return value === undefined ? all : util.getQueryValue(value);
}
);
}
reader.body = rulesMgr.resolveTplVar(reader.body, req);
}
addLength(reader, Buffer.byteLength(reader.body));
} else {
addLength(reader, 0);
}
var realUrl = reader.realUrl;
reader = util.wrapResponse(reader);
reader.realUrl = realUrl;
sendResponse(rule, res, reader, req);
}
};