Improve OpenAPI server URL validation (#3937)

This commit is contained in:
Nolann B.
2026-01-26 13:45:03 +01:00
committed by GitHub
parent 58f0cc8287
commit 3ba9e46f2b
5 changed files with 53 additions and 5 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@gitbook/react-openapi": patch
---
Improve OpenAPI server URL validation
@@ -11,7 +11,7 @@ import { generateMediaTypeExamples, generateSchemaExample } from './generateSche
import { stringifyOpenAPI } from './stringifyOpenAPI'; import { stringifyOpenAPI } from './stringifyOpenAPI';
import type { OpenAPIOperationData } from './types'; import type { OpenAPIOperationData } from './types';
import { mergeHeaders } from './util/headers'; import { mergeHeaders } from './util/headers';
import { getDefaultServerURL } from './util/server'; import { getDefaultServerURL, hasValidServerHost } from './util/server';
import { import {
resolvePrefillCodePlaceholderFromSecurityScheme, resolvePrefillCodePlaceholderFromSecurityScheme,
resolveURLWithPrefillCodePlaceholdersFromServer, resolveURLWithPrefillCodePlaceholdersFromServer,
@@ -216,11 +216,14 @@ function OpenAPICodeSampleFooter(props: {
const hasMultipleMediaTypes = const hasMultipleMediaTypes =
renderers.length > 1 || renderers.some((renderer) => renderer.examples.length > 0); renderers.length > 1 || renderers.some((renderer) => renderer.examples.length > 0);
// Check if any server has a host that can be used in an HTTP request
const hasValidHost = hasValidServerHost(servers);
if (hideTryItPanel && !hasMultipleMediaTypes) { if (hideTryItPanel && !hasMultipleMediaTypes) {
return null; return null;
} }
if (!validateHttpMethod(method) || (!hasMultipleMediaTypes && servers.length === 0)) { if (!validateHttpMethod(method) || (!hasMultipleMediaTypes && !hasValidHost)) {
return null; return null;
} }
@@ -237,7 +240,7 @@ function OpenAPICodeSampleFooter(props: {
) : ( ) : (
<span /> <span />
)} )}
{!hideTryItPanel && servers.length > 0 && ( {!hideTryItPanel && hasValidHost && (
<ScalarApiButton <ScalarApiButton
context={getOpenAPIClientContext(context)} context={getOpenAPIClientContext(context)}
method={method} method={method}
@@ -18,6 +18,14 @@ export function OpenAPIMediaTypeExamplesSelector(props: {
const state = useSelectState(stateKey, renderers[0].mediaType); const state = useSelectState(stateKey, renderers[0].mediaType);
const selected = renderers.find((r) => r.mediaType === state.key) || renderers[0]; const selected = renderers.find((r) => r.mediaType === state.key) || renderers[0];
const hasMultipleMediaTypes = renderers.length >= 2;
const hasMultipleExamples = selected.examples.length >= 2;
// Only render the wrapper div if at least one selector will render
if (!hasMultipleMediaTypes && !hasMultipleExamples) {
return null;
}
return ( return (
<div className="openapi-codesample-selectors"> <div className="openapi-codesample-selectors">
<MediaTypeSelector selectIcon={selectIcon} stateKey={stateKey} renderers={renderers} /> <MediaTypeSelector selectIcon={selectIcon} stateKey={stateKey} renderers={renderers} />
+6 -2
View File
@@ -13,6 +13,7 @@ import {
} from './contentTypeChecks'; } from './contentTypeChecks';
import { json2xml } from './json2xml'; import { json2xml } from './json2xml';
import { stringifyOpenAPI } from './stringifyOpenAPI'; import { stringifyOpenAPI } from './stringifyOpenAPI';
import { isValidServerHost } from './util/server';
export interface CodeSampleInput { export interface CodeSampleInput {
method: string; method: string;
@@ -69,9 +70,12 @@ export const codeSampleGenerators: CodeSampleGenerator[] = [
const bodyString = body ? `\n${body}` : ''; const bodyString = body ? `\n${body}` : '';
// Only include Host header if origin is considered a valid server host
const hasValidHost = isValidServerHost(origin);
const hostLine = hasValidHost ? `Host: ${origin.replaceAll(/https?:\/\//g, '')}\n` : '';
const httpRequest = `${method.toUpperCase()} ${decodeURI(path)} HTTP/1.1 const httpRequest = `${method.toUpperCase()} ${decodeURI(path)} HTTP/1.1
Host: ${origin.replaceAll(/https*:\/\//g, '')} ${hostLine}${headerString}${bodyString}`;
${headerString}${bodyString}`;
return httpRequest; return httpRequest;
}, },
+28
View File
@@ -45,3 +45,31 @@ function parseServerURL(url: string) {
} }
return result; return result;
} }
/**
* Check if any server has a host that can be used in an HTTP request.
* This is used to determine if the "Try it" button should be shown.
*/
export function hasValidServerHost(servers: OpenAPIV3.ServerObject[]): boolean {
if (servers.length === 0) {
return false;
}
return servers.some((server) => {
const url = interpolateServerURL(server);
return isValidServerHost(url);
});
}
/**
* Check if the server host/URL is valid for making direct HTTP requests.
* Accepts both full URLs (with protocol) and hostnames (without protocol).
*/
export function isValidServerHost(url: string): boolean {
// Check if URL starts with http:// or https://
if (url.startsWith('http://') || url.startsWith('https://')) {
return true;
}
return /^(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/.test(url);
}