Skip to content

Commit 007d82a

Browse files
Refactor processURL to preserve original URL encoding
ProcessURL previously ran the query string through query-string.parse → mutate → query-string.stringify, which re-encoded values and collapsed repeated keys. That caused two classes of issue: encoded characters like %20, %2B, %25 being rewritten, and duplicate keys (?foo=1&foo=2) being lost in the round-trip. The new path keeps the raw query string as a string, uses regex to extract ch-* parameter values (decoded for internal use), and filters ch-* keys out by splitting/rejoining on &. Non-ch parameters are now preserved byte-for-byte, and duplicates are retained in order. Also drops the query-string dependency and the unused QueryObject schema export. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
1 parent e54ee4b commit 007d82a

16 files changed

Lines changed: 397 additions & 1182 deletions

dist/common/processURL.js

Lines changed: 72 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
"use strict";
2-
var __importDefault = (this && this.__importDefault) || function (mod) {
3-
return (mod && mod.__esModule) ? mod : { "default": mod };
4-
};
52
Object.defineProperty(exports, "__esModule", { value: true });
63
exports.ProcessURL = void 0;
7-
var query_string_1 = __importDefault(require("query-string"));
84
var logger_1 = require("./logger");
95
var ProcessURL = /** @class */ (function () {
106
function ProcessURL(request, debug) {
@@ -35,88 +31,90 @@ var ProcessURL = /** @class */ (function () {
3531
specialParameters: this.specialParameters,
3632
};
3733
}
38-
//Extract query string from this.path
39-
function extractQueryString(path) {
40-
var queryString;
41-
if (path.includes("?")) {
42-
queryString = path.split("?")[1];
43-
}
44-
return queryString;
45-
}
46-
function formatQueryString(q) {
47-
if (q) {
48-
return query_string_1.default.parse(q, { sort: false });
49-
}
50-
}
51-
var unprocessedQueryString;
52-
unprocessedQueryString = extractQueryString(this.path);
53-
if (unprocessedQueryString) {
54-
this.queryString = formatQueryString(unprocessedQueryString);
55-
}
56-
//Destructure special params from query string if they are present
57-
var _a = this.queryString || {}, chCode = _a["ch-code"], chID = _a["ch-id"], chIDSignature = _a["ch-id-signature"], chPublicKey = _a["ch-public-key"], chRequested = _a["ch-requested"];
58-
//Override chCode value if the current one is unusable
59-
if (!chCode || chCode === "undefined" || chCode === "null") {
60-
chCode = "";
61-
}
62-
this.specialParameters.chCode = chCode;
63-
//Override chID value if the current one is unusable
64-
if (!chID || chID === "undefined" || chID === "null") {
65-
chID = "";
66-
}
67-
this.specialParameters.chID = chID;
68-
//Override chIDSignature value if the current one is unusable
69-
if (!chIDSignature ||
70-
chIDSignature === "undefined" ||
71-
chIDSignature === "null") {
72-
chIDSignature = "";
73-
}
74-
this.specialParameters.chIDSignature = chIDSignature;
75-
//Override chPublicKey value if the current one is unusable
76-
if (!chPublicKey || chPublicKey === "undefined" || chPublicKey === "null") {
77-
chPublicKey = "";
34+
// Extract raw query string from path (preserving original encoding)
35+
if (this.path.includes("?")) {
36+
this.rawQueryString = this.path.split("?")[1];
7837
}
79-
this.specialParameters.chPublicKey = chPublicKey;
80-
//Override chRequested value if the current one is unusable
81-
if (!chRequested || chRequested === "undefined" || chRequested === "null") {
82-
chRequested = "";
83-
}
84-
this.specialParameters.chRequested = chRequested;
85-
// Process the query string
86-
var processedQueryString = this.processQueryString(this.queryString);
87-
//URL encode the targetURL to be used later in redirects
88-
var targetURL;
89-
//We no longer need the query string in the path
90-
this.path = this.path.split("?")[0];
38+
// Extract ch-* parameter values using regex (decode for actual use)
39+
var chCode = this.extractParamValue("ch-code");
40+
var chID = this.extractParamValue("ch-id");
41+
var chIDSignature = this.extractParamValue("ch-id-signature");
42+
var chPublicKey = this.extractParamValue("ch-public-key");
43+
var chRequested = this.extractParamValue("ch-requested");
44+
// Set special parameters (with validation)
45+
this.specialParameters.chCode = this.sanitizeParam(chCode);
46+
this.specialParameters.chID = this.sanitizeParam(chID);
47+
this.specialParameters.chIDSignature = this.sanitizeParam(chIDSignature);
48+
this.specialParameters.chPublicKey = this.sanitizeParam(chPublicKey);
49+
this.specialParameters.chRequested = this.sanitizeParam(chRequested);
50+
// Remove ch-* params from query string while preserving everything else
51+
var processedQueryString = this.removeChParams(this.rawQueryString);
52+
// Extract path without query string
53+
var cleanPath = this.path.split("?")[0];
54+
// Construct targetURL
9155
if (processedQueryString) {
92-
this.targetURL = encodeURIComponent("https://".concat(this.host).concat(this.path, "?").concat(processedQueryString));
56+
this.targetURL = encodeURIComponent("https://".concat(this.host).concat(cleanPath, "?").concat(processedQueryString));
9357
}
9458
else {
95-
this.targetURL = encodeURIComponent("https://".concat(this.host).concat(this.path));
59+
this.targetURL = encodeURIComponent("https://".concat(this.host).concat(cleanPath));
9660
}
9761
return {
9862
targetURL: this.targetURL,
9963
specialParameters: this.specialParameters,
10064
};
10165
};
102-
ProcessURL.prototype.processQueryString = function (queryString) {
103-
var processedQueryString;
104-
if (queryString) {
105-
delete queryString["ch-code"];
106-
delete queryString["ch-fresh"];
107-
delete queryString["ch-id"];
108-
delete queryString["ch-id-signature"];
109-
delete queryString["ch-public-key"];
110-
delete queryString["ch-requested"];
111-
}
112-
//Convert to usable querystring format
113-
if (queryString && Object.keys(queryString).length !== 0) {
114-
processedQueryString = query_string_1.default.stringify(queryString, { sort: false });
66+
/**
67+
* Extract a parameter value from the raw query string using regex.
68+
* Decodes the value for actual use.
69+
*/
70+
ProcessURL.prototype.extractParamValue = function (paramName) {
71+
if (!this.rawQueryString)
72+
return "";
73+
// Match the parameter in the query string
74+
var regex = new RegExp("(?:^|&)".concat(paramName, "=([^&]*)"), "i");
75+
var match = this.rawQueryString.match(regex);
76+
if (match && match[1]) {
77+
try {
78+
return decodeURIComponent(match[1]);
79+
}
80+
catch (_a) {
81+
return match[1];
82+
}
11583
}
116-
else {
117-
processedQueryString = "";
84+
return "";
85+
};
86+
/**
87+
* Sanitize a parameter value - return empty string for unusable values.
88+
*/
89+
ProcessURL.prototype.sanitizeParam = function (value) {
90+
if (!value || value === "undefined" || value === "null") {
91+
return "";
11892
}
119-
return processedQueryString;
93+
return value;
94+
};
95+
/**
96+
* Remove ch-* parameters from the query string while preserving
97+
* the original encoding of all other parameters.
98+
*/
99+
ProcessURL.prototype.removeChParams = function (queryString) {
100+
if (!queryString)
101+
return "";
102+
// List of ch-* parameters to remove
103+
var chParams = [
104+
"ch-code",
105+
"ch-fresh",
106+
"ch-id",
107+
"ch-id-signature",
108+
"ch-public-key",
109+
"ch-requested",
110+
];
111+
// Split into individual params, filter out ch-* params, rejoin
112+
var params = queryString.split("&");
113+
var filteredParams = params.filter(function (param) {
114+
var key = param.split("=")[0];
115+
return !chParams.includes(key.toLowerCase());
116+
});
117+
return filteredParams.join("&");
120118
};
121119
return ProcessURL;
122120
}());

dist/common/types.js

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use strict";
22
Object.defineProperty(exports, "__esModule", { value: true });
3-
exports.Modes = exports.RecordPerformanceOptions = exports.SessionStatusWrapper = exports.HttpErrorWrapper = exports.ValidateRequestObject = exports.ValidateRequestParams = exports.TokenObjectConstructor = exports.TokenObject = exports.ExtractTokenOptions = exports.SignatureSourceObject = exports.SignatureResponseObject = exports.SignatureObject = exports.RoomMetaObject = exports.LocalStorageOptions = exports.LocalStorageObject = exports.CookieObject = exports.RequestObject = exports.ProcessURLResultObject = exports.SessionRequestConfig = exports.SpecialParametersObject = exports.QueryObject = exports.GatekeeperKeyPair = exports.GatekeeperOptions = exports.RoomsConfig = exports.RoomConfig = void 0;
3+
exports.Modes = exports.RecordPerformanceOptions = exports.SessionStatusWrapper = exports.HttpErrorWrapper = exports.ValidateRequestObject = exports.ValidateRequestParams = exports.TokenObjectConstructor = exports.TokenObject = exports.ExtractTokenOptions = exports.SignatureSourceObject = exports.SignatureResponseObject = exports.SignatureObject = exports.RoomMetaObject = exports.LocalStorageOptions = exports.LocalStorageObject = exports.CookieObject = exports.RequestObject = exports.ProcessURLResultObject = exports.SessionRequestConfig = exports.SpecialParametersObject = exports.GatekeeperKeyPair = exports.GatekeeperOptions = exports.RoomsConfig = exports.RoomConfig = void 0;
44
var zod_1 = require("zod");
55
// Lite Validator types
66
exports.RoomConfig = zod_1.z.object({
@@ -32,16 +32,6 @@ exports.GatekeeperKeyPair = zod_1.z.object({
3232
publicKey: zod_1.z.string(),
3333
privateKey: zod_1.z.string().optional(),
3434
});
35-
exports.QueryObject = zod_1.z
36-
.object({
37-
"ch-code": zod_1.z.string().optional(),
38-
"ch-id": zod_1.z.string().optional(),
39-
"ch-id-signature": zod_1.z.string().optional(),
40-
"ch-public-key": zod_1.z.string().optional(),
41-
"ch-requested": zod_1.z.string().optional(),
42-
"ch-token": zod_1.z.string().optional(),
43-
})
44-
.catchall(zod_1.z.any());
4535
exports.SpecialParametersObject = zod_1.z.object({
4636
chCode: zod_1.z.string(),
4737
chID: zod_1.z.string(),

dist/crowdhandler.cjs.js

Lines changed: 73 additions & 81 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/crowdhandler.cjs.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)