@crowdin/app-project-module 1.14.0 → 1.15.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/out/index.js CHANGED
@@ -71,6 +71,7 @@ const types_1 = require("./types");
71
71
  const util_1 = require("./util");
72
72
  const form_schema_1 = require("./util/form-schema");
73
73
  const connection_1 = require("./util/connection");
74
+ const log_sanitizer_1 = require("./util/log-sanitizer");
74
75
  const logger = __importStar(require("./util/logger"));
75
76
  const logger_1 = require("./util/logger");
76
77
  const terminus_express_1 = __importDefault(require("./util/terminus-express"));
@@ -187,6 +188,7 @@ function addCrowdinEndpoints(app, clientConfig) {
187
188
  });
188
189
  if (!config.disableLogsFormatter) {
189
190
  logsFormatter.setup();
191
+ logsFormatter.registerSanitizer(log_sanitizer_1.crowdinLogSanitizer);
190
192
  app.use(logsFormatter.contextResolverMiddleware());
191
193
  app.use(logsFormatter.expressMiddleware());
192
194
  }
@@ -27185,6 +27185,15 @@ export default theme;`;
27185
27185
  /** @type {(value: string) => boolean} */
27186
27186
  const isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
27187
27187
 
27188
+ /** @type {(value: string) => boolean} */
27189
+ const isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
27190
+
27191
+ /** @type {(value: string) => boolean} */
27192
+ const isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
27193
+
27194
+ /** @type {(value: string) => boolean} */
27195
+ const isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu);
27196
+
27188
27197
  /**
27189
27198
  * @param {Array<string>} input
27190
27199
  * @returns {string}
@@ -27443,31 +27452,126 @@ export default theme;`;
27443
27452
  }
27444
27453
 
27445
27454
  /**
27446
- * @param {import('../types/index').URIComponent} component
27447
- * @param {boolean} esc
27448
- * @returns {import('../types/index').URIComponent}
27455
+ * Re-escape RFC 3986 gen-delims that must not appear literally in the host.
27456
+ * After the URI regex parses, these characters cannot be literal in the host
27457
+ * field, so any that appear after decoding came from percent-encoding and
27458
+ * must be restored to prevent authority structure changes.
27459
+ *
27460
+ * @param {string} host
27461
+ * @param {boolean} isIP - true for IPv4/IPv6 hosts (skip colon re-escaping)
27462
+ * @returns {string}
27449
27463
  */
