From 3ae759789200a0f7d89550f4a6b2a7a6aff1ba64 Mon Sep 17 00:00:00 2001 From: Diego Nunez <90226979+xDiegoNunezx@users.noreply.github.com> Date: Tue, 13 Jan 2026 14:53:44 -0600 Subject: [PATCH 1/3] fix: handle sampleUrl parsing more robustly --- src/app/views/common/share.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/app/views/common/share.ts b/src/app/views/common/share.ts index 22b546b11..dc2d981e5 100644 --- a/src/app/views/common/share.ts +++ b/src/app/views/common/share.ts @@ -1,5 +1,5 @@ -import { authenticationWrapper } from '../../../modules/authentication'; import { IQuery } from '../../../types/query-runner'; +import { authenticationWrapper } from '../../../modules/authentication'; import { encodeHashCharacters } from '../../utils/query-url-sanitization'; import { parseSampleUrl } from '../../utils/sample-url-generation'; @@ -17,8 +17,15 @@ export const createShareLink = (sampleQuery: IQuery, authenticated?: boolean): s return ''; } - const url = new URL(sampleUrl); - const graphUrl = url.origin; + let graphUrl = 'https://graph.microsoft.com'; + + try { + const url = new URL(sampleUrl); + graphUrl = url.origin; + } catch { + // do nothing and use default graph url + } + const appUrl = 'https://developer.microsoft.com/graph/graph-explorer'; const graphUrlRequest = encodeURIComponent(requestUrl + search); From 5da6ba366b676a9672adc319857ce1f3a32008c2 Mon Sep 17 00:00:00 2001 From: Claudio Castillo Date: Wed, 14 Oct 2026 11:53:54 -0700 Subject: [PATCH 2/3] suggestions for typos: https:// is incorrect or host name is incorrect. added tests and a message for first error --- src/app/views/common/share.spec.ts | 30 +++++++++++++++++++ src/messages/GE.json | 1 + .../validation/validation-service.spec.ts | 25 ++++++++++++++++ src/modules/validation/validation-service.ts | 17 +++++++++-- 4 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/app/views/common/share.spec.ts b/src/app/views/common/share.spec.ts index 189a6c839..d90184f90 100644 --- a/src/app/views/common/share.spec.ts +++ b/src/app/views/common/share.spec.ts @@ -1,3 +1,4 @@ +import { parseSampleUrl } from '../../utils/sample-url-generation'; import { createShareLink } from './share'; jest.mock('../../../modules/authentication', () => ({ @@ -64,6 +65,35 @@ describe('createShareLink', () => { expect(link).toContain('sid=test-session-id'); }); + describe('when the sample url is malformed', () => { + // Use the real parser: the module-level mock above does not reproduce how it handles bad input. + const { parseSampleUrl: realParseSampleUrl } = jest.requireActual('../../utils/sample-url-generation'); + + const createShareLinkFor = (sampleUrl: string) => { + (parseSampleUrl as jest.Mock).mockImplementationOnce(() => realParseSampleUrl(sampleUrl)); + return createShareLink({ ...baseQuery, sampleUrl } as any); + }; + + // The parser derives an origin of 'null' for an unknown scheme, which produces a url that cannot be parsed. + it.each([ + 'htps://graph.microsoft.com/v1.0/me', + 'htttps://graph.microsoft.com/v1.0/me?$select=id' + ])('falls back to the default graph url for a mistyped scheme: %s', (sampleUrl) => { + expect(realParseSampleUrl(sampleUrl).sampleUrl).toMatch(/^null\//); + + const link = createShareLinkFor(sampleUrl); + + expect(link).toContain('GraphUrl=https://graph.microsoft.com'); + }); + + it.each([ + 'https//graph.microsoft.com/v1.0/me', + 'graph.microsoft.com/v1.0/me' + ])('returns an empty link without throwing when the parser cannot read the url: %s', (sampleUrl) => { + expect(createShareLinkFor(sampleUrl)).toBe(''); + }); + }); + it('should not include session ID when not authenticated', () => { const link = createShareLink(baseQuery as any, false); expect(link).not.toContain('sid='); diff --git a/src/messages/GE.json b/src/messages/GE.json index b7ebdd4d9..82c536864 100644 --- a/src/messages/GE.json +++ b/src/messages/GE.json @@ -475,6 +475,7 @@ "Documentation": "Documentation", "Query documentation": "Query documentation", "This token is not a jwt token and cannot be decoded by jwt.ms": "This token is not a jwt token and cannot be decoded by jwt.ms", + "The URL must begin with https://": "The URL must begin with https://", "The URL must contain graph.microsoft.com": "The URL must contain graph.microsoft.com", "Something went wrong": "Something went wrong while downloading this tab. Please try again", "Query documentation not found": "Query documentation not found", diff --git a/src/modules/validation/validation-service.spec.ts b/src/modules/validation/validation-service.spec.ts index 4bf42577a..636af8dd7 100644 --- a/src/modules/validation/validation-service.spec.ts +++ b/src/modules/validation/validation-service.spec.ts @@ -101,6 +101,31 @@ describe('ValidationService.validate edge cases', () => { expect(() => ValidationService.validate('https://example.com/v1.0/me', [])).toThrow(ValidationError); }); + it.each([ + 'http://graph.microsoft.com/v1.0/me', + 'htps://graph.microsoft.com/v1.0/me', + 'https//graph.microsoft.com/v1.0/me', + 'graph.microsoft.com/v1.0/me' + ])('reports an error asking for https:// when the scheme is wrong: %s', (url) => { + expect(() => ValidationService.validate(url, [])).toThrow( + expect.objectContaining({ type: 'error', message: 'The URL must begin with https://' }) + ); + }); + + it.each([ + 'https://graph.microsft.com/v1.0/me', + 'https://example.com/v1.0/me', + 'https://' + ])('reports an error asking for graph.microsoft.com when the host is wrong: %s', (url) => { + expect(() => ValidationService.validate(url, [])).toThrow( + expect.objectContaining({ type: 'error', message: 'The URL must contain graph.microsoft.com' }) + ); + }); + + it('does not throw for a valid https url', () => { + expect(() => ValidationService.validate('https://graph.microsoft.com/v1.0/me', [])).not.toThrow(); + }); + it('should throw error for missing version', () => { expect(() => ValidationService.validate('https://graph.microsoft.com/', [])).toThrow(ValidationError); }); diff --git a/src/modules/validation/validation-service.ts b/src/modules/validation/validation-service.ts index a09668f5f..34fce94c9 100644 --- a/src/modules/validation/validation-service.ts +++ b/src/modules/validation/validation-service.ts @@ -8,6 +8,14 @@ import { ValidatedUrl } from './abnf'; class ValidationService { + private static getHostname(queryUrl: string): string { + try { + return new URL(queryUrl).hostname; + } catch { + return ''; + } + } + private static getResourceValidationError(queryUrl: string, resources: IResource[]): string | null { if (resources.length === 0) { return null; @@ -40,8 +48,13 @@ class ValidationService { 'error'); } - const { hostname } = new URL(queryUrl); - if (!isValidHostname(hostname)) { + if (!/^https:\/\//i.test(queryUrl.trim())) { + throw new ValidationError( + `${translateMessage('The URL must begin with https://')}`, + 'error'); + } + + if (!isValidHostname(ValidationService.getHostname(queryUrl))) { throw new ValidationError( `${translateMessage('The URL must contain graph.microsoft.com')}`, 'error'); From e05db2451ed3cef7afcec17f4688c2523f645292 Mon Sep 17 00:00:00 2001 From: Claudio Castillo Date: Wed, 14 Oct 2026 12:18:02 -0700 Subject: [PATCH 3/3] 1 line clean up --- src/app/views/common/share.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/views/common/share.ts b/src/app/views/common/share.ts index dc2d981e5..7bee25a9c 100644 --- a/src/app/views/common/share.ts +++ b/src/app/views/common/share.ts @@ -1,5 +1,5 @@ -import { IQuery } from '../../../types/query-runner'; import { authenticationWrapper } from '../../../modules/authentication'; +import { IQuery } from '../../../types/query-runner'; import { encodeHashCharacters } from '../../utils/query-url-sanitization'; import { parseSampleUrl } from '../../utils/sample-url-generation';