|
1 | 1 | import path from 'path'; |
2 | | -import { existsSync, readFileSync } from 'fs'; |
| 2 | +import {existsSync, readFileSync} from 'fs'; |
3 | 3 | import '../utils/marked.min.js'; |
4 | 4 |
|
5 | | -export default (fastify, options) => { |
| 5 | +/** |
| 6 | + * 根据扩展名返回 MIME 类型 |
| 7 | + * @param {string} ext 文件扩展名 |
| 8 | + * @returns {string} MIME 类型 |
| 9 | + */ |
| 10 | +function getMimeType(ext) { |
| 11 | + const mimeTypes = { |
| 12 | + '.txt': 'text/plain; charset=utf-8', |
| 13 | + '.html': 'text/html; charset=utf-8', |
| 14 | + '.css': 'text/css; charset=utf-8', |
| 15 | + '.js': 'application/javascript; charset=utf-8', |
| 16 | + '.json': 'application/json; charset=utf-8', |
| 17 | + '.jpg': 'image/jpeg', |
| 18 | + '.jpeg': 'image/jpeg', |
| 19 | + '.png': 'image/png', |
| 20 | + '.gif': 'image/gif', |
| 21 | + '.svg': 'image/svg+xml', |
| 22 | + '.pdf': 'application/pdf', |
| 23 | + }; |
| 24 | + return mimeTypes[ext] || 'application/octet-stream'; |
| 25 | +} |
| 26 | + |
| 27 | +export default (fastify, options, done) => { |
6 | 28 | fastify.get('/docs/*', async (request, reply) => { |
7 | | - const fullPath = path.resolve(options.docsDir, request.params['*']); |
8 | | - if (!fullPath.startsWith(options.docsDir) || !existsSync(fullPath)) { |
9 | | - reply.status(403).send('<h1>403 Forbidden</h1>'); |
10 | | - return; |
11 | | - } |
| 29 | + const fullPath = request.params['*']; // 捕获整个路径 |
| 30 | + console.log(`Request received for path: ${fullPath}`); |
| 31 | + try { |
| 32 | + const resolvedPath = path.resolve(options.docsDir, fullPath); // 将路径解析为绝对路径 |
| 33 | + |
| 34 | + // 确保 resolvedPath 在 docsDir 下 |
| 35 | + if (!resolvedPath.startsWith(options.docsDir)) { |
| 36 | + reply.status(403).send(`<h1>403 Forbidden</h1><p>Access to the requested file is forbidden.</p>`); |
| 37 | + return; |
| 38 | + } |
| 39 | + fastify.log.info(`Resolved path: ${resolvedPath}`); |
| 40 | + |
| 41 | + // 检查文件是否存在 |
| 42 | + if (!existsSync(resolvedPath)) { |
| 43 | + reply.status(404).send(`<h1>404 Not Found</h1><p>File "${fullPath}" not found in /docs.</p>`); |
| 44 | + return; |
| 45 | + } |
| 46 | + |
| 47 | + // 获取文件扩展名 |
| 48 | + const ext = path.extname(resolvedPath).toLowerCase(); |
12 | 49 |
|
13 | | - const ext = path.extname(fullPath).toLowerCase(); |
14 | | - if (ext === '.md') { |
15 | | - const markdownContent = readFileSync(fullPath, 'utf8'); |
16 | | - const htmlContent = marked.parse(markdownContent); |
17 | | - reply.type('text/html').send(htmlContent); |
18 | | - } else { |
19 | | - reply.sendFile(fullPath); |
| 50 | + if (ext === '.md') { |
| 51 | + // 处理 Markdown 文件 |
| 52 | + const markdownContent = readFileSync(resolvedPath, 'utf8'); |
| 53 | + const htmlContent = marked.parse(markdownContent); |
| 54 | + |
| 55 | + reply.type('text/html').send(` |
| 56 | + <!DOCTYPE html> |
| 57 | + <html lang="en"> |
| 58 | + <head> |
| 59 | + <meta charset="UTF-8"> |
| 60 | + <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 61 | + <title>${fullPath}</title> |
| 62 | + <style> |
| 63 | + body { font-family: Arial, sans-serif; line-height: 1.6; margin: 20px; padding: 0; } |
| 64 | + h1, h2, h3 { color: #333; } |
| 65 | + pre { background: #f4f4f4; padding: 10px; border-radius: 5px; } |
| 66 | + code { font-family: monospace; } |
| 67 | + </style> |
| 68 | + </head> |
| 69 | + <body> |
| 70 | + ${htmlContent} |
| 71 | + </body> |
| 72 | + </html> |
| 73 | + `); |
| 74 | + } else { |
| 75 | + try { |
| 76 | + const mimeType = getMimeType(ext); |
| 77 | + |
| 78 | + if (mimeType.startsWith('text') || mimeType.includes('json') || mimeType.includes('javascript')) { |
| 79 | + const fileContent = readFileSync(resolvedPath, 'utf8'); // 确保读取文本内容为 UTF-8 |
| 80 | + reply.type(mimeType).send(fileContent); |
| 81 | + } else { |
| 82 | + const fileContent = readFileSync(resolvedPath); |
| 83 | + reply.type(mimeType).send(fileContent); |
| 84 | + } |
| 85 | + |
| 86 | + } catch (e) { |
| 87 | + console.log(e); |
| 88 | + } |
| 89 | + } |
| 90 | + } catch (error) { |
| 91 | + reply.status(500).send(`<h1>500 Internal Server Error</h1><p>Error reading or rendering file: ${error.message}</p>`); |
20 | 92 | } |
21 | 93 | }); |
| 94 | + |
| 95 | + |
| 96 | + done(); |
22 | 97 | }; |
0 commit comments