mirror of
https://github.com/ChromeDevTools/chrome-devtools-mcp.git
synced 2026-11-04 15:00:11 +00:00
Fixes #3097 Closes https://github.com/ChromeDevTools/chrome-devtools-mcp/pull/3124 ### Summary - Enforce strict numeric parsing and tag validation in `geolocationTransform` and `viewportTransform` (`src/tools/ToolDefinition.ts`), rejecting empty segments, extra parts, unknown boolean flags, and non-decimal literals instead of silently coercing to `0` or dropping them. - Add regression tests in `tests/tools/emulation.test.ts`.
This commit is contained in:
1 parent
78c8320c50
commit
3ea31fbab1
2 files changed
+78
-11
No files matched your search
+45
-11
@@ -593,6 +593,18 @@ export const timeoutSchema = {
|
||||
),
|
||||
};
|
||||
|
||||
const DECIMAL = /^[+-]?(\d+(\.\d*)?|\.\d+)$/;
|
||||
const VALID_VIEWPORT_TAGS = new Set(['mobile', 'touch', 'landscape']);
|
||||
|
||||
function parseDecimal(part: string | undefined, name: string): number {
|
||||
if (part === undefined || !DECIMAL.test(part)) {
|
||||
throw new Error(
|
||||
`Invalid ${name} "${part ?? ''}". Expected a decimal number.`,
|
||||
);
|
||||
}
|
||||
return Number(part);
|
||||
}
|
||||
|
||||
export function viewportTransform(arg: string | undefined):
|
||||
| {
|
||||
width: number;
|
||||
@@ -607,33 +619,48 @@ export function viewportTransform(arg: string | undefined):
|
||||
return undefined;
|
||||
}
|
||||
const [dimensions, ...tags] = arg.split(',');
|
||||
for (const tag of tags) {
|
||||
if (!VALID_VIEWPORT_TAGS.has(tag)) {
|
||||
throw new Error(
|
||||
`Invalid viewport tag "${tag}". Expected format '<width>x<height>x<devicePixelRatio>[,mobile][,touch][,landscape]'.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
const isMobile = tags.includes('mobile');
|
||||
const hasTouch = tags.includes('touch');
|
||||
const isLandscape = tags.includes('landscape');
|
||||
const [width, height, dpr] = dimensions.split('x').map(Number) as [
|
||||
number,
|
||||
number,
|
||||
number | undefined,
|
||||
];
|
||||
const dimParts = dimensions.split('x');
|
||||
if (dimParts.length !== 2 && dimParts.length !== 3) {
|
||||
throw new Error(
|
||||
`Invalid viewport dimensions "${dimensions}". Expected format '<width>x<height>x<devicePixelRatio>[,mobile][,touch][,landscape]'.`,
|
||||
);
|
||||
}
|
||||
const width = parseDecimal(dimParts[0], 'viewport width');
|
||||
if (!Number.isFinite(width) || width <= 0) {
|
||||
throw new Error(
|
||||
`Invalid viewport width "${width}". Expected format '<width>x<height>x<devicePixelRatio>[,mobile][,touch][,landscape]' with a positive width.`,
|
||||
);
|
||||
}
|
||||
const height = parseDecimal(dimParts[1], 'viewport height');
|
||||
if (!Number.isFinite(height) || height <= 0) {
|
||||
throw new Error(
|
||||
`Invalid viewport height "${height}". Expected format '<width>x<height>x<devicePixelRatio>[,mobile][,touch][,landscape]' with a positive height.`,
|
||||
);
|
||||
}
|
||||
if (dpr !== undefined && (!Number.isFinite(dpr) || dpr <= 0)) {
|
||||
throw new Error(
|
||||
`Invalid devicePixelRatio "${dpr}". Expected a positive number.`,
|
||||
);
|
||||
let deviceScaleFactor: number | undefined;
|
||||
if (dimParts.length === 3) {
|
||||
const dpr = parseDecimal(dimParts[2], 'devicePixelRatio');
|
||||
if (!Number.isFinite(dpr) || dpr <= 0) {
|
||||
throw new Error(
|
||||
`Invalid devicePixelRatio "${dpr}". Expected a positive number.`,
|
||||
);
|
||||
}
|
||||
deviceScaleFactor = dpr;
|
||||
}
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
deviceScaleFactor: dpr,
|
||||
deviceScaleFactor,
|
||||
isMobile,
|
||||
isLandscape,
|
||||
hasTouch,
|
||||
@@ -646,12 +673,19 @@ export function geolocationTransform(
|
||||
if (!arg) {
|
||||
return undefined;
|
||||
}
|
||||
const [latitude, longitude] = arg.split(',').map(Number) as [number, number];
|
||||
const parts = arg.split(',');
|
||||
if (parts.length !== 2) {
|
||||
throw new Error(
|
||||
`Invalid geolocation "${arg}". Expected '<latitude>,<longitude>'.`,
|
||||
);
|
||||
}
|
||||
const latitude = parseDecimal(parts[0], 'latitude');
|
||||
if (!Number.isFinite(latitude) || latitude < -90 || latitude > 90) {
|
||||
throw new Error(
|
||||
`Invalid latitude "${latitude}". Latitude must be a number between -90 and 90.`,
|
||||
);
|
||||
}
|
||||
const longitude = parseDecimal(parts[1], 'longitude');
|
||||
if (!Number.isFinite(longitude) || longitude < -180 || longitude > 180) {
|
||||
throw new Error(
|
||||
`Invalid longitude "${longitude}". Longitude must be a number between -180 and 180.`,
|
||||
|
||||
@@ -91,6 +91,25 @@ describe('emulation', () => {
|
||||
it('throws when height is missing', () => {
|
||||
assert.throws(() => viewportTransform('800'));
|
||||
});
|
||||
|
||||
it('throws when viewport has unknown tags', () => {
|
||||
assert.throws(() => viewportTransform('1280x720,mobile,bogus'));
|
||||
});
|
||||
|
||||
it('throws when viewport has empty tags', () => {
|
||||
assert.throws(() => viewportTransform('1280x720,'));
|
||||
});
|
||||
|
||||
it('throws when viewport has extra dimension parts', () => {
|
||||
assert.throws(() => viewportTransform('1280x720x2x9'));
|
||||
});
|
||||
|
||||
it('throws when dimension parts are empty or contain whitespace', () => {
|
||||
assert.throws(() => viewportTransform('800x'));
|
||||
assert.throws(() => viewportTransform('x600'));
|
||||
assert.throws(() => viewportTransform(' 1280 x 720'));
|
||||
assert.throws(() => viewportTransform('1280x720, mobile'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('geolocationTransform', () => {
|
||||
@@ -120,6 +139,20 @@ describe('emulation', () => {
|
||||
it('throws when longitude is missing', () => {
|
||||
assert.throws(() => geolocationTransform('48.1'));
|
||||
});
|
||||
|
||||
it('throws when geolocation has empty parts or trailing commas', () => {
|
||||
assert.throws(() => geolocationTransform('37.77,'));
|
||||
assert.throws(() => geolocationTransform(','));
|
||||
});
|
||||
|
||||
it('throws when geolocation has extra parts', () => {
|
||||
assert.throws(() => geolocationTransform('37.77,-122.42,9'));
|
||||
});
|
||||
|
||||
it('throws on hex coordinates or surrounding whitespace', () => {
|
||||
assert.throws(() => geolocationTransform('0x10,0x20'));
|
||||
assert.throws(() => geolocationTransform(' 37.77 , -122.42 '));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user