mockm
Version:
Analog interface server, painless parallel development of front and back ends.
246 lines (222 loc) • 7 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _map = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/map"));
var _slice = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/slice"));
var _reduce = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/reduce"));
var _filter = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/filter"));
var _startsWith = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/starts-with"));
const fs = require(`fs`);
const util = require(`../util/tool.js`);
function getHtml(data) {
var _context, _context2, _context3;
// 文件路径
const pathList = (0, _map.default)(_context = data.path.replace(/\/$/, ``).split(`/`)).call(_context, (item, index, arr) => {
const cur = (0, _slice.default)(arr).call(arr, 0, index + 1);
return {
cur,
href: `${data.baseUrl}${cur.join(`/`) || `/`}`,
text: `/${(0, _slice.default)(cur).call(cur, -1)[0]}`
};
});
const pathStr = (0, _reduce.default)(pathList).call(pathList, (acc, cur) => `${acc}<a href="${cur.href}">${cur.text}</a>`, ``); // 文件列表
const fileListStr = (0, _reduce.default)(_context2 = (0, _map.default)(_context3 = data.files).call(_context3, item => ({ ...item,
mtime: util.time.dateFormat(`YYYY-MM-DD hh:mm:ss`, new Date(item.mtime)),
name: item.isFile ? item.name : `${item.name}/`,
size: item.isFile ? util.hex.getSize(item.size) : `--`
}))).call(_context2, (acc, cur) => {
return `${acc}
<div class="item">
<a href="./${cur.name}" class="name">${cur.name}</a>
<div class="size">${cur.size}</div>
<div class="mtime">${cur.mtime}</div>
<div class="action">
<a href="./${cur.name}?download=true" class="download ${cur.isFile && 'show'}">+</a>
</div>
</div>
`;
}, pathList.length > 1 ? `
<a href=".." class="item">
<div class="name">..</div>
<div class="size">--</div>
<div class="mtime">--</div>
<div class="action"> </div>
</a>
` : ``); // 文件模板
const template = `
<head>
<meta charset="UTF-8">
<base href="${data.originalUrl}" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<style>
html,
body {
margin: 0;
padding: 0;
}
.main {
font-family: 'Courier New', Courier, monospace;
}
.main a {
text-decoration: none;
}
.main .path {
word-break: break-all;
}
.main .path .item:hover {
background-color: #eee;
}
.main .list.header {
font-weight: bold;
display: flex;
}
.main .list .item {
display: flex;
}
.main .list .item:hover {
background-color: #eee;
}
.main .list .name,
.main .list .size,
.main .list .mtime {
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.main .list.header :not(.name),
.main .list .item :not(.name) {
flex-shrink: 0;
min-width: 40px;
}
.main .list .name {
width: 100%;
}
.main .list .size {
width: 120px;
text-align: left;
}
.main .list .mtime {
width: 200px;
}
.action a {
display: none;
font-style: normal;
}
.action a.show {
display: inline-block;
padding: 0 10px;
box-sizing: border-box;
}
@media screen and (max-width: 480px) {
.main .list .size,
.main .list .mtime {
display: none;
}
}
@media screen and (max-width: 768px) {
.main .list .mtime {
display: none;
}
}
</style>
<div class="main">
<div class="path">${pathStr}</div>
<div class="list header">
<div class="name">name</div>
<div class="size">size</div>
<div class="mtime">mtime</div>
<div class="action"> </div>
</div>
<div class="list fileList">${fileListStr}</div>
</div>
`;
return template;
}
const obj = {
post() {},
delete() {},
/**
* 获取目录下的内容列表
* @param {*} option
* @param {*} option.root 目录地址
* @param {*} option.sort 排序的 key
* @param {*} option.order 排序方式 asc desc
* @returns
*/
async get(option) {
var _context4, _context5;
const list = (0, _filter.default)(_context4 = (0, _map.default)(_context5 = fs.readdirSync(option.root)).call(_context5, name => {
try {
const stat = fs.statSync(`${option.root}/${name}`);
const isFile = stat.isFile();
const isDirectory = stat.isDirectory();
return isFile || isDirectory ? {
name,
// 文件大小
size: stat.size,
// 修改时间
mtime: stat.mtime,
// 创建时间
birthtime: stat.birthtime,
// 是否是文件
isFile // stat,
} : undefined;
} catch (error) {
return undefined;
}
})).call(_context4, item => item);
return list;
},
put() {}
};
module.exports = option => {
return async (req, res, next) => {
const nodePath = require(`path`);
const {
download
} = req.query;
const url = decodeURI(req.url.split(`?`)[0]);
const path = nodePath.normalize(`${option.fileDir}/${url}`);
const pathRoot = nodePath.normalize(option.fileDir);
/**
* 避免路径遍历
* https://cwe.mitre.org/data/definitions/23.html
*/
if ((0, _startsWith.default)(path).call(path, pathRoot) === false) {
res.status(403);
res.json({
msg: `Forbidden ${url}`
});
} else {
if (req.method.toLowerCase() === `get`) {
if (fs.existsSync(path)) {
if (fs.statSync(path).isDirectory()) {
const files = await obj.get({
root: path
});
const data = {
baseUrl: req.baseUrl,
originalUrl: `${req.originalUrl}/`.replace(/\/\/$/, `/`),
path: url,
// 由于数据不会存储并展示, 所以不用担心 xss
files
};
res.send(getHtml(data));
} else {
if (download) {
const name = require(`path`).parse(path).base;
res.set(`Content-Disposition`, `attachment; filename*=UTF-8''${encodeURIComponent(name)}`);
}
res.sendFile(path, option.option);
}
} else {
res.status(404);
res.json({
msg: `no such file or directory ${path}`
});
}
}
}
};
};