fix: issue with navigation when operationId contains backslash or quotes (#1513)

Co-authored-by: Andrey Lomakin <lom16133@gmail.com>
Co-authored-by: Anastasiia Derymarko <anastasiia@redocly.com>
Co-authored-by: Andriy Zaleskyy <andriy.zaleskyy@redocly.com>
This commit is contained in:
Andrey Lomakin 2022-04-15 16:53:36 +03:00 committed by GitHub
parent fd8917e5c1
commit 8f7e56c747
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 64 additions and 19 deletions

View File

@ -93,7 +93,7 @@ paths:
parameters:
- name: Accept-Language
in: header
description: "The language you prefer for messages. Supported values are en-AU, en-CA, en-GB, en-US"
description: 'The language you prefer for messages. Supported values are en-AU, en-CA, en-GB, en-US'
example: en-US
required: false
schema:
@ -182,6 +182,16 @@ paths:
}
requestBody:
$ref: '#/components/requestBodies/Pet'
delete:
tags:
- pet
summary: OperationId with quotes
operationId: deletePetBy"Id
get:
tags:
- pet
summary: OperationId with backslash
operationId: delete\PetById
'/pet/{petId}':
get:
tags:
@ -259,7 +269,7 @@ paths:
required: false
schema:
type: string
example: "Bearer <TOKEN>"
example: 'Bearer <TOKEN>'
- name: petId
in: path
description: Pet id to delete
@ -432,7 +442,7 @@ paths:
application/json:
example:
status: 400
message: "Invalid Order"
message: 'Invalid Order'
requestBody:
content:
application/json:
@ -894,11 +904,11 @@ paths:
type: string
examples:
response:
value: <Message> OK </Message>
value: <Message> OK </Message>
text/plain:
examples:
response:
value: OK
value: OK
'400':
description: Invalid username/password supplied
/user/logout:
@ -925,7 +935,7 @@ components:
content:
multipart/form-data:
schema:
$ref: "#/components/schemas/Cat"
$ref: '#/components/schemas/Cat'
responses:
'200':
description: update Cat details
@ -940,7 +950,7 @@ components:
content:
multipart/form-data:
schema:
$ref: "#/components/schemas/Cat"
$ref: '#/components/schemas/Cat'
responses:
'200':
description: create Cat details
@ -1073,8 +1083,8 @@ components:
properties:
id:
externalDocs:
description: "Find more info here"
url: "https://example.com"
description: 'Find more info here'
url: 'https://example.com'
description: Pet ID
$ref: '#/components/schemas/Id'
category:
@ -1251,9 +1261,9 @@ webhooks:
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
$ref: '#/components/schemas/Pet'
responses:
"200":
'200':
description: Return a 200 status to indicate that the data was received successfully
myWebhook:
$ref: '#/components/pathItems/webhooks'

View File

@ -76,4 +76,20 @@ describe('Menu', () => {
.then($h5 => $h5[0].firstChild!.nodeValue!.trim())
.should('eq', 'Response Schema:');
});
it('should be able to open the operation details when the operation IDs have quotes', () => {
cy.visit('e2e/standalone-3-1.html');
cy.get('label span[title="pet"]').click({ multiple: true, force: true });
cy.get('li').contains('OperationId with quotes').click({ multiple: true, force: true });
cy.get('h2').contains('OperationId with quotes').should('be.visible');
cy.url().should('include', 'deletePetBy%22Id');
});
it.only('should encode URL when the operation IDs have backslashes', () => {
cy.visit('e2e/standalone-3-1.html');
cy.get('label span[title="pet"]').click({ multiple: true, force: true });
cy.get('li').contains('OperationId with backslash').click({ multiple: true, force: true });
cy.get('h2').contains('OperationId with backslash').should('be.visible');
cy.url().should('include', 'delete%5CPetById');
});
});

View File

@ -67,7 +67,7 @@ function navigate(history: HistoryService, event: React.MouseEvent<HTMLAnchorEle
!isModifiedEvent(event) // ignore clicks with modifier keys
) {
event.preventDefault();
history.replace(to);
history.replace(encodeURI(to));
}
}

View File

@ -5,7 +5,7 @@ import { SpecStore } from './models';
import { history as historyInst, HistoryService } from './HistoryService';
import { ScrollService } from './ScrollService';
import { flattenByProp, SECURITY_SCHEMES_SECTION_PREFIX } from '../utils';
import { escapeHTMLAttrChars, flattenByProp, SECURITY_SCHEMES_SECTION_PREFIX } from '../utils';
import { GROUP_DEPTH } from './MenuBuilder';
export type MenuItemGroupType = 'group' | 'tag' | 'section';
@ -47,7 +47,7 @@ export class MenuStore {
if (!id) {
return;
}
scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${id}"]`);
scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${escapeHTMLAttrChars(id)}"]`);
}
/**
@ -153,7 +153,7 @@ export class MenuStore {
item = this.flatItems.find(i => SECURITY_SCHEMES_SECTION_PREFIX.startsWith(i.id));
this.activateAndScroll(item, false);
}
this.scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${id}"]`);
this.scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${escapeHTMLAttrChars(id)}"]`);
}
};
@ -163,7 +163,7 @@ export class MenuStore {
*/
getElementAt(idx: number): Element | null {
const item = this.flatItems[idx];
return (item && querySelector(`[${SECTION_ATTR}="${item.id}"]`)) || null;
return (item && querySelector(`[${SECTION_ATTR}="${escapeHTMLAttrChars(item.id)}"]`)) || null;
}
/**
@ -175,7 +175,7 @@ export class MenuStore {
if (item && item.type === 'group') {
item = item.items[0];
}
return (item && querySelector(`[${SECTION_ATTR}="${item.id}"]`)) || null;
return (item && querySelector(`[${SECTION_ATTR}="${escapeHTMLAttrChars(item.id)}"]`)) || null;
}
/**
@ -224,7 +224,7 @@ export class MenuStore {
this.activeItemIdx = item.absoluteIdx!;
if (updateLocation) {
this.history.replace(item.id, rewriteHistory);
this.history.replace(encodeURI(item.id), rewriteHistory);
}
item.activate();

View File

@ -2330,6 +2330,20 @@ and standard method from web, mobile and desktop applications.
"openapi": "3.1.0",
"paths": Object {
"/pet": Object {
"delete": Object {
"operationId": "deletePetBy\\"Id",
"summary": "OperationId with quotes",
"tags": Array [
"pet",
],
},
"get": Object {
"operationId": "delete\\\\PetById",
"summary": "OperationId with backslash",
"tags": Array [
"pet",
],
},
"parameters": Array [
Object {
"description": "The language you prefer for messages. Supported values are en-AU, en-CA, en-GB, en-US",

View File

@ -195,8 +195,13 @@ function parseURL(url: string) {
}
}
export function escapeHTMLAttrChars(str: string): string {
return str.replace(/["\\]/g, '\\$&');
}
export function unescapeHTMLChars(str: string): string {
return str
.replace(/&#(\d+);/g, (_m, code) => String.fromCharCode(parseInt(code, 10)))
.replace(/&amp;/g, '&');
.replace(/&amp;/g, '&')
.replace(/&quot;/g, '"');
}