@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,701 @@
|
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
import { URLSearchParams } from 'node:url';
|
|
3
|
+
import { conf } from './conf.js';
|
|
4
|
+
import * as db from './db/db.js';
|
|
5
|
+
import { getRepoByName, accessMode, AccessMode } from './db/db.js';
|
|
6
|
+
import { basicAuthDecode } from './authx/password.js';
|
|
7
|
+
import * as umBridge from './authx/um.js';
|
|
8
|
+
import { getAccessTokenBySHA1, touchAccessToken } from './db/db.js';
|
|
9
|
+
import { verifyPassword } from './authx/password.js';
|
|
10
|
+
// ---------------------------------------------------------------- sessions
|
|
11
|
+
const sessions = new Map();
|
|
12
|
+
function newSessionID() {
|
|
13
|
+
return crypto.randomBytes(16).toString('hex');
|
|
14
|
+
}
|
|
15
|
+
export class Session {
|
|
16
|
+
sid;
|
|
17
|
+
data;
|
|
18
|
+
isNew;
|
|
19
|
+
maxAge;
|
|
20
|
+
constructor(sid) {
|
|
21
|
+
this.maxAge = 86400 * conf.loginRememberDays;
|
|
22
|
+
const now = Date.now();
|
|
23
|
+
if (sid && sessions.has(sid)) {
|
|
24
|
+
const s = sessions.get(sid);
|
|
25
|
+
if (s.expires > now) {
|
|
26
|
+
this.sid = sid;
|
|
27
|
+
this.data = s.data;
|
|
28
|
+
this.isNew = false;
|
|
29
|
+
s.expires = now + this.maxAge * 1000;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
sessions.delete(sid);
|
|
33
|
+
}
|
|
34
|
+
this.sid = newSessionID();
|
|
35
|
+
this.data = {};
|
|
36
|
+
this.isNew = true;
|
|
37
|
+
sessions.set(this.sid, { data: this.data, expires: now + this.maxAge * 1000 });
|
|
38
|
+
}
|
|
39
|
+
Get(key) {
|
|
40
|
+
return this.data[key];
|
|
41
|
+
}
|
|
42
|
+
Set(key, value) {
|
|
43
|
+
this.data[key] = value;
|
|
44
|
+
}
|
|
45
|
+
Delete(key) {
|
|
46
|
+
delete this.data[key];
|
|
47
|
+
}
|
|
48
|
+
Clear() {
|
|
49
|
+
this.data = {};
|
|
50
|
+
sessions.set(this.sid, { data: this.data, expires: Date.now() + this.maxAge * 1000 });
|
|
51
|
+
}
|
|
52
|
+
Release() {
|
|
53
|
+
sessions.set(this.sid, { data: this.data, expires: Date.now() + this.maxAge * 1000 });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// ---------------------------------------------------------------- flash
|
|
57
|
+
export class Flash {
|
|
58
|
+
SuccessMsg = '';
|
|
59
|
+
ErrorMsg = '';
|
|
60
|
+
WarningMsg = '';
|
|
61
|
+
InfoMsg = '';
|
|
62
|
+
values = {};
|
|
63
|
+
readFromCookie(cookie) {
|
|
64
|
+
if (!cookie)
|
|
65
|
+
return this;
|
|
66
|
+
const params = new URLSearchParams(cookie);
|
|
67
|
+
for (const key of ['error', 'warning', 'info', 'success']) {
|
|
68
|
+
const v = params.get(key);
|
|
69
|
+
if (v === null)
|
|
70
|
+
continue;
|
|
71
|
+
this.values[key] = v;
|
|
72
|
+
if (key === 'error')
|
|
73
|
+
this.ErrorMsg = v;
|
|
74
|
+
if (key === 'warning')
|
|
75
|
+
this.WarningMsg = v;
|
|
76
|
+
if (key === 'info')
|
|
77
|
+
this.InfoMsg = v;
|
|
78
|
+
if (key === 'success')
|
|
79
|
+
this.SuccessMsg = v;
|
|
80
|
+
}
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
Success(msg) { this.values['success'] = msg; }
|
|
84
|
+
Error(msg) { this.values['error'] = msg; }
|
|
85
|
+
Warning(msg) { this.values['warning'] = msg; }
|
|
86
|
+
Info(msg) { this.values['info'] = msg; }
|
|
87
|
+
hasWrites() {
|
|
88
|
+
return Object.keys(this.values).length > 0;
|
|
89
|
+
}
|
|
90
|
+
encoded() {
|
|
91
|
+
const params = new URLSearchParams();
|
|
92
|
+
for (const [k, v] of Object.entries(this.values))
|
|
93
|
+
params.set(k, v);
|
|
94
|
+
return params.toString();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// ---------------------------------------------------------------- context
|
|
98
|
+
export class Context {
|
|
99
|
+
req;
|
|
100
|
+
res;
|
|
101
|
+
params = {};
|
|
102
|
+
urlObj;
|
|
103
|
+
session;
|
|
104
|
+
lang = 'en-US';
|
|
105
|
+
Data = {};
|
|
106
|
+
flash = new Flash();
|
|
107
|
+
User = null;
|
|
108
|
+
isBasicAuth = false;
|
|
109
|
+
isTokenAuth = false;
|
|
110
|
+
/** repo assignment */
|
|
111
|
+
Repo = new RepoContext();
|
|
112
|
+
Org = {};
|
|
113
|
+
bodyBuffer = null;
|
|
114
|
+
formObj = null;
|
|
115
|
+
filesObj = null;
|
|
116
|
+
rendered = false;
|
|
117
|
+
pageStartTime = Date.now();
|
|
118
|
+
constructor(req, res) {
|
|
119
|
+
this.req = req;
|
|
120
|
+
this.res = res;
|
|
121
|
+
}
|
|
122
|
+
init(session) {
|
|
123
|
+
this.session = session;
|
|
124
|
+
this.urlObj = new URL(this.req.url ?? '/', 'http://internal');
|
|
125
|
+
this.Data['PageStartTime'] = new Date(this.pageStartTime);
|
|
126
|
+
this.Data['TmplLoadTimes'] = () => `${Date.now() - this.pageStartTime}ms`;
|
|
127
|
+
// security headers like gogs
|
|
128
|
+
this.res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
129
|
+
this.res.setHeader('X-Frame-Options', 'deny');
|
|
130
|
+
}
|
|
131
|
+
// -------------------------------------------------- request accessors
|
|
132
|
+
Method() {
|
|
133
|
+
return (this.req.method ?? 'GET').toUpperCase();
|
|
134
|
+
}
|
|
135
|
+
Path() {
|
|
136
|
+
return this.urlObj.pathname;
|
|
137
|
+
}
|
|
138
|
+
RequestURI() {
|
|
139
|
+
return this.urlObj.pathname + this.urlObj.search;
|
|
140
|
+
}
|
|
141
|
+
Query(name) {
|
|
142
|
+
return this.urlObj.searchParams.get(name) ?? '';
|
|
143
|
+
}
|
|
144
|
+
QueryInt(name) {
|
|
145
|
+
return Number(this.Query(name)) || 0;
|
|
146
|
+
}
|
|
147
|
+
Params(name) {
|
|
148
|
+
return this.params[name] ?? '';
|
|
149
|
+
}
|
|
150
|
+
ParamsInt64(name) {
|
|
151
|
+
return Number(this.params[name]) || 0;
|
|
152
|
+
}
|
|
153
|
+
header() {
|
|
154
|
+
return this.res;
|
|
155
|
+
}
|
|
156
|
+
Header() {
|
|
157
|
+
return this.res.getHeaders();
|
|
158
|
+
}
|
|
159
|
+
SetHeader(name, value) {
|
|
160
|
+
this.res.setHeader(name, value);
|
|
161
|
+
}
|
|
162
|
+
async body() {
|
|
163
|
+
if (this.bodyBuffer === null) {
|
|
164
|
+
const chunks = [];
|
|
165
|
+
for await (const chunk of this.req)
|
|
166
|
+
chunks.push(chunk);
|
|
167
|
+
this.bodyBuffer = Buffer.concat(chunks);
|
|
168
|
+
}
|
|
169
|
+
return this.bodyBuffer;
|
|
170
|
+
}
|
|
171
|
+
/** Parsed body: urlencoded, JSON, or multipart (fields merged; files in c.Files). */
|
|
172
|
+
async form() {
|
|
173
|
+
if (this.formObj)
|
|
174
|
+
return this.formObj;
|
|
175
|
+
const buf = await this.body();
|
|
176
|
+
const ctype = String(this.req.headers['content-type'] ?? '');
|
|
177
|
+
this.formObj = {};
|
|
178
|
+
if (ctype.includes('application/json')) {
|
|
179
|
+
try {
|
|
180
|
+
this.formObj = JSON.parse(buf.toString('utf8') || '{}');
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
this.formObj = {};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else if (ctype.includes('multipart/form-data')) {
|
|
187
|
+
const { parseMultipart } = await import('./multipart.js');
|
|
188
|
+
const { fields, files } = await parseMultipart(this.req, buf);
|
|
189
|
+
this.formObj = fields;
|
|
190
|
+
this.filesObj = files;
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
const params = new URLSearchParams(buf.toString('utf8'));
|
|
194
|
+
const obj = {};
|
|
195
|
+
for (const [k, v] of params.entries()) {
|
|
196
|
+
if (obj[k] === undefined)
|
|
197
|
+
obj[k] = v;
|
|
198
|
+
else if (Array.isArray(obj[k]))
|
|
199
|
+
obj[k].push(v);
|
|
200
|
+
else
|
|
201
|
+
obj[k] = [obj[k], v];
|
|
202
|
+
}
|
|
203
|
+
this.formObj = obj;
|
|
204
|
+
}
|
|
205
|
+
return this.formObj;
|
|
206
|
+
}
|
|
207
|
+
Files() {
|
|
208
|
+
return this.filesObj ?? [];
|
|
209
|
+
}
|
|
210
|
+
async FormString(name) {
|
|
211
|
+
const f = await this.form();
|
|
212
|
+
const v = f[name];
|
|
213
|
+
if (v === undefined || v === null)
|
|
214
|
+
return '';
|
|
215
|
+
return String(v);
|
|
216
|
+
}
|
|
217
|
+
GetCookie(name) {
|
|
218
|
+
const cookie = this.req.headers.cookie ?? '';
|
|
219
|
+
for (const part of cookie.split(';')) {
|
|
220
|
+
const idx = part.indexOf('=');
|
|
221
|
+
if (idx < 0)
|
|
222
|
+
continue;
|
|
223
|
+
if (part.slice(0, idx).trim() === name)
|
|
224
|
+
return decodeURIComponent(part.slice(idx + 1).trim());
|
|
225
|
+
}
|
|
226
|
+
return '';
|
|
227
|
+
}
|
|
228
|
+
SetCookie(name, value, maxAge, pathStr, httpOnly = true) {
|
|
229
|
+
const parts = [`${name}=${encodeURIComponent(value)}`];
|
|
230
|
+
parts.push(`Path=${pathStr ?? (conf.subpath || '/')}`);
|
|
231
|
+
if (maxAge > 0)
|
|
232
|
+
parts.push(`Max-Age=${maxAge}`);
|
|
233
|
+
if (maxAge === 0)
|
|
234
|
+
parts.push('Max-Age=0');
|
|
235
|
+
if (conf.sessionCookieSecure)
|
|
236
|
+
parts.push('Secure');
|
|
237
|
+
if (httpOnly)
|
|
238
|
+
parts.push('HttpOnly');
|
|
239
|
+
const prev = this.res.getHeader('Set-Cookie');
|
|
240
|
+
const arr = prev ? (Array.isArray(prev) ? prev.map(String) : [String(prev)]) : [];
|
|
241
|
+
arr.push(parts.join('; '));
|
|
242
|
+
this.res.setHeader('Set-Cookie', arr);
|
|
243
|
+
}
|
|
244
|
+
// -------------------------------------------------- response helpers
|
|
245
|
+
Status(code) {
|
|
246
|
+
this.res.statusCode = code;
|
|
247
|
+
}
|
|
248
|
+
JSON(status, obj) {
|
|
249
|
+
this.rendered = true;
|
|
250
|
+
this.res.statusCode = status;
|
|
251
|
+
this.res.setHeader('Content-Type', 'application/json;charset=utf-8');
|
|
252
|
+
this.res.end(JSON.stringify(obj ?? null));
|
|
253
|
+
}
|
|
254
|
+
JSONSuccess(obj) {
|
|
255
|
+
this.JSON(200, obj);
|
|
256
|
+
}
|
|
257
|
+
NoContent() {
|
|
258
|
+
this.rendered = true;
|
|
259
|
+
this.res.statusCode = 204;
|
|
260
|
+
this.res.end();
|
|
261
|
+
}
|
|
262
|
+
PlainText(status, text) {
|
|
263
|
+
this.rendered = true;
|
|
264
|
+
this.res.statusCode = status;
|
|
265
|
+
this.res.setHeader('Content-Type', 'text/plain;charset=utf-8');
|
|
266
|
+
this.res.end(text);
|
|
267
|
+
}
|
|
268
|
+
Redirect(location, status = 303) {
|
|
269
|
+
this.rendered = true;
|
|
270
|
+
this.res.statusCode = status;
|
|
271
|
+
this.res.setHeader('Location', escapePound(location));
|
|
272
|
+
this.res.end();
|
|
273
|
+
}
|
|
274
|
+
RedirectSubpath(location, status = 303) {
|
|
275
|
+
this.Redirect(conf.subpath + location, status);
|
|
276
|
+
}
|
|
277
|
+
Success(tmpl) {
|
|
278
|
+
this.HTML(200, tmpl);
|
|
279
|
+
}
|
|
280
|
+
// 网页 UI 已裁撤:模板渲染管线整体移除,HTML()/Success() 退化为纯文本兜底
|
|
281
|
+
//(仅死网页路由会调到;API 全走 c.JSON(),不会进这里)。
|
|
282
|
+
HTML(status, tmpl) {
|
|
283
|
+
this.rendered = true;
|
|
284
|
+
this.res.statusCode = status;
|
|
285
|
+
this.res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
286
|
+
this.res.end('web UI removed — use the dsh Git panel');
|
|
287
|
+
}
|
|
288
|
+
/** gogs 404: redirect to SPA shell — we render a simple 404 page (status/404 template missing in this version) */
|
|
289
|
+
NotFound() {
|
|
290
|
+
// gogs 404 hands the request to the React SPA shell with status 404
|
|
291
|
+
this.rendered = true;
|
|
292
|
+
if (serveWebHandler) {
|
|
293
|
+
serveWebHandler(this, 404);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
this.res.statusCode = 404;
|
|
297
|
+
this.res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
298
|
+
this.res.end('404 page not found');
|
|
299
|
+
}
|
|
300
|
+
/** Hand the request to the React SPA (c.ServeWeb in gogs). */
|
|
301
|
+
ServeWeb(statusCode = 200) {
|
|
302
|
+
this.rendered = true;
|
|
303
|
+
if (serveWebHandler) {
|
|
304
|
+
serveWebHandler(this, statusCode);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
this.res.statusCode = statusCode;
|
|
308
|
+
this.res.end();
|
|
309
|
+
}
|
|
310
|
+
Error(err, msg) {
|
|
311
|
+
console.error(`[ctx error] ${msg}: ${err?.stack ?? err}`);
|
|
312
|
+
// 500 改纯文本提示(模板与 i18n 已随网页 UI / /api/web 裁撤,标题硬编码英文)。
|
|
313
|
+
const title = 'Internal Server Error';
|
|
314
|
+
const showDetail = !conf.isProdMode() || this.User?.is_admin === 1;
|
|
315
|
+
const detail = showDetail ? `\n${msg}: ${String(err?.message ?? err)}` : '';
|
|
316
|
+
this.rendered = true;
|
|
317
|
+
this.res.statusCode = 500;
|
|
318
|
+
this.res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
319
|
+
this.res.end(title + detail);
|
|
320
|
+
}
|
|
321
|
+
NotFoundOrError(err, msg) {
|
|
322
|
+
if (err instanceof db.NotFoundError || err?.notFound) {
|
|
323
|
+
this.NotFound();
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
this.Error(err, msg);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
HasError() {
|
|
330
|
+
return !!this.Data['HasError'];
|
|
331
|
+
}
|
|
332
|
+
FormErr(names) {
|
|
333
|
+
for (const n of names)
|
|
334
|
+
this.Data['Err_' + n] = true;
|
|
335
|
+
}
|
|
336
|
+
RawTitle(s) {
|
|
337
|
+
this.Data['Title'] = s;
|
|
338
|
+
}
|
|
339
|
+
PageIs(name) {
|
|
340
|
+
this.Data['PageIs' + name] = true;
|
|
341
|
+
}
|
|
342
|
+
Require(name) {
|
|
343
|
+
this.Data['Require' + name] = true;
|
|
344
|
+
}
|
|
345
|
+
async RenderWithErr(msg, tpl, form) {
|
|
346
|
+
if (form)
|
|
347
|
+
this.Data['Form'] = form;
|
|
348
|
+
this.Data['HasError'] = true;
|
|
349
|
+
this.Data['ErrorMsg'] = msg;
|
|
350
|
+
this.Data['Flash'] = this.flash;
|
|
351
|
+
this.Success(tpl);
|
|
352
|
+
}
|
|
353
|
+
UserID() {
|
|
354
|
+
return this.User?.id ?? 0;
|
|
355
|
+
}
|
|
356
|
+
// auth flags
|
|
357
|
+
get IsLogged() {
|
|
358
|
+
return this.User !== null;
|
|
359
|
+
}
|
|
360
|
+
/** link of current request path (EscapePound'ed), like gogs Contexter */
|
|
361
|
+
get Link() {
|
|
362
|
+
return conf.subpath + this.Path().replace(/\/$/, '');
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
export class RepoContext {
|
|
366
|
+
Repository = null;
|
|
367
|
+
Owner = null;
|
|
368
|
+
AccessMode = AccessMode.NONE;
|
|
369
|
+
Commit = null;
|
|
370
|
+
CommitID = '';
|
|
371
|
+
BranchName = '';
|
|
372
|
+
TagName = '';
|
|
373
|
+
TreePath = '';
|
|
374
|
+
IsViewBranch = false;
|
|
375
|
+
IsViewTag = false;
|
|
376
|
+
IsViewCommit = false;
|
|
377
|
+
PullRequest = { BaseRepo: null, Allowed: false, SameRepo: false, HeadInfo: '' };
|
|
378
|
+
GitRepoDir = '';
|
|
379
|
+
IsOwner() {
|
|
380
|
+
return this.AccessMode >= AccessMode.OWNER;
|
|
381
|
+
}
|
|
382
|
+
IsAdmin() {
|
|
383
|
+
return this.AccessMode >= AccessMode.ADMIN;
|
|
384
|
+
}
|
|
385
|
+
IsWriter() {
|
|
386
|
+
return this.AccessMode >= AccessMode.WRITE;
|
|
387
|
+
}
|
|
388
|
+
HasAccess() {
|
|
389
|
+
return this.AccessMode >= AccessMode.READ;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/** Injected by server.ts so context can render the SPA shell. */
|
|
393
|
+
let serveWebHandler = null;
|
|
394
|
+
export function setServeWebHandler(fn) {
|
|
395
|
+
serveWebHandler = fn;
|
|
396
|
+
}
|
|
397
|
+
// ---------------------------------------------------------------- helpers
|
|
398
|
+
export function escapePound(str) {
|
|
399
|
+
return String(str).replaceAll('%', '%25').replaceAll('#', '%23').replaceAll(' ', '%20').replaceAll('?', '%3F');
|
|
400
|
+
}
|
|
401
|
+
// ---------------------------------------------------------------- auth
|
|
402
|
+
/**
|
|
403
|
+
* Basic 认证(异步):本地兜底账号 → user-management service(登录校验
|
|
404
|
+
* 直接走 UM store 的 checkLogin,影子账号只承载身份/所有权,密码永不在
|
|
405
|
+
* 本库校验)→ access token。
|
|
406
|
+
*/
|
|
407
|
+
export async function authenticateUserByBasic(header) {
|
|
408
|
+
const parts = header.split(' ');
|
|
409
|
+
if (parts.length !== 2 || parts[0] !== 'Basic')
|
|
410
|
+
return null;
|
|
411
|
+
const [uname, passwd] = basicAuthDecode(parts[1]);
|
|
412
|
+
const user = db.getUserByUsername(uname);
|
|
413
|
+
// 兜底管理员(插件自有账号,DSH_BOOTSTRAP_ADMIN):永远本地校验——
|
|
414
|
+
// 宿主用它铸个人令牌/紧急登录,它不是 UM 用户,不能被 UM-first 拦下
|
|
415
|
+
const bootstrapName = (process.env.DSH_BOOTSTRAP_ADMIN || '').split(':')[0] || 'root';
|
|
416
|
+
if (user && user.name === bootstrapName && verifyPassword(passwd, user.salt, user.passwd)) {
|
|
417
|
+
return { user, isBasic: true };
|
|
418
|
+
}
|
|
419
|
+
// 账户单一来源:UM service 启用时认证只走 service;本地密码仅当
|
|
420
|
+
// service 不可用(模块/用户库缺失)时作紧急兜底(兜底管理员 root 等)
|
|
421
|
+
if (umBridge.umServiceEnabled()) {
|
|
422
|
+
const check = await umBridge.umCheckLogin(uname, passwd);
|
|
423
|
+
if (check.ok) {
|
|
424
|
+
const shadow = umBridge.ensureShadowUser(check.user);
|
|
425
|
+
if (shadow)
|
|
426
|
+
return { user: shadow, isBasic: true };
|
|
427
|
+
}
|
|
428
|
+
if (!check.unavailable) {
|
|
429
|
+
// service 明确拒绝(错密码/禁用/TOTP)——不落本地校验,防旧同步密码绕过
|
|
430
|
+
const token = getAccessTokenBySHA1(uname) ?? getAccessTokenBySHA1(passwd);
|
|
431
|
+
if (token) {
|
|
432
|
+
const tu = db.getUserByID(token.uid);
|
|
433
|
+
if (tu)
|
|
434
|
+
return { user: tu, isBasic: true };
|
|
435
|
+
}
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
if (user && verifyPassword(passwd, user.salt, user.passwd)) {
|
|
440
|
+
return { user, isBasic: true };
|
|
441
|
+
}
|
|
442
|
+
// try token in either field
|
|
443
|
+
const token = getAccessTokenBySHA1(uname) ?? getAccessTokenBySHA1(passwd);
|
|
444
|
+
if (token) {
|
|
445
|
+
const tu = db.getUserByID(token.uid);
|
|
446
|
+
if (tu)
|
|
447
|
+
return { user: tu, isBasic: true };
|
|
448
|
+
}
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
export function authenticateUserByToken(sha1) {
|
|
452
|
+
const token = getAccessTokenBySHA1(sha1);
|
|
453
|
+
if (!token)
|
|
454
|
+
return null;
|
|
455
|
+
touchAccessToken(token.id);
|
|
456
|
+
return db.getUserByID(token.uid);
|
|
457
|
+
}
|
|
458
|
+
// ---------------------------------------------------------------- middleware factories
|
|
459
|
+
export function Contexter() {
|
|
460
|
+
return async (c) => {
|
|
461
|
+
// session auth
|
|
462
|
+
const uid = c.session.Get('uid');
|
|
463
|
+
if (uid > 0) {
|
|
464
|
+
c.User = db.getUserByID(uid);
|
|
465
|
+
if (c.User && c.User.prohibit_login === 1)
|
|
466
|
+
c.User = null;
|
|
467
|
+
}
|
|
468
|
+
// link + common data
|
|
469
|
+
c.Data['Link'] = escapePound(c.Link);
|
|
470
|
+
c.Data['IsLogged'] = c.IsLogged;
|
|
471
|
+
c.Data['LoggedUser'] = c.User;
|
|
472
|
+
c.Data['LoggedUserID'] = c.UserID();
|
|
473
|
+
c.Data['LoggedUserName'] = c.User?.name ?? '';
|
|
474
|
+
c.Data['IsAdmin'] = c.User?.is_admin === 1;
|
|
475
|
+
c.Data['ShowRegistrationButton'] = !conf.disableRegistration;
|
|
476
|
+
// server notice banner 已随网页 UI 裁撤移除(原消费方是网页模板,无活读者)。
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
/** Toggle middleware mirroring gogs context.Toggle. */
|
|
480
|
+
export function Toggle(opts) {
|
|
481
|
+
return (c) => {
|
|
482
|
+
if (c.IsLogged && c.User.prohibit_login === 1) {
|
|
483
|
+
c.Data['Title'] = 'Login Prohibited';
|
|
484
|
+
c.Success('user/auth/prohibit_login');
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
if (!c.IsLogged && c.RequestURI() === '/' && conf.landingURL !== '/') {
|
|
488
|
+
c.RedirectSubpath(conf.landingURL);
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
if (opts.SignOutRequired && c.IsLogged && c.RequestURI() !== '/') {
|
|
492
|
+
c.RedirectSubpath('/');
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
if (opts.SignInRequired) {
|
|
496
|
+
if (!c.IsLogged) {
|
|
497
|
+
if (c.Path().startsWith('/api/')) {
|
|
498
|
+
c.JSON(403, { message: 'Only authenticated user is allowed to call APIs.' });
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
c.SetCookie('redirect_to', encodeURIComponent(conf.subpath + c.RequestURI()), 0);
|
|
502
|
+
c.RedirectSubpath('/user/sign-in');
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
else if (!c.User.is_active && conf.requireEmailConfirmation) {
|
|
506
|
+
c.RedirectSubpath('/user/activate');
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (opts.AdminRequired) {
|
|
511
|
+
if (c.User?.is_admin !== 1) {
|
|
512
|
+
c.Status(403);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
c.PageIs('Admin');
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
export const reqSignIn = Toggle({ SignInRequired: true });
|
|
520
|
+
export const ignSignIn = Toggle({ SignInRequired: conf.requireSigninView });
|
|
521
|
+
export const reqSignOut = Toggle({ SignOutRequired: true });
|
|
522
|
+
export const reqAdmin = Toggle({ SignInRequired: true, AdminRequired: true });
|
|
523
|
+
/** RepoAssignment middleware (args: mustBeNotBare) */
|
|
524
|
+
export function RepoAssignment(mustBeNotBare = false) {
|
|
525
|
+
return (c) => {
|
|
526
|
+
const username = c.Params(':username');
|
|
527
|
+
const reponame = c.Params(':reponame');
|
|
528
|
+
const owner = db.getUserByUsername(username);
|
|
529
|
+
if (!owner) {
|
|
530
|
+
c.NotFound();
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
const repo = getRepoByName(username, reponame);
|
|
534
|
+
if (!repo) {
|
|
535
|
+
c.NotFound();
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
c.Repo.Owner = owner;
|
|
539
|
+
c.Repo.Repository = repo;
|
|
540
|
+
c.Repo.AccessMode = accessMode(c.UserID(), repo);
|
|
541
|
+
if (mustBeNotBare && repo.is_bare) {
|
|
542
|
+
c.NotFound();
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
c.Data['Username'] = username;
|
|
546
|
+
c.Data['Reponame'] = reponame;
|
|
547
|
+
c.Data['IsBareRepo'] = repo.is_bare === 1;
|
|
548
|
+
c.Data['RepoLink'] = conf.subpath + '/' + repo.FullName();
|
|
549
|
+
c.Data['RepoRelPath'] = repo.FullName();
|
|
550
|
+
c.RawTitle(repo.FullName());
|
|
551
|
+
c.Data['Repository'] = repo;
|
|
552
|
+
c.Data['Owner'] = repo.owner;
|
|
553
|
+
c.Data['IsRepositoryOwner'] = c.Repo.IsOwner();
|
|
554
|
+
c.Data['IsRepositoryAdmin'] = c.Repo.IsAdmin();
|
|
555
|
+
c.Data['IsRepositoryWriter'] = c.Repo.IsWriter();
|
|
556
|
+
c.Data['DisableSSH'] = conf.disableSSH;
|
|
557
|
+
c.Data['DisableHTTP'] = conf.disableHTTPGit;
|
|
558
|
+
c.Data['CloneLink'] = {
|
|
559
|
+
HTTPS: repo.CloneURL(),
|
|
560
|
+
SSH: `${conf.sshDomain === 'localhost' ? conf.domain : conf.sshDomain}:${owner.name === conf.runUser ? conf.sshPort : 22}/${repo.FullName()}.git`,
|
|
561
|
+
Git: `git://${conf.domain}/${repo.FullName()}.git`,
|
|
562
|
+
};
|
|
563
|
+
c.Data['WikiCloneLink'] = {
|
|
564
|
+
HTTPS: conf.externalURL + repo.FullName() + '.wiki.git',
|
|
565
|
+
SSH: '',
|
|
566
|
+
Git: '',
|
|
567
|
+
};
|
|
568
|
+
if (c.IsLogged) {
|
|
569
|
+
c.Data['IsWatchingRepo'] = db.isWatching(c.UserID(), repo.id);
|
|
570
|
+
c.Data['IsStaringRepo'] = db.isStaring(c.UserID(), repo.id);
|
|
571
|
+
}
|
|
572
|
+
c.Data['IsGuest'] = !c.Repo.HasAccess();
|
|
573
|
+
if (!repo.is_bare) {
|
|
574
|
+
c.Repo.BranchName = repo.default_branch || conf.defaultBranch;
|
|
575
|
+
c.Data['BranchName'] = c.Repo.BranchName;
|
|
576
|
+
c.Data['Branches'] = [];
|
|
577
|
+
c.Data['BranchCount'] = 0;
|
|
578
|
+
}
|
|
579
|
+
if (repo.is_mirror) {
|
|
580
|
+
c.Data['MirrorInterval'] = '';
|
|
581
|
+
c.Data['MirrorEnablePrune'] = true;
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
export function RequireRepoAdmin() {
|
|
586
|
+
return (c) => {
|
|
587
|
+
if (!c.Repo.IsAdmin()) {
|
|
588
|
+
c.NotFound();
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
export function RequireRepoWriter() {
|
|
593
|
+
return (c) => {
|
|
594
|
+
if (!c.Repo.IsWriter()) {
|
|
595
|
+
c.NotFound();
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
/** RepoRef middleware — resolve ref from "*" param or default branch. */
|
|
600
|
+
export function RepoRef() {
|
|
601
|
+
return async (c) => {
|
|
602
|
+
const repo = c.Repo.Repository;
|
|
603
|
+
if (repo.is_bare)
|
|
604
|
+
return;
|
|
605
|
+
const repoDir = repo.RepoPath();
|
|
606
|
+
c.Repo.GitRepoDir = repoDir;
|
|
607
|
+
const { getCommit, getBranches, refExists, catFileCommit } = await import('./gitx/git.js');
|
|
608
|
+
let refName = '';
|
|
609
|
+
let treePath = '';
|
|
610
|
+
const wildcard = c.Params(':*');
|
|
611
|
+
if (wildcard === '') {
|
|
612
|
+
refName = repo.default_branch || conf.defaultBranch;
|
|
613
|
+
if (!(await refExists(repoDir, 'refs/heads/' + refName))) {
|
|
614
|
+
const branches = await getBranches(repoDir);
|
|
615
|
+
refName = branches[0]?.name ?? refName;
|
|
616
|
+
}
|
|
617
|
+
c.Repo.Commit = await getCommit(repoDir, refName);
|
|
618
|
+
c.Repo.CommitID = c.Repo.Commit?.id ?? '';
|
|
619
|
+
c.Repo.IsViewBranch = true;
|
|
620
|
+
}
|
|
621
|
+
else {
|
|
622
|
+
const parts = wildcard.split('/');
|
|
623
|
+
let hasMatched = false;
|
|
624
|
+
let acc = '';
|
|
625
|
+
for (let i = 0; i < parts.length; i++) {
|
|
626
|
+
acc = (acc ? acc + '/' : '') + parts[i];
|
|
627
|
+
if ((await refExists(repoDir, 'refs/heads/' + acc)) ||
|
|
628
|
+
(await refExists(repoDir, 'refs/tags/' + acc))) {
|
|
629
|
+
if (i < parts.length - 1)
|
|
630
|
+
treePath = parts.slice(i + 1).join('/');
|
|
631
|
+
refName = acc;
|
|
632
|
+
hasMatched = true;
|
|
633
|
+
break;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
if (!hasMatched && parts[0].length === 40) {
|
|
637
|
+
refName = parts[0];
|
|
638
|
+
treePath = parts.slice(1).join('/');
|
|
639
|
+
}
|
|
640
|
+
if (!refName) {
|
|
641
|
+
c.NotFound();
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
if (await refExists(repoDir, 'refs/heads/' + refName)) {
|
|
645
|
+
c.Repo.IsViewBranch = true;
|
|
646
|
+
c.Repo.Commit = await getCommit(repoDir, refName);
|
|
647
|
+
c.Repo.CommitID = c.Repo.Commit?.id ?? '';
|
|
648
|
+
}
|
|
649
|
+
else if (await refExists(repoDir, 'refs/tags/' + refName)) {
|
|
650
|
+
c.Repo.IsViewTag = true;
|
|
651
|
+
c.Repo.Commit = await getCommit(repoDir, refName + '^{commit}');
|
|
652
|
+
c.Repo.CommitID = c.Repo.Commit?.id ?? '';
|
|
653
|
+
}
|
|
654
|
+
else if (refName.length === 40) {
|
|
655
|
+
c.Repo.IsViewCommit = true;
|
|
656
|
+
c.Repo.CommitID = refName;
|
|
657
|
+
c.Repo.Commit = await catFileCommit(repoDir, refName).catch(() => null);
|
|
658
|
+
if (!c.Repo.Commit) {
|
|
659
|
+
c.NotFound();
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
else {
|
|
664
|
+
c.NotFound();
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
c.Repo.BranchName = refName;
|
|
669
|
+
c.Repo.TreePath = treePath;
|
|
670
|
+
c.Data['BranchName'] = refName;
|
|
671
|
+
c.Data['CommitID'] = c.Repo.CommitID;
|
|
672
|
+
c.Data['TreePath'] = treePath;
|
|
673
|
+
c.Data['IsViewBranch'] = c.Repo.IsViewBranch;
|
|
674
|
+
c.Data['IsViewTag'] = c.Repo.IsViewTag;
|
|
675
|
+
c.Data['IsViewCommit'] = c.Repo.IsViewCommit;
|
|
676
|
+
// pull request context
|
|
677
|
+
if (c.Repo.IsWriter() || repo.enable_pulls) {
|
|
678
|
+
c.Repo.PullRequest.Allowed = c.Repo.IsWriter();
|
|
679
|
+
c.Data['PullRequestCtx'] = c.Repo.PullRequest;
|
|
680
|
+
if (c.Repo.IsWriter()) {
|
|
681
|
+
c.Data['BaseRepo'] = repo;
|
|
682
|
+
c.Repo.PullRequest.BaseRepo = repo;
|
|
683
|
+
c.Repo.PullRequest.SameRepo = true;
|
|
684
|
+
c.Repo.PullRequest.HeadInfo = (c.Repo.Owner?.name ?? '') + ':' + refName;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
/** InjectParamsUser middleware: resolves :username to c.Data ContextUser. */
|
|
690
|
+
export function InjectParamsUser() {
|
|
691
|
+
return (c) => {
|
|
692
|
+
const user = db.getUserByUsername(c.Params(':username'));
|
|
693
|
+
if (user === null) {
|
|
694
|
+
c.NotFound();
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
c.Data['ContextUser'] = user;
|
|
698
|
+
c.ContextUser = user;
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
//# sourceMappingURL=context.js.map
|