hfs 0.49.0 → 0.49.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hfs",
3
- "version": "0.49.0",
3
+ "version": "0.49.2",
4
4
  "description": "HTTP File Server",
5
5
  "keywords": [
6
6
  "file server",
package/src/acme.js CHANGED
@@ -21,7 +21,7 @@ const acmeListener = (req, res) => {
21
21
  var _a;
22
22
  const BASE = '/.well-known/acme-challenge/';
23
23
  if (!((_a = req.url) === null || _a === void 0 ? void 0 : _a.startsWith(BASE)))
24
- return;
24
+ return res.end('HFS'); // to satisfy self-check with the temporary server
25
25
  const token = req.url.slice(BASE.length);
26
26
  console.debug("got http challenge", token);
27
27
  res.statusCode = cross_1.HTTP_OK;
@@ -35,23 +35,23 @@ const acmeMiddleware = (ctx, next) => {
35
35
  exports.acmeMiddleware = acmeMiddleware;
36
36
  async function generateSSLCert(domain, email) {
37
37
  // will answer challenge through our koa app (if on port 80) or must we spawn a dedicated server?
38
- const { upnp, externalPort } = await (0, nat_1.getNatInfo)();
38
+ const nat = await (0, nat_1.getNatInfo)();
39
39
  const { http } = await (0, listen_1.getServerStatus)();
40
- const tempSrv = externalPort === 80 || http.listening && http.port === 80 ? undefined : (0, http_1.createServer)(acmeListener);
40
+ const tempSrv = nat.externalPort === 80 || http.listening && http.port === 80 ? undefined : (0, http_1.createServer)(acmeListener);
41
41
  if (tempSrv)
42
- await new Promise((resolve) => tempSrv.listen(80, resolve).on('error', (e) => {
42
+ await new Promise(resolve => tempSrv.listen(80, resolve).on('error', (e) => {
43
43
  console.debug("cannot listen on 80", e.code || e);
44
44
  resolve(); // go on anyway
45
45
  }));
46
46
  acmeMiddlewareEnabled = true;
47
47
  console.debug('acme challenge server ready');
48
+ let tempMap;
48
49
  try {
49
50
  const checkUrl = `http://${domain}`;
50
51
  let check = await (0, selfCheck_1.selfCheck)(checkUrl); // some check services may not consider the domain, but we already verified that
51
- if (check && !check.success && upnp && externalPort !== 80) { // consider a short-lived mapping
52
+ if (check && !check.success && nat.upnp && !nat.mapped80) {
52
53
  console.debug("setting temporary port forward");
53
- // @ts-ignore
54
- await nat_1.upnpClient.createMapping({ private: 80, public: { host: '', port: 80 }, description: 'hfs temporary', ttl: 30 }).catch(() => { });
54
+ tempMap = await nat_1.upnpClient.createMapping({ private: 80, public: { host: '', port: 80 }, description: 'hfs temporary', ttl: 0 }).catch(() => { });
55
55
  check = await (0, selfCheck_1.selfCheck)(checkUrl); // repeat test
56
56
  }
57
57
  //if (!check) throw new ApiError(HTTP_FAILED_DEPENDENCY, "couldn't test port 80")
@@ -61,6 +61,7 @@ async function generateSSLCert(domain, email) {
61
61
  accountKey: await acme_client_1.default.crypto.createPrivateKey(),
62
62
  directoryUrl: acme_client_1.default.directory.letsencrypt.production
63
63
  });
64
+ acme_client_1.default.setLogger(console.debug);
64
65
  const [key, csr] = await acme_client_1.default.crypto.createCsr({ commonName: domain });
65
66
  const cert = await acmeClient.auto({
66
67
  csr,
@@ -68,17 +69,16 @@ async function generateSSLCert(domain, email) {
68
69
  challengePriority: ['http-01'],
69
70
  skipChallengeVerification: true,
70
71
  termsOfServiceAgreed: true,
71
- async challengeCreateFn(_, c, ka) {
72
- console.debug("producing challenge");
73
- acmeTokens[c.token] = ka;
74
- },
75
- async challengeRemoveFn(_, c) {
76
- delete acmeTokens[c.token];
77
- },
72
+ async challengeCreateFn(_, c, ka) { acmeTokens[c.token] = ka; },
73
+ async challengeRemoveFn(_, c) { delete acmeTokens[c.token]; },
78
74
  });
79
75
  return { key, cert };
80
76
  }
81
77
  finally {
78
+ if (tempMap) {
79
+ console.debug("removing temporary port forward");
80
+ nat_1.upnpClient.removeMapping({ public: { host: '', port: 80 } }).catch(() => { });
81
+ }
82
82
  acmeMiddlewareEnabled = false;
83
83
  if (tempSrv)
84
84
  await new Promise(res => tempSrv.close(res));
package/src/adminApis.js CHANGED
@@ -113,7 +113,7 @@ exports.adminApis = {
113
113
  version: const_1.VERSION,
114
114
  apiVersion: const_1.API_VERSION,
115
115
  compatibleApiVersion: const_1.COMPATIBLE_API_VERSION,
116
- ...await (0, listen_1.getServerStatus)(),
116
+ ...await (0, listen_1.getServerStatus)(false),
117
117
  platform: process.platform,
118
118
  urls: await (0, listen_1.getUrls)(),
119
119
  ips: await (0, listen_1.getIps)(false),
package/src/block.js CHANGED
@@ -10,7 +10,7 @@ const block = (0, config_1.defineConfig)('block', [], rules => {
10
10
  const ret = !Array.isArray(rules) ? []
11
11
  : (0, misc_1.onlyTruthy)(rules.map(rule => {
12
12
  rule.expire && (rule.expire = new Date(rule.expire));
13
- return !(rule.expire > now) && (0, misc_1.makeNetMatcher)(rule.ip, true);
13
+ return !(rule.expire > now) && (0, misc_1.makeNetMatcher)(rule.ip);
14
14
  }));
15
15
  // reapply new block to existing connections
16
16
  for (const { socket, ip } of (0, connections_1.getConnections)())
package/src/const.js CHANGED
@@ -42,7 +42,7 @@ exports.DEV = process.env.DEV || exports.argv.dev ? 'DEV' : '';
42
42
  exports.ORIGINAL_CWD = process.cwd();
43
43
  exports.HFS_STARTED = new Date();
44
44
  const PKG_PATH = (0, path_1.join)(__dirname, '..', 'package.json');
45
- exports.BUILD_TIMESTAMP = "2023-11-01T11:03:50.804Z";
45
+ exports.BUILD_TIMESTAMP = "2023-11-06T09:23:00.007Z";
46
46
  const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
47
47
  exports.VERSION = pkg.version;
48
48
  exports.RUNNING_BETA = exports.VERSION.includes('-');
package/src/cross.js CHANGED
@@ -18,7 +18,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
18
18
  };
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
20
  exports.isWindowsDrive = exports.isIP = exports.isPrimitive = exports.formatTimestamp = exports.repeat = exports.asyncGeneratorToArray = exports.filterMapGenerator = exports.throw_ = exports.hasProp = exports.typedEntries = exports.typedKeys = exports.objRenameKey = exports.randomId = exports.getOrSet = exports.waitFor = exports.newObj = exports.removeStarting = exports.findDefined = exports.isOrderedEqual = exports.swap = exports.tryJson = exports.basename = exports.pendingPromise = exports._dbg = exports._log = exports.wantArray = exports.formatPerc = exports.with_ = exports.try_ = exports.setHidden = exports.onlyTruthy = exports.truthy = exports.enforceFinal = exports.objSameKeys = exports.haveTimeout = exports.wait = exports.prefix = exports.formatBytes = exports.isWhoObject = exports.PERM_KEYS = exports.defaultPerms = exports.WHO_ANY_ACCOUNT = exports.WHO_NO_ONE = exports.WHO_ANYONE = exports.MAX_TILES_SIZE = exports.DAY = exports.HOUR = exports.MINUTE = exports.WIKI_URL = exports.REPO_URL = void 0;
21
- exports.promiseBestEffort = exports.escapeHTML = exports.ipForUrl = exports.ipLocalHost = exports.xlate = exports.isEqualLax = void 0;
21
+ exports.runAt = exports.promiseBestEffort = exports.escapeHTML = exports.ipForUrl = exports.ipLocalHost = exports.xlate = exports.isEqualLax = void 0;
22
22
  // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
23
23
  // all content here is shared between client and server
24
24
  const lodash_1 = __importDefault(require("lodash"));
@@ -312,3 +312,25 @@ async function promiseBestEffort(promises) {
312
312
  return res.filter(x => x.status === 'fulfilled').map((x) => x.value);
313
313
  }
314
314
  exports.promiseBestEffort = promiseBestEffort;
315
+ function runAt(ts, cb) {
316
+ let cancel = false;
317
+ let t;
318
+ setTimeout(async () => {
319
+ if (missing() < 0)
320
+ return;
321
+ const max = 0x7FFFFFFF;
322
+ while (!cancel && missing() > max)
323
+ await wait(max);
324
+ if (cancel)
325
+ return;
326
+ t = setTimeout(cb, missing());
327
+ function missing() {
328
+ return ts - Date.now();
329
+ }
330
+ });
331
+ return () => {
332
+ cancel = true;
333
+ clearTimeout(t);
334
+ };
335
+ }
336
+ exports.runAt = runAt;
package/src/listen.js CHANGED
@@ -118,9 +118,20 @@ const considerHttps = (0, misc_1.debounceAsync)(async () => {
118
118
  if (cn)
119
119
  console.log("certificate loaded for", cn);
120
120
  const now = new Date();
121
- httpsSrv.error = new Date(cert.validFrom) > now ? "certificate not valid yet"
122
- : new Date(cert.validTo) < now ? "certificate expired"
123
- : undefined;
121
+ const from = new Date(cert.validFrom);
122
+ const to = new Date(cert.validTo);
123
+ updateError(); // error will change at from and to dates of the certificate
124
+ const cancelTo = (0, misc_1.runAt)(to.getTime(), updateError);
125
+ const cancelFrom = (0, misc_1.runAt)(from.getTime(), updateError);
126
+ httpsSrv.on('close', () => {
127
+ cancelTo();
128
+ cancelFrom();
129
+ });
130
+ function updateError() {
131
+ if (!httpsSrv)
132
+ return;
133
+ httpsSrv.error = from > now ? "certificate not valid yet" : to < now ? "certificate expired" : undefined;
134
+ }
124
135
  const namesForOutput = { cert: 'certificate', private_key: 'private key' };
125
136
  const missing = (_c = httpsNeeds.find(x => !x.get())) === null || _c === void 0 ? void 0 : _c.key();
126
137
  if (missing)
@@ -247,7 +258,7 @@ function getCurrentPort(srv) {
247
258
  var _a;
248
259
  return (_a = srv === null || srv === void 0 ? void 0 : srv.address()) === null || _a === void 0 ? void 0 : _a.port;
249
260
  }
250
- async function getServerStatus() {
261
+ async function getServerStatus(includeSrv = true) {
251
262
  return {
252
263
  http: await serverStatus(httpSrv, exports.portCfg.get()),
253
264
  https: await serverStatus(httpsSrv, exports.httpsPortCfg.get()),
@@ -260,7 +271,7 @@ async function getServerStatus() {
260
271
  busy,
261
272
  port: getCurrentPort(srv) || configuredPort,
262
273
  configuredPort,
263
- srv,
274
+ srv: includeSrv ? srv : undefined,
264
275
  };
265
276
  }
266
277
  }
package/src/nat.js CHANGED
@@ -85,6 +85,7 @@ exports.getNatInfo = (0, debounceAsync_1.debounceAsync)(async () => {
85
85
  publicIps: exports.defaultBaseUrl.publicIps = await gettingIps,
86
86
  externalIp: exports.defaultBaseUrl.externalIp,
87
87
  mapped,
88
+ mapped80: lodash_1.default.find(mappings, x => x.private.host === localIp && x.private.port === 80 && x.public.port === 80),
88
89
  internalPort,
89
90
  externalPort,
90
91
  proto: ((_c = status === null || status === void 0 ? void 0 : status.https) === null || _c === void 0 ? void 0 : _c.listening) ? 'https' : ((_d = status === null || status === void 0 ? void 0 : status.http) === null || _d === void 0 ? void 0 : _d.listening) ? 'http' : '',
package/src/selfCheck.js CHANGED
@@ -10,51 +10,41 @@ const net_1 = require("net");
10
10
  const lodash_1 = __importDefault(require("lodash"));
11
11
  const cross_1 = require("./cross");
12
12
  const util_http_1 = require("./util-http");
13
- let selfCheckMiddlewareEnabled = false;
14
13
  const CHECK_URL = cross_const_1.SPECIAL_URI + 'self-check';
15
- const selfCheckMiddleware = (ctx, next) => {
16
- if (!selfCheckMiddlewareEnabled || !ctx.url.startsWith(CHECK_URL))
17
- return next();
18
- ctx.body = 'HFS';
19
- };
14
+ const selfCheckMiddleware = (ctx, next) => // koa format
15
+ ctx.url.startsWith(CHECK_URL) ? ctx.body = 'HFS' : next();
20
16
  exports.selfCheckMiddleware = selfCheckMiddleware;
21
17
  async function selfCheck(url) {
22
18
  var _a;
23
19
  const prjInfo = await (0, github_1.getProjectInfo)();
24
20
  console.log(`checking server ${url}`);
25
- selfCheckMiddlewareEnabled = true;
26
- try {
27
- const parsed = new URL(url);
28
- const family = !(0, net_1.isIP)(parsed.hostname) ? undefined : (0, net_1.isIPv6)(parsed.hostname) ? 6 : 4;
29
- for (const services of lodash_1.default.chunk(lodash_1.default.shuffle(prjInfo.selfCheckServices), 2)) {
30
- try {
31
- return await Promise.any(services.map(async (svc) => {
32
- if (!svc.url || svc.type)
33
- throw 'unsupported ' + svc.type; // only default type supported for now
34
- let { url: serviceUrl, body, regexpSuccess, regexpFailure, ...rest } = svc;
35
- const service = new URL(serviceUrl).hostname;
36
- console.log('trying external service', service);
37
- body = applySymbols(body);
38
- serviceUrl = applySymbols(serviceUrl);
39
- const res = await (0, cross_1.haveTimeout)(10000, (0, util_http_1.httpString)(serviceUrl, { family, ...rest, body }));
40
- const success = new RegExp(regexpSuccess).test(res);
41
- const failure = new RegExp(regexpFailure).test(res);
42
- if (success === failure)
43
- throw 'inconsistent: ' + service + ': ' + res; // this result cannot be trusted
44
- console.debug(service, 'responded', success);
45
- return { success, service, url };
46
- }));
47
- }
48
- catch (e) {
49
- console.debug(((_a = e === null || e === void 0 ? void 0 : e.errors) === null || _a === void 0 ? void 0 : _a.map(String)) || (e === null || e === void 0 ? void 0 : e.cause) || String(e));
50
- }
21
+ const parsed = new URL(url);
22
+ const family = !(0, net_1.isIP)(parsed.hostname) ? undefined : (0, net_1.isIPv6)(parsed.hostname) ? 6 : 4;
23
+ for (const services of lodash_1.default.chunk(lodash_1.default.shuffle(prjInfo.selfCheckServices), 2)) {
24
+ try {
25
+ return await Promise.any(services.map(async (svc) => {
26
+ if (!svc.url || svc.type)
27
+ throw 'unsupported ' + svc.type; // only default type supported for now
28
+ let { url: serviceUrl, body, regexpSuccess, regexpFailure, ...rest } = svc;
29
+ const service = new URL(serviceUrl).hostname;
30
+ console.log('trying external service', service);
31
+ body = applySymbols(body);
32
+ serviceUrl = applySymbols(serviceUrl);
33
+ const res = await (0, cross_1.haveTimeout)(6000, (0, util_http_1.httpString)(serviceUrl, { family, ...rest, body }));
34
+ const success = new RegExp(regexpSuccess).test(res);
35
+ const failure = new RegExp(regexpFailure).test(res);
36
+ if (success === failure)
37
+ throw 'inconsistent: ' + service + ': ' + res; // this result cannot be trusted
38
+ console.debug(service, 'responded', success);
39
+ return { success, service, url };
40
+ }));
51
41
  }
52
- function applySymbols(s) {
53
- return s === null || s === void 0 ? void 0 : s.replace('$IP', parsed.hostname).replace('$PORT', parsed.port || (parsed.protocol === 'https:' ? '443' : '80')).replace('$URL', url.replace(/\/$/, '') + CHECK_URL);
42
+ catch (e) {
43
+ console.debug(((_a = e === null || e === void 0 ? void 0 : e.errors) === null || _a === void 0 ? void 0 : _a.map(String)) || (e === null || e === void 0 ? void 0 : e.cause) || String(e));
54
44
  }
55
45
  }
56
- finally {
57
- selfCheckMiddlewareEnabled = false;
46
+ function applySymbols(s) {
47
+ return s === null || s === void 0 ? void 0 : s.replace('$IP', parsed.hostname).replace('$PORT', parsed.port || (parsed.protocol === 'https:' ? '443' : '80')).replace('$URL', url.replace(/\/$/, '') + CHECK_URL);
58
48
  }
59
49
  }
60
50
  exports.selfCheck = selfCheck;