Skip to content

Commit 90346db

Browse files
Merge branch 'main' into dependabot/npm_and_yarn/mocha-11.7.5
2 parents a8bda9c + 1bf436d commit 90346db

3 files changed

Lines changed: 77 additions & 20 deletions

File tree

src/data-connect/data-connect-api-client-internal.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -413,10 +413,7 @@ export class DataConnectApiClient {
413413
*/
414414
private objectToString(data: unknown): string {
415415
if (typeof data === 'string') {
416-
const escapedString = data
417-
.replace(/\\/g, '\\\\') // Replace \ with \\
418-
.replace(/"/g, '\\"'); // Replace " with \"
419-
return `"${escapedString}"`;
416+
return JSON.stringify(data);
420417
}
421418
if (typeof data === 'number' || typeof data === 'boolean' || data === null) {
422419
return String(data);

src/utils/validator.ts

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
* limitations under the License.
1616
*/
1717

18-
import url = require('url');
19-
2018
/**
2119
* Validates that a value is a byte buffer.
2220
*
@@ -234,33 +232,38 @@ export function isURL(urlStr: any): boolean {
234232
return false;
235233
}
236234
try {
237-
const uri = url.parse(urlStr);
235+
const uri = new URL(urlStr);
238236
const scheme = uri.protocol;
239-
const slashes = uri.slashes;
240-
const hostname = uri.hostname;
241-
const pathname = uri.pathname;
242-
if ((scheme !== 'http:' && scheme !== 'https:') || !slashes) {
237+
if (scheme !== 'http:' && scheme !== 'https:') {
243238
return false;
244239
}
245-
// Validate hostname: Can contain letters, numbers, underscore and dashes separated by a dot.
246-
// Each zone must not start with a hyphen or underscore.
247-
if (!hostname || !/^[a-zA-Z0-9]+[\w-]*([.]?[a-zA-Z0-9]+[\w-]*)*$/.test(hostname)) {
248-
return false;
240+
const hostname = uri.hostname;
241+
// Validate hostname strictly to match previous behavior and prevent weak/invalid domains.
242+
// Must be alphanumeric with optional dashes/underscores, separated by dots.
243+
// Cannot start/end with dot or dash (mostly).
244+
// This regex is safe (no nested quantifiers with overlap).
245+
if (!/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)*$/.test(hostname)) {
246+
// Check for IPv6 literals which are valid but behave differently.
247+
// Node 'new URL' keeps brackets for IPv6: [::1] -> [::1]
248+
// Check for IPv6 address (simple check for brackets)
249+
if (!/^\[[a-fA-F0-9:.]+\]$/.test(hostname)) {
250+
return false;
251+
}
249252
}
250-
// Allow for pathnames: (/chars+)*/?
253+
// Restore strict pathname validation: (/chars+)*/?
251254
// Where chars can be a combination of: a-z A-Z 0-9 - _ . ~ ! $ & ' ( ) * + , ; = : @ %
252255
const pathnameRe = /^(\/[\w\-.~!$'()*+,;=:@%]+)*\/?$/;
253256
// Validate pathname.
257+
const pathname = uri.pathname;
254258
if (pathname &&
255-
pathname !== '/' &&
256-
!pathnameRe.test(pathname)) {
259+
pathname !== '/' &&
260+
!pathnameRe.test(pathname)) {
257261
return false;
258262
}
259-
// Allow any query string and hash as long as no invalid character is used.
263+
return true;
260264
} catch (e) {
261265
return false;
262266
}
263-
return true;
264267
}
265268

266269

test/unit/data-connect/data-connect-api-client-internal.spec.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -928,4 +928,61 @@ describe('DataConnectApiClient CRUD helpers', () => {
928928
.to.be.rejectedWith(FirebaseDataConnectError, `${serverErrorString}. ${additionalErrorMessageForBulkImport}`);
929929
});
930930
});
931+
932+
describe('String serialization', () => {
933+
it('should correctly escape special characters in strings during insert', async () => {
934+
const data = {
935+
content: 'Line 1\nLine 2',
936+
};
937+
938+
await apiClient.insert(tableName, data);
939+
const callArgs = executeGraphqlStub.firstCall.args[0];
940+
941+
expect(callArgs).to.include(String.raw`content: "Line 1\nLine 2"`);
942+
});
943+
944+
it('should correctly escape backslash', async () => {
945+
const data = {
946+
content: 'Backslash \\',
947+
};
948+
949+
await apiClient.insert(tableName, data);
950+
const callArgs = executeGraphqlStub.firstCall.args[0];
951+
952+
expect(callArgs).to.include(String.raw`content: "Backslash \\"`);
953+
});
954+
955+
it('should correctly escape double quotes', async () => {
956+
const data = {
957+
content: 'Quote "test"',
958+
};
959+
960+
await apiClient.insert(tableName, data);
961+
const callArgs = executeGraphqlStub.firstCall.args[0];
962+
963+
expect(callArgs).to.include(String.raw`content: "Quote \"test\""`);
964+
});
965+
966+
it('should correctly escape tab character', async () => {
967+
const data = {
968+
content: 'Tab\tCharacter',
969+
};
970+
971+
await apiClient.insert(tableName, data);
972+
const callArgs = executeGraphqlStub.firstCall.args[0];
973+
974+
expect(callArgs).to.include(String.raw`content: "Tab\tCharacter"`);
975+
});
976+
977+
it('should correctly handle emojis', async () => {
978+
const data = {
979+
content: 'Emoji 😊',
980+
};
981+
982+
await apiClient.insert(tableName, data);
983+
const callArgs = executeGraphqlStub.firstCall.args[0];
984+
985+
expect(callArgs).to.include('content: "Emoji 😊"');
986+
});
987+
});
931988
});

0 commit comments

Comments
 (0)