27450
- function normalizeComponentEncoding (component, esc) {
27451
- const func = esc !== true ? escape : unescape;
27452
- if (component.scheme !== undefined) {
27453
- component.scheme = func(component.scheme);
27454
- }
27455
- if (component.userinfo !== undefined) {
27456
- component.userinfo = func(component.userinfo);
27457
- }
27458
- if (component.host !== undefined) {
27459
- component.host = func(component.host);
27464
+ const HOST_DELIMS = { '@': '%40', '/': '%2F', '?': '%3F', '#': '%23', ':': '%3A' };
27465
+ const HOST_DELIM_RE = /[@/?#:]/g;
27466
+ const HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
27467
+
27468
+ function reescapeHostDelimiters (host, isIP) {
27469
+ const re = isIP ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE;
27470
+ re.lastIndex = 0;
27471
+ return host.replace(re, (ch) => HOST_DELIMS[ch])
27472
+ }
27473
+
27474
+ /**
27475
+ * Normalizes percent escapes and optionally decodes only unreserved ASCII bytes.
27476
+ * Reserved delimiters such as `%2F` and `%2E` stay escaped.
27477
+ *
27478
+ * @param {string} input
27479
+ * @param {boolean} [decodeUnreserved=false]
27480
+ * @returns {string}
27481
+ */
27482
+ function normalizePercentEncoding (input, decodeUnreserved = false) {
27483
+ if (input.indexOf('%') === -1) {
27484
+ return input
27460
27485
  }
27461
- if (component.path !== undefined) {
27462
- component.path = func(component.path);
27486
+
27487
+ let output = '';
27488
+
27489
+ for (let i = 0; i < input.length; i++) {
27490
+ if (input[i] === '%' && i + 2 < input.length) {
27491
+ const hex = input.slice(i + 1, i + 3);
27492
+ if (isHexPair(hex)) {
27493
+ const normalizedHex = hex.toUpperCase();
27494
+ const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
27495
+
27496
+ if (decodeUnreserved && isUnreserved(decoded)) {
27497
+ output += decoded;
27498
+ } else {
27499
+ output += '%' + normalizedHex;
27500
+ }
27501
+
27502
+ i += 2;
27503
+ continue
27504
+ }
27505
+ }
27506
+
27507
+ output += input[i];
27463
27508
  }
27464
- if (component.query !== undefined) {
27465
- component.query = func(component.query);
27509
+
27510
+ return output
27511
+ }
27512
+
27513
+ /**
27514
+ * Normalizes path data without turning reserved escapes into live path syntax.
27515
+ * Valid escapes are uppercased, raw unsafe characters are escaped, and only
27516
+ * unreserved bytes that are not `.` are decoded.
27517
+ *
27518
+ * @param {string} input
27519
+ * @returns {string}
27520
+ */
27521
+ function normalizePathEncoding (input) {
27522
+ let output = '';
27523
+
27524
+ for (let i = 0; i < input.length; i++) {
27525
+ if (input[i] === '%' && i + 2 < input.length) {
27526
+ const hex = input.slice(i + 1, i + 3);
27527
+ if (isHexPair(hex)) {
27528
+ const normalizedHex = hex.toUpperCase();
27529
+ const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
27530
+
27531
+ if (decoded !== '.' && isUnreserved(decoded)) {
27532
+ output += decoded;
27533
+ } else {
27534
+ output += '%' + normalizedHex;
27535
+ }
27536
+
27537
+ i += 2;
27538
+ continue
27539
+ }
27540
+ }
27541
+
27542
+ if (isPathCharacter(input[i])) {
27543
+ output += input[i];
27544
+ } else {
27545
+ output += escape(input[i]);
27546
+ }
27466
27547
  }
27467
- if (component.fragment !== undefined) {
27468
- component.fragment = func(component.fragment);
27548
+
27549
+ return output
27550
+ }
27551
+
27552
+ /**
27553
+ * Escapes a component while preserving existing valid percent escapes.
27554
+ *
27555
+ * @param {string} input
27556
+ * @returns {string}
27557
+ */
27558
+ function escapePreservingEscapes (input) {
27559
+ let output = '';
27560
+
27561
+ for (let i = 0; i < input.length; i++) {
27562
+ if (input[i] === '%' && i + 2 < input.length) {
27563
+ const hex = input.slice(i + 1, i + 3);
27564
+ if (isHexPair(hex)) {
27565
+ output += '%' + hex.toUpperCase();
27566
+ i += 2;
27567
+ continue
27568
+ }
27569
+ }
27570
+
27571
+ output += escape(input[i]);
27469
27572
  }
27470
- return component
27573
+
27574
+ return output
27471
27575
  }
27472
27576
 
27473
27577
  /**
@@ -27489,7 +27593,7 @@ export default theme;`;
27489
27593
  if (ipV6res.isIPV6 === true) {
27490
27594
  host = `[${ipV6res.escapedHost}]`;
27491
27595
  } else {
27492
- host = component.host;
27596
+ host = reescapeHostDelimiters(host, false);
27493
27597
  }
27494
27598
  }
27495
27599
  uriTokens.push(host);
@@ -27505,7 +27609,10 @@ export default theme;`;
27505
27609
  utils = {
27506
27610
  nonSimpleDomain,
27507
27611
  recomposeAuthority,
27508
- normalizeComponentEncoding,
27612
+ reescapeHostDelimiters,
27613
+ normalizePercentEncoding,
27614
+ normalizePathEncoding,
27615
+ escapePreservingEscapes,
27509
27616
  removeDotSegments,
27510
27617
  isIPv4,
27511
27618
  isUUID,
@@ -27796,7 +27903,7 @@ export default theme;`;
27796
27903
  if (hasRequiredFastUri) return fastUri.exports;
27797
27904
  hasRequiredFastUri = 1;
27798
27905
 
27799
- const { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizeComponentEncoding, isIPv4, nonSimpleDomain } = requireUtils();
27906
+ const { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = requireUtils();
27800
27907
  const { SCHEMES, getSchemeHandler } = requireSchemes();
27801
27908
 
27802
27909
  /**
@@ -27807,7 +27914,7 @@ export default theme;`;
27807
27914
  */
27808
27915
  function normalize (uri, options) {
27809
27916
  if (typeof uri === 'string') {
27810
- uri = /** @type {T} */ (serialize(parse(uri, options), options));
27917
+ uri = /** @type {T} */ (normalizeString(uri, options));
27811
27918
  } else if (typeof uri === 'object') {
27812
27919
  uri = /** @type {T} */ (parse(serialize(uri, options), options));
27813
27920
  }
@@ -27902,21 +28009,10 @@ export default theme;`;
27902
28009
  * @returns {boolean}
27903
28010
  */
27904
28011
  function equal (uriA, uriB, options) {
27905
- if (typeof uriA === 'string') {
27906
- uriA = unescape(uriA);
27907
- uriA = serialize(normalizeComponentEncoding(parse(uriA, options), true), { ...options, skipEscape: true });
27908
- } else if (typeof uriA === 'object') {
27909
- uriA = serialize(normalizeComponentEncoding(uriA, true), { ...options, skipEscape: true });
27910
- }
27911
-
27912
- if (typeof uriB === 'string') {
27913
- uriB = unescape(uriB);
27914
- uriB = serialize(normalizeComponentEncoding(parse(uriB, options), true), { ...options, skipEscape: true });
27915
- } else if (typeof uriB === 'object') {
27916
- uriB = serialize(normalizeComponentEncoding(uriB, true), { ...options, skipEscape: true });
27917
- }
28012
+ const normalizedA = normalizeComparableURI(uriA, options);
28013
+ const normalizedB = normalizeComparableURI(uriB, options);
27918
28014
 
27919
- return uriA.toLowerCase() === uriB.toLowerCase()
28015
+ return normalizedA !== undefined && normalizedB !== undefined && normalizedA.toLowerCase() === normalizedB.toLowerCase()
27920
28016
  }
27921
28017
 
27922
28018
  /**
@@ -27952,13 +28048,13 @@ export default theme;`;
27952
28048
 
27953
28049
  if (component.path !== undefined) {
27954
28050
  if (!options.skipEscape) {
27955
- component.path = escape(component.path);
28051
+ component.path = escapePreservingEscapes(component.path);
27956
28052
 
27957
28053
  if (component.scheme !== undefined) {
27958
28054
  component.path = component.path.split('%3A').join(':');
27959
28055
  }
27960
28056
  } else {
27961
- component.path = unescape(component.path);
28057
+ component.path = normalizePercentEncoding(component.path);
27962
28058
  }
27963
28059
  }
27964
28060
 
@@ -28009,12 +28105,29 @@ export default theme;`;
28009
28105
 
28010
28106
  const URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
28011
28107
 
28108
+ /**
28109
+ * @param {import('./types/index').URIComponent} parsed
28110
+ * @param {RegExpMatchArray} matches
28111
+ * @returns {string|undefined}
28112
+ */
28113
+ function getParseError (parsed, matches) {
28114
+ if (matches[2] !== undefined && parsed.path && parsed.path[0] !== '/') {
28115
+ return 'URI path must start with "/" when authority is present.'
28116
+ }
28117
+
28118
+ if (typeof parsed.port === 'number' && (parsed.port < 0 || parsed.port > 65535)) {
28119
+ return 'URI port is malformed.'
28120
+ }
28121
+
28122
+ return undefined
28123
+ }
28124
+
28012
28125
  /**
28013
28126
  * @param {string} uri
28014
28127
  * @param {import('./types/index').Options} [opts]
28015
- * @returns
28128
+ * @returns {{ parsed: import('./types/index').URIComponent, malformedAuthorityOrPort: boolean }}
28016
28129
  */
28017
- function parse (uri, opts) {
28130
+ function parseWithStatus (uri, opts) {
28018
28131
  const options = Object.assign({}, opts);
28019
28132
  /** @type {import('./types/index').URIComponent} */
28020
28133
  const parsed = {
@@ -28027,6 +28140,8 @@ export default theme;`;
28027
28140
  fragment: undefined
28028
28141
  };
28029
28142
 
28143
+ let malformedAuthorityOrPort = false;
28144
+
28030
28145
  let isIP = false;
28031
28146
  if (options.reference === 'suffix') {
28032
28147
  if (options.scheme) {
@@ -28052,6 +28167,13 @@ export default theme;`;
28052
28167
  if (isNaN(parsed.port)) {
28053
28168
  parsed.port = matches[5];
28054
28169
  }
28170
+
28171
+ const parseError = getParseError(parsed, matches);
28172
+ if (parseError !== undefined) {
28173
+ parsed.error = parsed.error || parseError;
28174
+ malformedAuthorityOrPort = true;
28175
+ }
28176
+
28055
28177
  if (parsed.host) {
28056
28178
  const ipv4result = isIPv4(parsed.host);
28057
28179
  if (ipv4result === false) {
@@ -28100,14 +28222,18 @@ export default theme;`;
28100
28222
  parsed.scheme = unescape(parsed.scheme);
28101
28223
  }
28102
28224
  if (parsed.host !== undefined) {
28103
- parsed.host = unescape(parsed.host);
28225
+ parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP);
28104
28226
  }
28105
28227
  }
28106
28228
  if (parsed.path) {
28107
- parsed.path = escape(unescape(parsed.path));
28229
+ parsed.path = normalizePathEncoding(parsed.path);
28108
28230
  }
28109
28231
  if (parsed.fragment) {
28110
- parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
28232
+ try {
28233
+ parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
28234
+ } catch {
28235
+ parsed.error = parsed.error || 'URI malformed';
28236
+ }
28111
28237
  }
28112
28238
  }
28113
28239
 
@@ -28118,7 +28244,54 @@ export default theme;`;
28118
28244
  } else {
28119
28245
  parsed.error = parsed.error || 'URI can not be parsed.';
28120
28246
  }
28121
- return parsed
28247
+ return { parsed, malformedAuthorityOrPort }
28248
+ }
28249
+
28250
+ /**
28251
+ * @param {string} uri
28252
+ * @param {import('./types/index').Options} [opts]
28253
+ * @returns
28254
+ */
28255
+ function parse (uri, opts) {
28256
+ return parseWithStatus(uri, opts).parsed
28257
+ }
28258
+
28259
+ /**
28260
+ * @param {string} uri
28261
+ * @param {import('./types/index').Options} [opts]
28262
+ * @returns {string}
28263
+ */
28264
+ function normalizeString (uri, opts) {
28265
+ return normalizeStringWithStatus(uri, opts).normalized
28266
+ }
28267
+
28268
+ /**
28269
+ * @param {string} uri
28270
+ * @param {import('./types/index').Options} [opts]
28271
+ * @returns {{ normalized: string, malformedAuthorityOrPort: boolean }}
28272
+ */
28273
+ function normalizeStringWithStatus (uri, opts) {
28274
+ const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts);
28275
+ return {
28276
+ normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
28277
+ malformedAuthorityOrPort
28278
+ }
28279
+ }
28280
+
28281
+ /**
28282
+ * @param {import ('./types/index').URIComponent|string} uri
28283
+ * @param {import('./types/index').Options} [opts]
28284
+ * @returns {string|undefined}
28285
+ */
28286
+ function normalizeComparableURI (uri, opts) {
28287
+ if (typeof uri === 'string') {
28288
+ const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts);
28289
+ return malformedAuthorityOrPort ? undefined : normalized
28290
+ }
28291
+
28292
+ if (typeof uri === 'object') {
28293
+ return serialize(uri, opts)
28294
+ }
28122
28295
  }
28123
28296
 
28124
28297
  const fastUri$1 = {