@weibaohui/dsh-git-server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -0
- package/client/bundle.js +2356 -0
- package/client/index.js +2342 -0
- package/cordis.patch.yml +11 -0
- package/package.json +70 -0
- package/server/dist/api/v1.js +2029 -0
- package/server/dist/authx/password.js +43 -0
- package/server/dist/authx/um.js +157 -0
- package/server/dist/conf.js +323 -0
- package/server/dist/context.js +701 -0
- package/server/dist/db/actions.js +202 -0
- package/server/dist/db/db.js +838 -0
- package/server/dist/dshapi.js +1416 -0
- package/server/dist/editorconfig.js +104 -0
- package/server/dist/gitx/git.js +580 -0
- package/server/dist/gitx/http.js +232 -0
- package/server/dist/gitx/paths.js +28 -0
- package/server/dist/gitx/service.js +251 -0
- package/server/dist/gosprintf.js +167 -0
- package/server/dist/hook.js +53 -0
- package/server/dist/index.js +112 -0
- package/server/dist/lfsx.js +265 -0
- package/server/dist/mailer.js +127 -0
- package/server/dist/markup.js +317 -0
- package/server/dist/metrics.js +18 -0
- package/server/dist/mirror.js +203 -0
- package/server/dist/multipart.js +33 -0
- package/server/dist/repox.js +99 -0
- package/server/dist/router.js +185 -0
- package/server/dist/serv.js +43 -0
- package/server/dist/server.js +382 -0
- package/server/dist/sshx/dispatch.js +160 -0
- package/server/dist/sshx/lfs-transfer.js +289 -0
- package/server/dist/sshx/server.js +107 -0
- package/server/dist/toolx.js +108 -0
- package/server/dist/twofactor.js +160 -0
- package/server/dist/webapi.js +377 -0
- package/server/dist/webhook.js +128 -0
- package/server/package.json +34 -0
- package/server/public/img/avatar_default.png +0 -0
- package/server/tsconfig.json +19 -0
- package/server/vendored-conf/app.ini +538 -0
- package/server/vendored-conf/gitignore/Actionscript +11 -0
- package/server/vendored-conf/gitignore/Ada +5 -0
- package/server/vendored-conf/gitignore/Agda +1 -0
- package/server/vendored-conf/gitignore/Android +32 -0
- package/server/vendored-conf/gitignore/Anjuta +3 -0
- package/server/vendored-conf/gitignore/AppEngine +2 -0
- package/server/vendored-conf/gitignore/AppceleratorTitanium +3 -0
- package/server/vendored-conf/gitignore/ArchLinuxPackages +13 -0
- package/server/vendored-conf/gitignore/Archives +27 -0
- package/server/vendored-conf/gitignore/Autotools +14 -0
- package/server/vendored-conf/gitignore/BricxCC +4 -0
- package/server/vendored-conf/gitignore/C +32 -0
- package/server/vendored-conf/gitignore/C Sharp +108 -0
- package/server/vendored-conf/gitignore/C++ +28 -0
- package/server/vendored-conf/gitignore/CFWheels +12 -0
- package/server/vendored-conf/gitignore/CMake +6 -0
- package/server/vendored-conf/gitignore/CUDA +6 -0
- package/server/vendored-conf/gitignore/CakePHP +13 -0
- package/server/vendored-conf/gitignore/ChefCookbook +9 -0
- package/server/vendored-conf/gitignore/Cloud9 +3 -0
- package/server/vendored-conf/gitignore/CodeIgniter +6 -0
- package/server/vendored-conf/gitignore/CodeKit +3 -0
- package/server/vendored-conf/gitignore/CommonLisp +3 -0
- package/server/vendored-conf/gitignore/Composer +6 -0
- package/server/vendored-conf/gitignore/Concrete5 +3 -0
- package/server/vendored-conf/gitignore/Coq +3 -0
- package/server/vendored-conf/gitignore/CraftCMS +3 -0
- package/server/vendored-conf/gitignore/DM +5 -0
- package/server/vendored-conf/gitignore/Dart +15 -0
- package/server/vendored-conf/gitignore/DartEditor +2 -0
- package/server/vendored-conf/gitignore/Delphi +57 -0
- package/server/vendored-conf/gitignore/Dreamweaver +3 -0
- package/server/vendored-conf/gitignore/Drupal +36 -0
- package/server/vendored-conf/gitignore/EPiServer +4 -0
- package/server/vendored-conf/gitignore/Eagle +36 -0
- package/server/vendored-conf/gitignore/Eclipse +39 -0
- package/server/vendored-conf/gitignore/EiffelStudio +2 -0
- package/server/vendored-conf/gitignore/Elisp +5 -0
- package/server/vendored-conf/gitignore/Elixir +4 -0
- package/server/vendored-conf/gitignore/Emacs +32 -0
- package/server/vendored-conf/gitignore/Ensime +4 -0
- package/server/vendored-conf/gitignore/Erlang +10 -0
- package/server/vendored-conf/gitignore/Espresso +1 -0
- package/server/vendored-conf/gitignore/ExpressionEngine +19 -0
- package/server/vendored-conf/gitignore/ExtJs +4 -0
- package/server/vendored-conf/gitignore/Fancy +2 -0
- package/server/vendored-conf/gitignore/Finale +13 -0
- package/server/vendored-conf/gitignore/FlexBuilder +3 -0
- package/server/vendored-conf/gitignore/ForceDotCom +4 -0
- package/server/vendored-conf/gitignore/FuelPHP +2 -0
- package/server/vendored-conf/gitignore/GWT +28 -0
- package/server/vendored-conf/gitignore/Gcov +5 -0
- package/server/vendored-conf/gitignore/GitBook +16 -0
- package/server/vendored-conf/gitignore/Go +24 -0
- package/server/vendored-conf/gitignore/Gradle +8 -0
- package/server/vendored-conf/gitignore/Grails +33 -0
- package/server/vendored-conf/gitignore/Haskell +16 -0
- package/server/vendored-conf/gitignore/IGORPro +5 -0
- package/server/vendored-conf/gitignore/IPythonNotebook +2 -0
- package/server/vendored-conf/gitignore/Idris +2 -0
- package/server/vendored-conf/gitignore/JDeveloper +13 -0
- package/server/vendored-conf/gitignore/Java +12 -0
- package/server/vendored-conf/gitignore/Jboss +19 -0
- package/server/vendored-conf/gitignore/Jekyll +3 -0
- package/server/vendored-conf/gitignore/JetBrains +46 -0
- package/server/vendored-conf/gitignore/Joomla +525 -0
- package/server/vendored-conf/gitignore/KDevelop4 +2 -0
- package/server/vendored-conf/gitignore/Kate +3 -0
- package/server/vendored-conf/gitignore/KiCAD +13 -0
- package/server/vendored-conf/gitignore/Kohana +2 -0
- package/server/vendored-conf/gitignore/LabVIEW +16 -0
- package/server/vendored-conf/gitignore/Laravel +4 -0
- package/server/vendored-conf/gitignore/Lazarus +30 -0
- package/server/vendored-conf/gitignore/Leiningen +12 -0
- package/server/vendored-conf/gitignore/LemonStand +21 -0
- package/server/vendored-conf/gitignore/LibreOffice +2 -0
- package/server/vendored-conf/gitignore/Lilypond +6 -0
- package/server/vendored-conf/gitignore/Linux +7 -0
- package/server/vendored-conf/gitignore/Lithium +2 -0
- package/server/vendored-conf/gitignore/Lua +41 -0
- package/server/vendored-conf/gitignore/LyX +4 -0
- package/server/vendored-conf/gitignore/Magento +126 -0
- package/server/vendored-conf/gitignore/Matlab +16 -0
- package/server/vendored-conf/gitignore/Maven +9 -0
- package/server/vendored-conf/gitignore/Mercurial +6 -0
- package/server/vendored-conf/gitignore/Mercury +13 -0
- package/server/vendored-conf/gitignore/MetaProgrammingSystem +16 -0
- package/server/vendored-conf/gitignore/MicrosoftOffice +10 -0
- package/server/vendored-conf/gitignore/ModelSim +23 -0
- package/server/vendored-conf/gitignore/Momentics +8 -0
- package/server/vendored-conf/gitignore/MonoDevelop +8 -0
- package/server/vendored-conf/gitignore/Nanoc +10 -0
- package/server/vendored-conf/gitignore/NetBeans +8 -0
- package/server/vendored-conf/gitignore/Nim +1 -0
- package/server/vendored-conf/gitignore/Ninja +2 -0
- package/server/vendored-conf/gitignore/Node +28 -0
- package/server/vendored-conf/gitignore/NotepadPP +2 -0
- package/server/vendored-conf/gitignore/OCaml +20 -0
- package/server/vendored-conf/gitignore/Objective-C +43 -0
- package/server/vendored-conf/gitignore/Opa +13 -0
- package/server/vendored-conf/gitignore/OpenCart +11 -0
- package/server/vendored-conf/gitignore/OracleForms +8 -0
- package/server/vendored-conf/gitignore/Packer +5 -0
- package/server/vendored-conf/gitignore/Perl +20 -0
- package/server/vendored-conf/gitignore/Phalcon +2 -0
- package/server/vendored-conf/gitignore/PhpStorm +63 -0
- package/server/vendored-conf/gitignore/PlayFramework +17 -0
- package/server/vendored-conf/gitignore/Plone +18 -0
- package/server/vendored-conf/gitignore/Prestashop +25 -0
- package/server/vendored-conf/gitignore/Processing +7 -0
- package/server/vendored-conf/gitignore/Python +58 -0
- package/server/vendored-conf/gitignore/Qooxdoo +5 -0
- package/server/vendored-conf/gitignore/Qt +34 -0
- package/server/vendored-conf/gitignore/R +16 -0
- package/server/vendored-conf/gitignore/ROS +47 -0
- package/server/vendored-conf/gitignore/Rails +35 -0
- package/server/vendored-conf/gitignore/Redcar +1 -0
- package/server/vendored-conf/gitignore/Redis +3 -0
- package/server/vendored-conf/gitignore/RhodesRhomobile +9 -0
- package/server/vendored-conf/gitignore/Ruby +36 -0
- package/server/vendored-conf/gitignore/Rust +11 -0
- package/server/vendored-conf/gitignore/SBT +9 -0
- package/server/vendored-conf/gitignore/SCons +2 -0
- package/server/vendored-conf/gitignore/SVN +1 -0
- package/server/vendored-conf/gitignore/Sass +2 -0
- package/server/vendored-conf/gitignore/Scala +17 -0
- package/server/vendored-conf/gitignore/Scrivener +7 -0
- package/server/vendored-conf/gitignore/Sdcc +8 -0
- package/server/vendored-conf/gitignore/SeamGen +26 -0
- package/server/vendored-conf/gitignore/SketchUp +1 -0
- package/server/vendored-conf/gitignore/SlickEdit +11 -0
- package/server/vendored-conf/gitignore/Stella +12 -0
- package/server/vendored-conf/gitignore/SublimeText +14 -0
- package/server/vendored-conf/gitignore/SugarCRM +25 -0
- package/server/vendored-conf/gitignore/Swift +43 -0
- package/server/vendored-conf/gitignore/Symfony +37 -0
- package/server/vendored-conf/gitignore/SymphonyCMS +6 -0
- package/server/vendored-conf/gitignore/SynopsysVCS +36 -0
- package/server/vendored-conf/gitignore/Tags +14 -0
- package/server/vendored-conf/gitignore/TeX +136 -0
- package/server/vendored-conf/gitignore/TextMate +3 -0
- package/server/vendored-conf/gitignore/Textpattern +11 -0
- package/server/vendored-conf/gitignore/TortoiseGit +2 -0
- package/server/vendored-conf/gitignore/TurboGears2 +20 -0
- package/server/vendored-conf/gitignore/Typo3 +20 -0
- package/server/vendored-conf/gitignore/Umbraco +17 -0
- package/server/vendored-conf/gitignore/Unity +21 -0
- package/server/vendored-conf/gitignore/UnrealEngine +76 -0
- package/server/vendored-conf/gitignore/VVVV +6 -0
- package/server/vendored-conf/gitignore/Vagrant +1 -0
- package/server/vendored-conf/gitignore/Vim +6 -0
- package/server/vendored-conf/gitignore/VirtualEnv +10 -0
- package/server/vendored-conf/gitignore/VisualStudio +217 -0
- package/server/vendored-conf/gitignore/VisualStudioCode +2 -0
- package/server/vendored-conf/gitignore/Waf +3 -0
- package/server/vendored-conf/gitignore/WebMethods +14 -0
- package/server/vendored-conf/gitignore/WebStorm +63 -0
- package/server/vendored-conf/gitignore/Windows +18 -0
- package/server/vendored-conf/gitignore/WordPress +18 -0
- package/server/vendored-conf/gitignore/Xcode +23 -0
- package/server/vendored-conf/gitignore/XilinxISE +57 -0
- package/server/vendored-conf/gitignore/Xojo +11 -0
- package/server/vendored-conf/gitignore/Yeoman +6 -0
- package/server/vendored-conf/gitignore/Yii +6 -0
- package/server/vendored-conf/gitignore/ZendFramework +20 -0
- package/server/vendored-conf/gitignore/Zephir +26 -0
- package/server/vendored-conf/gitignore/macOS +25 -0
- package/server/vendored-conf/label/Default +7 -0
- package/server/vendored-conf/license/Abstyles License +10 -0
- package/server/vendored-conf/license/Academic Free License v1.1 +22 -0
- package/server/vendored-conf/license/Academic Free License v1.2 +27 -0
- package/server/vendored-conf/license/Academic Free License v2.0 +43 -0
- package/server/vendored-conf/license/Academic Free License v2.1 +44 -0
- package/server/vendored-conf/license/Academic Free License v3.0 +42 -0
- package/server/vendored-conf/license/Affero General Public License v1.0 +84 -0
- package/server/vendored-conf/license/Apache License 1.0 +19 -0
- package/server/vendored-conf/license/Apache License 1.1 +20 -0
- package/server/vendored-conf/license/Apache License 2.0 +72 -0
- package/server/vendored-conf/license/Artistic License 1.0 +48 -0
- package/server/vendored-conf/license/Artistic License 2.0 +84 -0
- package/server/vendored-conf/license/BSD 2-clause License +8 -0
- package/server/vendored-conf/license/BSD 3-clause License +10 -0
- package/server/vendored-conf/license/BSD 4-clause License +13 -0
- package/server/vendored-conf/license/Creative Commons CC0 1.0 Universal +39 -0
- package/server/vendored-conf/license/Eclipse Public License 1.0 +72 -0
- package/server/vendored-conf/license/Educational Community License v1.0 +22 -0
- package/server/vendored-conf/license/Educational Community License v2.0 +97 -0
- package/server/vendored-conf/license/GNU Affero General Public License v3.0 +241 -0
- package/server/vendored-conf/license/GNU Free Documentation License v1.1 +118 -0
- package/server/vendored-conf/license/GNU Free Documentation License v1.2 +129 -0
- package/server/vendored-conf/license/GNU Free Documentation License v1.3 +148 -0
- package/server/vendored-conf/license/GNU General Public License v1.0 +99 -0
- package/server/vendored-conf/license/GNU General Public License v2.0 +116 -0
- package/server/vendored-conf/license/GNU General Public License v3.0 +231 -0
- package/server/vendored-conf/license/GNU Lesser General Public License v2.1 +174 -0
- package/server/vendored-conf/license/GNU Lesser General Public License v3.0 +70 -0
- package/server/vendored-conf/license/GNU Library General Public License v2.0 +173 -0
- package/server/vendored-conf/license/ISC license +6 -0
- package/server/vendored-conf/license/MIT License +8 -0
- package/server/vendored-conf/license/Mozilla Public License 1.0 +122 -0
- package/server/vendored-conf/license/Mozilla Public License 1.1 +142 -0
- package/server/vendored-conf/license/Mozilla Public License 2.0 +143 -0
- package/server/vendored-conf/readme/Default +3 -0
- package/src/dsh-host.js +168 -0
- package/src/index.js +682 -0
- package/src/runner.js +364 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
function compilePattern(pattern) {
|
|
2
|
+
const paramNames = [];
|
|
3
|
+
let source;
|
|
4
|
+
// macaron-style regex route: leading "/^" (drop the slash and the anchors —
|
|
5
|
+
// the compiled form already anchors ^/… and tolerates a trailing slash)
|
|
6
|
+
if (pattern.startsWith('/^')) {
|
|
7
|
+
const body = pattern.slice(2).replace(/\$$/, '').replace(/^\^/, '');
|
|
8
|
+
const compiled = compilePattern('/' + body);
|
|
9
|
+
return compiled;
|
|
10
|
+
}
|
|
11
|
+
if (pattern.startsWith('^')) {
|
|
12
|
+
// Full regex form, e.g. /^:type(issues|pulls)$ — :name(rx) becomes a named
|
|
13
|
+
// capture; bare :name matches [^/]+; other chars literal.
|
|
14
|
+
source = pattern.slice(1);
|
|
15
|
+
source = source.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)\(((?:[^()\\]|\\.)*)\)/g, (_m, name, rx) => {
|
|
16
|
+
paramNames.push(name);
|
|
17
|
+
return `(${rx})`;
|
|
18
|
+
});
|
|
19
|
+
source = source.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_m, name) => {
|
|
20
|
+
paramNames.push(name);
|
|
21
|
+
return `([^/]+)`;
|
|
22
|
+
});
|
|
23
|
+
return { regex: new RegExp('^' + source + '$'), paramNames };
|
|
24
|
+
}
|
|
25
|
+
const segs = pattern.split('/');
|
|
26
|
+
const parts = [];
|
|
27
|
+
for (let i = 0; i < segs.length; i++) {
|
|
28
|
+
const seg = segs[i];
|
|
29
|
+
if (seg === '') {
|
|
30
|
+
if (i === 0)
|
|
31
|
+
parts.push('');
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (seg === '*') {
|
|
35
|
+
paramNames.push('*');
|
|
36
|
+
parts.push('(.*)');
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
// general segment scan: literal text mixed with :name and :name(rx) params
|
|
40
|
+
// (e.g. `:sha([a-f0-9]{7,40}).:ext(patch|diff)`)
|
|
41
|
+
let rx = '';
|
|
42
|
+
let k = 0;
|
|
43
|
+
let matched = false;
|
|
44
|
+
while (k < seg.length) {
|
|
45
|
+
if (seg[k] === ':') {
|
|
46
|
+
const nm = /^:([a-zA-Z_][a-zA-Z0-9_]*)/.exec(seg.slice(k));
|
|
47
|
+
if (nm) {
|
|
48
|
+
paramNames.push(nm[1]);
|
|
49
|
+
matched = true;
|
|
50
|
+
k += nm[0].length;
|
|
51
|
+
if (seg[k] === '(') {
|
|
52
|
+
// balanced-paren rx group — wrap in a capture group for params
|
|
53
|
+
let depth = 1;
|
|
54
|
+
let e = k + 1;
|
|
55
|
+
while (e < seg.length && depth > 0) {
|
|
56
|
+
if (seg[e] === '(')
|
|
57
|
+
depth++;
|
|
58
|
+
else if (seg[e] === ')')
|
|
59
|
+
depth--;
|
|
60
|
+
else if (seg[e] === '\\')
|
|
61
|
+
e++;
|
|
62
|
+
e++;
|
|
63
|
+
}
|
|
64
|
+
rx += '(' + seg.slice(k + 1, e - 1) + ')';
|
|
65
|
+
k = e;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
rx += '([^/]+)';
|
|
69
|
+
k += 0;
|
|
70
|
+
}
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
rx += seg[k].replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
75
|
+
k++;
|
|
76
|
+
}
|
|
77
|
+
if (matched) {
|
|
78
|
+
parts.push(rx);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
parts.push(seg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
82
|
+
}
|
|
83
|
+
source = parts.join('/');
|
|
84
|
+
return { regex: new RegExp('^' + source + '/?$'), paramNames };
|
|
85
|
+
}
|
|
86
|
+
/** macaron Tree semantics: at any segment position a static literal beats a
|
|
87
|
+
* :param, which beats a * wildcard — regardless of registration order. */
|
|
88
|
+
function segmentKinds(pattern) {
|
|
89
|
+
if (pattern.startsWith('/^') || pattern.startsWith('^'))
|
|
90
|
+
return [1];
|
|
91
|
+
return pattern
|
|
92
|
+
.split('/')
|
|
93
|
+
.slice(1)
|
|
94
|
+
.filter((s) => s !== '')
|
|
95
|
+
.map((seg) => (seg === '*' ? 2 : seg.includes(':') ? 1 : 0));
|
|
96
|
+
}
|
|
97
|
+
export class Router {
|
|
98
|
+
routes = [];
|
|
99
|
+
add(method, pattern, ...handlers) {
|
|
100
|
+
const { regex, paramNames } = compilePattern(pattern);
|
|
101
|
+
this.routes.push({ method, pattern, regex, paramNames, handlers, kinds: segmentKinds(pattern) });
|
|
102
|
+
}
|
|
103
|
+
get(pattern, ...h) { this.add('GET', pattern, ...h); }
|
|
104
|
+
post(pattern, ...h) { this.add('POST', pattern, ...h); }
|
|
105
|
+
put(pattern, ...h) { this.add('PUT', pattern, ...h); }
|
|
106
|
+
patch(pattern, ...h) { this.add('PATCH', pattern, ...h); }
|
|
107
|
+
delete(pattern, ...h) { this.add('DELETE', pattern, ...h); }
|
|
108
|
+
head(pattern, ...h) { this.add('HEAD', pattern, ...h); }
|
|
109
|
+
options(pattern, ...h) { this.add('OPTIONS', pattern, ...h); }
|
|
110
|
+
any(pattern, ...h) { this.add('ANY', pattern, ...h); }
|
|
111
|
+
/** Route(pattern, "GET,POST", handlers...) — macaron style */
|
|
112
|
+
route(pattern, methods, ...h) {
|
|
113
|
+
for (const m of methods.split(','))
|
|
114
|
+
this.add(m.trim().toUpperCase(), pattern, ...h);
|
|
115
|
+
}
|
|
116
|
+
combo(pattern, ...h) {
|
|
117
|
+
// treated as route registered for GET + POST when handlers count allows;
|
|
118
|
+
// gogs uses Combo().Get(x).Post(y) — handled by route() calls at registration
|
|
119
|
+
this.route(pattern, 'GET,POST', ...h);
|
|
120
|
+
}
|
|
121
|
+
/** macaron tree priority: a route wins over another if at the first differing
|
|
122
|
+
* segment it is more specific (static 0 < param 1 < wildcard 2). Ties keep
|
|
123
|
+
* registration order (best stays). */
|
|
124
|
+
static moreSpecific(a, b) {
|
|
125
|
+
const n = Math.min(a.kinds.length, b.kinds.length);
|
|
126
|
+
for (let i = 0; i < n; i++) {
|
|
127
|
+
if (a.kinds[i] !== b.kinds[i])
|
|
128
|
+
return a.kinds[i] < b.kinds[i] ? a : b;
|
|
129
|
+
}
|
|
130
|
+
return a; // tie — prefer the earlier-registered route (a is current best)
|
|
131
|
+
}
|
|
132
|
+
/** Find first matching route. HEAD falls back to GET (SetAutoHead). */
|
|
133
|
+
match(method, pathname) {
|
|
134
|
+
let best = null;
|
|
135
|
+
let getFallback = null;
|
|
136
|
+
for (const r of this.routes) {
|
|
137
|
+
if (r.method !== 'ANY' && r.method !== method) {
|
|
138
|
+
if (!(method === 'HEAD' && r.method === 'GET'))
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const m = r.regex.exec(pathname);
|
|
142
|
+
if (!m)
|
|
143
|
+
continue;
|
|
144
|
+
const hit = {
|
|
145
|
+
route: r,
|
|
146
|
+
params: r.paramNames.reduce((acc, name, i) => {
|
|
147
|
+
acc[name === '*' ? ':*' : ':' + name] = decodeURIComponent(m[i + 1] ?? '');
|
|
148
|
+
return acc;
|
|
149
|
+
}, {}),
|
|
150
|
+
};
|
|
151
|
+
if (method === 'HEAD' && r.method === 'GET') {
|
|
152
|
+
if (!getFallback || Router.moreSpecific(getFallback.route, hit.route) === hit.route)
|
|
153
|
+
getFallback = hit;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (!best || Router.moreSpecific(best.route, hit.route) === hit.route)
|
|
157
|
+
best = hit;
|
|
158
|
+
}
|
|
159
|
+
return best ?? getFallback;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/** Group builder collecting prefix + middleware, mirroring m.Group(). */
|
|
163
|
+
export class GroupBuilder {
|
|
164
|
+
router;
|
|
165
|
+
prefix;
|
|
166
|
+
middleware;
|
|
167
|
+
constructor(router, prefix, middleware) {
|
|
168
|
+
this.router = router;
|
|
169
|
+
this.prefix = prefix;
|
|
170
|
+
this.middleware = middleware;
|
|
171
|
+
}
|
|
172
|
+
Group(prefix, fn, ...middleware) {
|
|
173
|
+
const g = new GroupBuilder(this.router, this.prefix + prefix, [...this.middleware, ...middleware]);
|
|
174
|
+
fn(g);
|
|
175
|
+
}
|
|
176
|
+
Get(pattern, ...h) { this.router.get(this.prefix + pattern, ...this.middleware, ...h); }
|
|
177
|
+
Post(pattern, ...h) { this.router.post(this.prefix + pattern, ...this.middleware, ...h); }
|
|
178
|
+
Put(pattern, ...h) { this.router.put(this.prefix + pattern, ...this.middleware, ...h); }
|
|
179
|
+
Patch(pattern, ...h) { this.router.patch(this.prefix + pattern, ...this.middleware, ...h); }
|
|
180
|
+
Delete(pattern, ...h) { this.router.delete(this.prefix + pattern, ...this.middleware, ...h); }
|
|
181
|
+
Any(pattern, ...h) { this.router.any(this.prefix + pattern, ...this.middleware, ...h); }
|
|
182
|
+
Route(pattern, methods, ...h) { this.router.route(this.prefix + pattern, methods, ...this.middleware, ...h); }
|
|
183
|
+
Combo(pattern, ...h) { this.router.route(this.prefix + pattern, 'GET,POST', ...this.middleware, ...h); }
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=router.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// serv subcommand: entrypoint for authorized_keys mode. The system sshd runs
|
|
2
|
+
// `<ts-gogs> serv key-<id>` via a command= restriction on each public key line;
|
|
3
|
+
// the actual git command arrives in SSH_ORIGINAL_COMMAND (gogs cmd/gogs/serv.go).
|
|
4
|
+
import * as db from './db/db.js';
|
|
5
|
+
export async function runServ(keyArg) {
|
|
6
|
+
const keyID = Number(String(keyArg).replace(/^key-/, ''));
|
|
7
|
+
const key = db.getPublicKeyByID(keyID);
|
|
8
|
+
if (!key) {
|
|
9
|
+
console.error('Serv: cannot find public key:', keyArg);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
const userID = key.owner_id;
|
|
13
|
+
const identity = {
|
|
14
|
+
userID,
|
|
15
|
+
keyID,
|
|
16
|
+
deployRepoID: null,
|
|
17
|
+
};
|
|
18
|
+
if (key.type === 2) {
|
|
19
|
+
const dk = db.db().prepare('SELECT repo_id FROM deploy_key WHERE key_id = ?').get(keyID);
|
|
20
|
+
identity.deployRepoID = dk?.repo_id ?? null;
|
|
21
|
+
}
|
|
22
|
+
const sshCmd = process.env.SSH_ORIGINAL_COMMAND ?? '';
|
|
23
|
+
if (!sshCmd) {
|
|
24
|
+
console.error('Serv: interactive shell is disabled.');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
const { dispatchSSHCommand } = await import('./sshx/dispatch.js');
|
|
28
|
+
// keep the event loop alive until close() — async handlers (LFS transfer)
|
|
29
|
+
// must never be cut off by a premature exit
|
|
30
|
+
const keepAlive = setInterval(() => { }, 1 << 30);
|
|
31
|
+
dispatchSSHCommand(sshCmd, identity, {
|
|
32
|
+
write: (chunk) => process.stdout.write(chunk),
|
|
33
|
+
onStdin: (cb) => {
|
|
34
|
+
process.stdin.on('data', (d) => cb(d));
|
|
35
|
+
process.stdin.resume();
|
|
36
|
+
},
|
|
37
|
+
close: (code) => {
|
|
38
|
+
clearInterval(keepAlive);
|
|
39
|
+
process.exit(code);
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=serv.js.map
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
// HTTP server: request pipeline wiring session/context/router/static/SPA,
|
|
2
|
+
// mirroring cmd/gogs/internal/web/web.go dispatch order.
|
|
3
|
+
import * as http from 'node:http';
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import { conf } from './conf.js';
|
|
7
|
+
import { Context, Session, Contexter, setServeWebHandler } from './context.js';
|
|
8
|
+
import { registerAPIRoutes } from './api/v1.js';
|
|
9
|
+
import { handleGitHTTP } from './gitx/http.js';
|
|
10
|
+
const MIME = {
|
|
11
|
+
'.html': 'text/html; charset=utf-8',
|
|
12
|
+
'.css': 'text/css; charset=utf-8',
|
|
13
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
14
|
+
'.mjs': 'text/javascript; charset=utf-8',
|
|
15
|
+
'.json': 'application/json',
|
|
16
|
+
'.png': 'image/png',
|
|
17
|
+
'.jpg': 'image/jpeg',
|
|
18
|
+
'.jpeg': 'image/jpeg',
|
|
19
|
+
'.gif': 'image/gif',
|
|
20
|
+
'.svg': 'image/svg+xml',
|
|
21
|
+
'.ico': 'image/x-icon',
|
|
22
|
+
'.woff': 'font/woff',
|
|
23
|
+
'.woff2': 'font/woff2',
|
|
24
|
+
'.ttf': 'font/ttf',
|
|
25
|
+
'.eot': 'application/vnd.ms-fontobject',
|
|
26
|
+
'.map': 'application/json',
|
|
27
|
+
'.txt': 'text/plain; charset=utf-8',
|
|
28
|
+
'.md': 'text/markdown; charset=utf-8',
|
|
29
|
+
'.pdf': 'application/pdf',
|
|
30
|
+
'.wasm': 'application/wasm',
|
|
31
|
+
};
|
|
32
|
+
function serveFile(res, file, cacheForever = true) {
|
|
33
|
+
try {
|
|
34
|
+
const stat = fs.statSync(file);
|
|
35
|
+
if (!stat.isFile())
|
|
36
|
+
return false;
|
|
37
|
+
const ext = path.extname(file).toLowerCase();
|
|
38
|
+
res.setHeader('Content-Type', MIME[ext] ?? 'application/octet-stream');
|
|
39
|
+
if (cacheForever) {
|
|
40
|
+
res.setHeader('Cache-Control', 'public, max-age=31536000');
|
|
41
|
+
}
|
|
42
|
+
res.setHeader('Content-Length', String(stat.size));
|
|
43
|
+
fs.createReadStream(file).pipe(res);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function serveStaticPrefix(req, res, pathname) {
|
|
51
|
+
// static roots, mirroring macaron.Static registration order
|
|
52
|
+
for (const [prefix, dir] of [
|
|
53
|
+
['/css/', path.join(conf.workDir, 'public', 'css')],
|
|
54
|
+
['/js/', path.join(conf.workDir, 'public', 'js')],
|
|
55
|
+
['/img/', path.join(conf.workDir, 'public', 'img')],
|
|
56
|
+
['/plugins/', path.join(conf.workDir, 'public', 'plugins')],
|
|
57
|
+
['/assets/', path.join(conf.workDir, 'public', 'assets')],
|
|
58
|
+
['/less/', path.join(conf.workDir, 'public', 'less')],
|
|
59
|
+
]) {
|
|
60
|
+
if (pathname.startsWith(prefix)) {
|
|
61
|
+
const rel = pathname.slice(prefix.length);
|
|
62
|
+
const safe = path.normalize(rel).replace(/^(\.\.[/\\])+/, '');
|
|
63
|
+
return serveFile(res, path.join(dir, safe));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// custom public overrides first
|
|
67
|
+
const customDir = path.join(conf.customDir, 'public');
|
|
68
|
+
if (fs.existsSync(customDir)) {
|
|
69
|
+
const rel = pathname.replace(/^\/+/, '');
|
|
70
|
+
const safe = path.normalize(rel).replace(/^(\.\.[/\\])+/, '');
|
|
71
|
+
if (safe && serveFile(res, path.join(customDir, safe)))
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
/** Serve the SPA shell (public/dist/index.html) with WebContext substitution. */
|
|
77
|
+
function serveGone(c) {
|
|
78
|
+
// 原版 gogs 网页界面已裁撤:dsh 原生 UI 覆盖全部功能,
|
|
79
|
+
// 3400 只保留 git HTTP 协议 + /api/v1 + /api/dsh(/api/web 登录面已随 SPA 裁撤)
|
|
80
|
+
c.res.statusCode = 404;
|
|
81
|
+
c.res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
82
|
+
c.res.end('web UI removed — use the dsh Git panel; git HTTP and APIs remain on this port');
|
|
83
|
+
}
|
|
84
|
+
let router;
|
|
85
|
+
export async function startServer() {
|
|
86
|
+
setServeWebHandler((c) => serveGone(c));
|
|
87
|
+
const { Router } = await import('./router.js');
|
|
88
|
+
router = new Router();
|
|
89
|
+
registerAPIRoutes(router);
|
|
90
|
+
const { registerDshRoutes } = await import('./dshapi.js');
|
|
91
|
+
registerDshRoutes(router);
|
|
92
|
+
const server = http.createServer(async (req, res) => {
|
|
93
|
+
try {
|
|
94
|
+
await handleRequest(req, res);
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
console.error('[server panic]', e?.stack ?? e);
|
|
98
|
+
if (!res.headersSent) {
|
|
99
|
+
res.statusCode = 500;
|
|
100
|
+
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
101
|
+
}
|
|
102
|
+
res.end('Internal server error');
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
return new Promise((resolve) => {
|
|
106
|
+
server.listen(conf.httpPort, conf.httpAddr, () => resolve(server));
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
async function handleRequest(req, res) {
|
|
110
|
+
const url = new URL(req.url ?? '/', 'http://internal');
|
|
111
|
+
let pathname = decodeURIComponent(url.pathname);
|
|
112
|
+
if (conf.subpath && pathname.startsWith(conf.subpath + '/')) {
|
|
113
|
+
pathname = pathname.slice(conf.subpath.length);
|
|
114
|
+
}
|
|
115
|
+
else if (conf.subpath && pathname === conf.subpath) {
|
|
116
|
+
pathname = '/';
|
|
117
|
+
}
|
|
118
|
+
// internal: /-/api/sanitize_ipynb (bluemonday-style ipynb HTML sanitizer)
|
|
119
|
+
if (pathname === '/-/api/sanitize_ipynb' && req.method === 'POST') {
|
|
120
|
+
const chunks = [];
|
|
121
|
+
for await (const chunk of req)
|
|
122
|
+
chunks.push(chunk);
|
|
123
|
+
const { sanitizeHTML } = await import('./markup.js');
|
|
124
|
+
res.statusCode = 200;
|
|
125
|
+
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
126
|
+
res.end(sanitizeHTML(Buffer.concat(chunks).toString('utf8')));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
// internal: /-/metrics (prometheus text format; gated by [prometheus] ENABLED)
|
|
130
|
+
if (pathname === '/-/metrics' && req.method === 'GET') {
|
|
131
|
+
if (!conf.prometheusEnabled) {
|
|
132
|
+
res.statusCode = 404;
|
|
133
|
+
res.end();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (conf.prometheusEnableBasicAuth) {
|
|
137
|
+
const expected = 'Basic ' + Buffer.from('gogsmetrics:gogsplant').toString('base64');
|
|
138
|
+
if (req.headers.authorization !== expected) {
|
|
139
|
+
res.statusCode = 401;
|
|
140
|
+
res.end();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const { renderMetrics } = await import('./metrics.js');
|
|
145
|
+
res.setHeader('Content-Type', 'text/plain; version=0.0.4; charset=utf-8');
|
|
146
|
+
res.end(renderMetrics());
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
// healthcheck
|
|
150
|
+
if (pathname === '/healthcheck' && (req.method === 'GET' || req.method === 'HEAD')) {
|
|
151
|
+
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
152
|
+
res.statusCode = 200;
|
|
153
|
+
res.end(req.method === 'HEAD' ? undefined : '* Database connection: OK\n');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
// redirect helper (flamego)
|
|
157
|
+
if (pathname === '/redirect') {
|
|
158
|
+
let to = url.searchParams.get('to') ?? '';
|
|
159
|
+
if (!to.startsWith('/') || to.startsWith('//'))
|
|
160
|
+
to = conf.subpath + '/';
|
|
161
|
+
res.statusCode = 303;
|
|
162
|
+
res.setHeader('Location', to);
|
|
163
|
+
res.end();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (pathname === '/robots.txt') {
|
|
167
|
+
const f = path.join(conf.customDir, 'robots.txt');
|
|
168
|
+
if (fs.existsSync(f)) {
|
|
169
|
+
serveFile(res, f, false);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
res.statusCode = 404;
|
|
173
|
+
res.end();
|
|
174
|
+
}
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
// git smart HTTP: /:username/:reponame(.git)(.wiki)/...
|
|
178
|
+
if (handleGitHTTP(req, res, pathname))
|
|
179
|
+
return;
|
|
180
|
+
const c = new Context(req, res);
|
|
181
|
+
const session = new Session(cookieValue(req, conf.cookieUserName));
|
|
182
|
+
c.init(session);
|
|
183
|
+
// flash cookie read
|
|
184
|
+
c.flash.readFromCookie(cookieValue(req, 'macaron_flash'));
|
|
185
|
+
// macaron writes session/flash cookies just before the response flushes
|
|
186
|
+
const origEnd = res.end.bind(res);
|
|
187
|
+
res.end = (...args) => {
|
|
188
|
+
if (!res.headersSent) {
|
|
189
|
+
const maxAge = 86400 * conf.loginRememberDays;
|
|
190
|
+
c.SetCookie(conf.cookieUserName, session.sid, maxAge);
|
|
191
|
+
if (c.flash.hasWrites()) {
|
|
192
|
+
c.SetCookie('macaron_flash', c.flash.encoded(), 0);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return origEnd(...args);
|
|
196
|
+
};
|
|
197
|
+
// global Contexter (i18n + session auth + common Data), like macaron global middleware
|
|
198
|
+
await Contexter()(c);
|
|
199
|
+
// API v1?
|
|
200
|
+
if (pathname === '/api' || pathname.startsWith('/api/v1') || pathname.startsWith('/api/v1/')) {
|
|
201
|
+
const match = router.match(req.method ?? 'GET', pathname);
|
|
202
|
+
if (match) {
|
|
203
|
+
c.params = match.params;
|
|
204
|
+
await runChain(match.route.handlers, c);
|
|
205
|
+
finalize(c);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
res.statusCode = 404;
|
|
209
|
+
res.end();
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
// captcha image (flamego/captcha equivalent)
|
|
213
|
+
if (pathname.startsWith('/captcha/')) {
|
|
214
|
+
const { newCaptcha } = await import('./toolx.js');
|
|
215
|
+
const { id, svg } = newCaptcha();
|
|
216
|
+
res.statusCode = 200;
|
|
217
|
+
res.setHeader('Content-Type', 'image/svg+xml');
|
|
218
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
219
|
+
res.setHeader('Set-Cookie', `gogs_captcha=${id}; Path=${conf.subpath || '/'}; HttpOnly`);
|
|
220
|
+
res.end(svg);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
// avatar endpoints
|
|
224
|
+
// avatar middleware: serve cached → download from gravatar source (once) → default
|
|
225
|
+
const avatarMatch = /^\/user\/avatar\/([0-9a-f]{32})$/.exec(pathname);
|
|
226
|
+
if (avatarMatch) {
|
|
227
|
+
const hash = avatarMatch[1];
|
|
228
|
+
const cached = path.join(conf.avatarUploadPath, hash);
|
|
229
|
+
const failMark = path.join(conf.avatarUploadPath, hash + '.failed');
|
|
230
|
+
const defaultPng = path.join(conf.workDir, 'public', 'img', 'avatar_default.png');
|
|
231
|
+
const serveDefault = () => {
|
|
232
|
+
res.setHeader('Cache-Control', 'public, max-age=86400');
|
|
233
|
+
serveFile(res, defaultPng, false);
|
|
234
|
+
};
|
|
235
|
+
if (fs.existsSync(cached)) {
|
|
236
|
+
res.setHeader('Cache-Control', 'public, max-age=86400');
|
|
237
|
+
res.setHeader('Content-Type', 'image/png');
|
|
238
|
+
fs.createReadStream(cached).pipe(res);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (conf.disableGravatar || fs.existsSync(failMark)) {
|
|
242
|
+
serveDefault();
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
// one-shot gravatar download with short timeout; negative-cached for 24h
|
|
246
|
+
try {
|
|
247
|
+
const buf = await downloadAvatar(conf.gravatarSource + hash + '?d=identicon');
|
|
248
|
+
fs.mkdirSync(conf.avatarUploadPath, { recursive: true });
|
|
249
|
+
fs.writeFileSync(cached, buf);
|
|
250
|
+
res.setHeader('Content-Type', 'image/png');
|
|
251
|
+
res.end(buf);
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
try {
|
|
255
|
+
fs.mkdirSync(conf.avatarUploadPath, { recursive: true });
|
|
256
|
+
fs.writeFileSync(failMark, '');
|
|
257
|
+
}
|
|
258
|
+
catch { }
|
|
259
|
+
serveDefault();
|
|
260
|
+
}
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const customAvatarMatch = /^\/user\/avatars\/(\d+)$/.exec(pathname);
|
|
264
|
+
if (customAvatarMatch) {
|
|
265
|
+
if (serveFile(res, path.join(conf.avatarUploadPath, customAvatarMatch[1]), false))
|
|
266
|
+
return;
|
|
267
|
+
res.statusCode = 302;
|
|
268
|
+
res.setHeader('Location', conf.subpath + '/img/avatar_default.png');
|
|
269
|
+
res.end();
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const repoAvatarMatch = /^\/repo-avatars\/([0-9a-f]{40})$/.exec(pathname);
|
|
273
|
+
if (repoAvatarMatch) {
|
|
274
|
+
if (serveFile(res, path.join(conf.repositoryAvatarUploadPath, repoAvatarMatch[1]), false))
|
|
275
|
+
return;
|
|
276
|
+
res.statusCode = 302;
|
|
277
|
+
res.setHeader('Location', conf.subpath + '/img/avatar_default.png');
|
|
278
|
+
res.end();
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
// go-get meta (gogs ServeGoGet): quick response regardless of repo existence
|
|
282
|
+
if (url.searchParams.get('go-get') === '1') {
|
|
283
|
+
const m = /^\/([^/]+)\/([^/]+)$/.exec(pathname);
|
|
284
|
+
if (m) {
|
|
285
|
+
const [, ownerName, repoName] = m;
|
|
286
|
+
let branchName = 'master';
|
|
287
|
+
const owner = (await import('./db/db.js')).getUserByUsername(ownerName);
|
|
288
|
+
const repo = owner ? (await import('./db/db.js')).getRepoByOwnerAndName(owner, repoName) : null;
|
|
289
|
+
if (repo && repo.default_branch)
|
|
290
|
+
branchName = repo.default_branch;
|
|
291
|
+
const prefix = conf.externalURL + [ownerName, repoName, 'src', branchName].join('/');
|
|
292
|
+
const goGetImport = conf.url.host + (conf.subpath || '') + '/' + ownerName + '/' + repoName;
|
|
293
|
+
const cloneLink = conf.externalURL + path.posix.join(ownerName, repoName) + '.git';
|
|
294
|
+
const insecureFlag = conf.externalURL.startsWith('https://') ? '' : '--insecure ';
|
|
295
|
+
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
296
|
+
res.statusCode = 200;
|
|
297
|
+
res.end(`<!doctype html>
|
|
298
|
+
<html>
|
|
299
|
+
\t<head>
|
|
300
|
+
\t\t<meta name="go-import" content="${goGetImport} git ${cloneLink}">
|
|
301
|
+
\t\t<meta name="go-source" content="${goGetImport} _ ${prefix}{/dir} ${prefix}{/dir}/{file}#L{line}">
|
|
302
|
+
\t</head>
|
|
303
|
+
\t<body>
|
|
304
|
+
\t\tgo get ${insecureFlag}${goGetImport}
|
|
305
|
+
\t</body>
|
|
306
|
+
</html>
|
|
307
|
+
`);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
// static assets — served BEFORE routes (macaron.Static ordering; otherwise
|
|
312
|
+
// /css/gogs.min.css would be captured by /:username/:reponame)
|
|
313
|
+
if (serveStaticPrefix(req, res, pathname))
|
|
314
|
+
return;
|
|
315
|
+
// SPA dist static files: vite base './' makes asset URLs relative to the
|
|
316
|
+
// page path (e.g. /user/sign-in → /user/assets/index-*.js), so any
|
|
317
|
+
// .../assets/<file> request resolves against public/dist/assets
|
|
318
|
+
const assetFile = /\/(?:[a-zA-Z0-9_.-]+\/)?assets\/([a-zA-Z0-9_.-]+)$/.exec(pathname);
|
|
319
|
+
if ((req.method === 'GET' || req.method === 'HEAD') && assetFile) {
|
|
320
|
+
const safe = path.normalize(assetFile[1]).replace(/^(\.\.\/)+/, '');
|
|
321
|
+
if (serveFile(res, path.join(conf.workDir, 'public', 'dist', 'assets', safe), false))
|
|
322
|
+
return;
|
|
323
|
+
res.statusCode = 404;
|
|
324
|
+
res.end();
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
// route table
|
|
328
|
+
const match = router.match(req.method ?? 'GET', pathname);
|
|
329
|
+
if (match) {
|
|
330
|
+
c.params = match.params;
|
|
331
|
+
await runChain(match.route.handlers, c);
|
|
332
|
+
finalize(c);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
// web UI removed (see serveGone)
|
|
336
|
+
if (req.method === 'GET' || req.method === 'HEAD') {
|
|
337
|
+
serveGone(c);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
res.statusCode = 404;
|
|
341
|
+
res.end();
|
|
342
|
+
}
|
|
343
|
+
function downloadAvatar(url) {
|
|
344
|
+
return new Promise((resolve, reject) => {
|
|
345
|
+
const mod = url.startsWith('https:') ? require('node:https') : require('node:http');
|
|
346
|
+
const req2 = mod.get(url, { timeout: 5000 }, (r) => {
|
|
347
|
+
if (r.statusCode !== 200) {
|
|
348
|
+
r.resume();
|
|
349
|
+
return reject(new Error('status ' + r.statusCode));
|
|
350
|
+
}
|
|
351
|
+
const chunks = [];
|
|
352
|
+
r.on('data', (d) => chunks.push(d));
|
|
353
|
+
r.on('end', () => resolve(Buffer.concat(chunks)));
|
|
354
|
+
});
|
|
355
|
+
req2.on('timeout', () => req2.destroy(new Error('timeout')));
|
|
356
|
+
req2.on('error', reject);
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
function cookieValue(req, name) {
|
|
360
|
+
const cookie = req.headers.cookie ?? '';
|
|
361
|
+
for (const part of cookie.split(';')) {
|
|
362
|
+
const idx = part.indexOf('=');
|
|
363
|
+
if (idx < 0)
|
|
364
|
+
continue;
|
|
365
|
+
if (part.slice(0, idx).trim() === name)
|
|
366
|
+
return decodeURIComponent(part.slice(idx + 1).trim());
|
|
367
|
+
}
|
|
368
|
+
return undefined;
|
|
369
|
+
}
|
|
370
|
+
async function runChain(handlers, c) {
|
|
371
|
+
for (const h of handlers) {
|
|
372
|
+
if (c.rendered)
|
|
373
|
+
return;
|
|
374
|
+
await h(c);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/** After handlers: release session back to the store. */
|
|
378
|
+
function finalize(c) {
|
|
379
|
+
c.session.Release();
|
|
380
|
+
}
|
|
381
|
+
export { serveFile };
|
|
382
|
+
//# sourceMappingURL=server.js.map
|