From de539468963272eff51e194c6b7b1156a715ed11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Mon, 7 Apr 2025 17:03:09 +0200 Subject: [PATCH 1/2] Sanitize the back URL used when rendering PDF (#3111) --- .changeset/fair-actors-remain.md | 5 + .../gitbook/src/components/PDF/PDFPage.tsx | 17 +- packages/gitbook/src/lib/app.ts | 27 ++++ packages/gitbook/src/lib/links.ts | 153 ------------------ packages/gitbook/src/lib/references.tsx | 8 +- packages/gitbook/src/lib/v1.ts | 42 ++++- 6 files changed, 92 insertions(+), 160 deletions(-) create mode 100644 .changeset/fair-actors-remain.md create mode 100644 packages/gitbook/src/lib/app.ts delete mode 100644 packages/gitbook/src/lib/links.ts diff --git a/.changeset/fair-actors-remain.md b/.changeset/fair-actors-remain.md new file mode 100644 index 000000000..e77aac777 --- /dev/null +++ b/.changeset/fair-actors-remain.md @@ -0,0 +1,5 @@ +--- +"gitbook": patch +--- + +Fix security issue with injection of "javacript:` url in the back button of PDFs diff --git a/packages/gitbook/src/components/PDF/PDFPage.tsx b/packages/gitbook/src/components/PDF/PDFPage.tsx index 081fadc02..96cb784cc 100644 --- a/packages/gitbook/src/components/PDF/PDFPage.tsx +++ b/packages/gitbook/src/components/PDF/PDFPage.tsx @@ -20,7 +20,6 @@ import { TrademarkLink } from '@/components/TableOfContents/Trademark'; import type { PolymorphicComponentProp } from '@/components/utils/types'; import { getSpaceLanguage } from '@/intl/server'; import { tString } from '@/intl/translate'; -import { getPagePDFContainerId } from '@/lib/links'; import { resolvePageId } from '@/lib/pages'; import { tcls } from '@/lib/tailwind'; import { defaultCustomization } from '@/lib/utils'; @@ -29,6 +28,7 @@ import { type PDFSearchParams, getPDFSearchParams } from './urls'; import { PageControlButtons } from './PageControlButtons'; import { PrintButton } from './PrintButton'; import './pdf.css'; +import { sanitizeGitBookAppURL } from '@/lib/app'; const DEFAULT_LIMIT = 100; @@ -92,7 +92,10 @@ export async function PDFPage(props: {
{ - assertIsNotV2(); - const headersList = await headers(); - const path = headersList.get('x-gitbook-basepath') ?? '/'; - - return withTrailingSlash(withLeadingSlash(path)); -} - -/** - * Return the site base path for the current request. - * The value will start and finish with / - */ -export async function getSiteBasePath(): Promise { - assertIsNotV2(); - const headersList = await headers(); - const path = headersList.get('x-gitbook-site-basepath') ?? '/'; - - return withTrailingSlash(withLeadingSlash(path)); -} - -/** - * Return the current host for the current request. - */ -export async function getHost(): Promise { - assertIsNotV2(); - const headersList = await headers(); - const mode = headersList.get('x-gitbook-mode'); - if (mode === 'proxy') { - return headersList.get('x-forwarded-host') ?? ''; - } - - return headersList.get('x-gitbook-host') ?? headersList.get('host') ?? ''; -} - -/** - * Return the root URL for the GitBook Open instance (not the content). - * Use `baseUrl` to get the base URL for the current content. - * - * The URL will end with "/". - */ -export async function getRootUrl(): Promise { - assertIsNotV2(); - const [headersList, host] = await Promise.all([headers(), getHost()]); - const protocol = headersList.get('x-forwarded-proto') ?? 'https'; - let path = headersList.get('x-gitbook-origin-basepath') ?? '/'; - - if (!path.startsWith('/')) { - path = `/${path}`; - } - - if (!path.endsWith('/')) { - path = `${path}/`; - } - - return `${protocol}://${host}${path}`; -} - -/** - * Return the base URL for the current content. - * The URL will end with "/". - */ -export async function getBaseUrl(): Promise { - assertIsNotV2(); - const [headersList, host, basePath] = await Promise.all([headers(), getHost(), getBasePath()]); - const protocol = headersList.get('x-forwarded-proto') ?? 'https'; - return `${protocol}://${host}${basePath}`; -} - -/** - * Create an absolute href in the current content. - */ -export async function getAbsoluteHref(href: string, withHost = false): Promise { - assertIsNotV2(); - const base = withHost ? await getBaseUrl() : await getBasePath(); - return `${base}${href.startsWith('/') ? href.slice(1) : href}`; -} - -/** - * Create an absolute href in the GitBook application. - */ -export function getGitbookAppHref(pathname: string): string { - const appUrl = new URL(GITBOOK_APP_URL); - appUrl.pathname = pathname; - - return appUrl.toString(); -} - -/** - * Create a link to a page path in the current space. - */ -export async function getPageHref( - rootPages: RevisionPage[], - page: RevisionPageDocument | RevisionPageGroup, - context: PageHrefContext = {}, - /** Anchor to link to in the page. */ - anchor?: string -): Promise { - assertIsNotV2(); - const { pdf } = context; - - if (pdf) { - if (pdf.includes(page.id)) { - return `#${getPagePDFContainerId(page, anchor)}`; - } - if (page.type === RevisionPageType.Group) { - return '#'; - } - - // Use an absolute URL to the page - return page.urls.app; - } - - const href = - (await getAbsoluteHref(getPagePath(rootPages, page))) + (anchor ? `#${anchor}` : ''); - return href; -} - -/** - * Create the HTML ID for the container of a page during a PDF rendering. - */ -export function getPagePDFContainerId( - page: RevisionPageDocument | RevisionPageGroup, - anchor?: string -): string { - return `pdf-page-${page.id}${anchor ? `-${anchor}` : ''}`; -} diff --git a/packages/gitbook/src/lib/references.tsx b/packages/gitbook/src/lib/references.tsx index 9b1e469b6..ab941e43c 100644 --- a/packages/gitbook/src/lib/references.tsx +++ b/packages/gitbook/src/lib/references.tsx @@ -15,8 +15,8 @@ import type React from 'react'; import { PageIcon } from '@/components/PageIcon'; +import { getGitBookAppHref } from './app'; import { getBlockById, getBlockTitle } from './document'; -import { getGitbookAppHref } from './links'; import { resolvePageId } from './pages'; import { findSiteSpaceById } from './sites'; import type { ClassValue } from './tailwind'; @@ -194,7 +194,7 @@ export async function resolveContentRef( if (!targetSpace) { return { - href: getGitbookAppHref(`/s/${contentRef.space}`), + href: getGitBookAppHref(`/s/${contentRef.space}`), text: 'space', active: false, }; @@ -224,7 +224,7 @@ export async function resolveContentRef( case 'collection': { return { - href: getGitbookAppHref('/home'), + href: getGitBookAppHref('/home'), text: 'collection', active: false, }; @@ -242,7 +242,7 @@ export async function resolveContentRef( return null; } return { - href: getGitbookAppHref(`/s/${space.id}`), + href: getGitBookAppHref(`/s/${space.id}`), text: reusableContent.title, active: false, reusableContent, diff --git a/packages/gitbook/src/lib/v1.ts b/packages/gitbook/src/lib/v1.ts index 625eef0a4..ea5219816 100644 --- a/packages/gitbook/src/lib/v1.ts +++ b/packages/gitbook/src/lib/v1.ts @@ -9,6 +9,7 @@ import { createImageResizer } from '@v2/lib/images'; import { createLinker } from '@v2/lib/links'; import { DataFetcherError, wrapDataFetcherError } from '@v2/lib/data'; +import { headers } from 'next/headers'; import { type SiteContentPointer, type SpaceContentPointer, @@ -31,7 +32,8 @@ import { searchSiteContent, } from './api'; import { getDynamicCustomizationSettings } from './customization'; -import { getBasePath, getHost, getSiteBasePath } from './links'; +import { withLeadingSlash, withTrailingSlash } from './paths'; +import { assertIsNotV2 } from './v2'; /* * Code that will be used until the migration to v2 is complete. @@ -329,3 +331,41 @@ export function getSitePointerFromContext(context: GitBookSiteContext): SiteCont siteShareKey: context.shareKey, }; } + +/** + * Return the base path for the current request. + * The value will start and finish with / + */ +async function getBasePath(): Promise { + assertIsNotV2(); + const headersList = await headers(); + const path = headersList.get('x-gitbook-basepath') ?? '/'; + + return withTrailingSlash(withLeadingSlash(path)); +} + +/** + * Return the site base path for the current request. + * The value will start and finish with / + */ +async function getSiteBasePath(): Promise { + assertIsNotV2(); + const headersList = await headers(); + const path = headersList.get('x-gitbook-site-basepath') ?? '/'; + + return withTrailingSlash(withLeadingSlash(path)); +} + +/** + * Return the current host for the current request. + */ +async function getHost(): Promise { + assertIsNotV2(); + const headersList = await headers(); + const mode = headersList.get('x-gitbook-mode'); + if (mode === 'proxy') { + return headersList.get('x-forwarded-host') ?? ''; + } + + return headersList.get('x-gitbook-host') ?? headersList.get('host') ?? ''; +} From aa3357a57d99e2946c67d6306978d2bf988838fb Mon Sep 17 00:00:00 2001 From: "Nolann B." <100787331+nolannbiron@users.noreply.github.com> Date: Mon, 7 Apr 2025 21:54:33 +0200 Subject: [PATCH 2/2] Fix OpenAPISchemas description padding (#3115) --- .changeset/lazy-oranges-play.md | 5 +++++ .../gitbook/src/components/DocumentView/OpenAPI/style.css | 4 ++++ 2 files changed, 9 insertions(+) create mode 100644 .changeset/lazy-oranges-play.md diff --git a/.changeset/lazy-oranges-play.md b/.changeset/lazy-oranges-play.md new file mode 100644 index 000000000..a965a9300 --- /dev/null +++ b/.changeset/lazy-oranges-play.md @@ -0,0 +1,5 @@ +--- +'gitbook': patch +--- + +Fix OpenAPISchemas description padding diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/style.css b/packages/gitbook/src/components/DocumentView/OpenAPI/style.css index e0b5488e6..ad32f7e45 100644 --- a/packages/gitbook/src/components/DocumentView/OpenAPI/style.css +++ b/packages/gitbook/src/components/DocumentView/OpenAPI/style.css @@ -156,6 +156,10 @@ @apply prose-sm text-balance mt-1.5 !text-[0.813rem] text-tint overflow-hidden !font-normal select-text prose-strong:font-semibold prose-strong:text-inherit; } +.openapi-section-schemas > .openapi-section-body > .openapi-schema-root-description { + @apply px-2.5 pt-1 !text-sm; +} + .openapi-schema-properties { @apply flex flex-col; }