Merge branch 'main' into v1-fix-ogimage

This commit is contained in:
Samy Pessé
2025-04-07 23:33:31 +02:00
8 changed files with 101 additions and 160 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"gitbook": patch
---
Fix security issue with injection of "javacript:` url in the back button of PDFs
+5
View File
@@ -0,0 +1,5 @@
---
'gitbook': patch
---
Fix OpenAPISchemas description padding
@@ -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;
}
@@ -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: {
<div className={tcls('fixed', 'left-12', 'top-12', 'print:hidden', 'z-50')}>
<a
title={tString(language, 'pdf_goback')}
href={pdfParams.back ?? linker.toAbsoluteURL(linker.toPathInSpace(''))}
href={
(pdfParams.back ? sanitizeGitBookAppURL(pdfParams.back) : null) ??
linker.toAbsoluteURL(linker.toPathInSpace(''))
}
className={tcls(
'flex',
'flex-row',
@@ -353,3 +356,13 @@ function selectPages(
});
return limitTo(allPages);
}
/**
* Create the HTML ID for the container of a page or a given anchor in it.
*/
function getPagePDFContainerId(
page: RevisionPageDocument | RevisionPageGroup,
anchor?: string
): string {
return `pdf-page-${page.id}${anchor ? `-${anchor}` : ''}`;
}
+27
View File
@@ -0,0 +1,27 @@
import { GITBOOK_APP_URL } from '@v2/lib/env';
/**
* 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();
}
/**
* Sanitize a URL to be a valid GitBook.com app URL.
*/
export function sanitizeGitBookAppURL(input: string): string | null {
if (!URL.canParse(input)) {
return null;
}
const url = new URL(input);
if (url.origin !== GITBOOK_APP_URL) {
return null;
}
return url.toString();
}
-153
View File
@@ -1,153 +0,0 @@
import 'server-only';
import {
type RevisionPage,
type RevisionPageDocument,
type RevisionPageGroup,
RevisionPageType,
} from '@gitbook/api';
import { headers } from 'next/headers';
import { GITBOOK_APP_URL } from '@v2/lib/env';
import { getPagePath } from './pages';
import { withLeadingSlash, withTrailingSlash } from './paths';
import { assertIsNotV2 } from './v2';
export interface PageHrefContext {
/**
* If defined, we are generating a PDF of the specific page IDs,
* and these pages will be rendered in the same HTML output.
*/
pdf?: string[];
}
/**
* Return the base path for the current request.
* The value will start and finish with /
*/
export async function getBasePath(): Promise<string> {
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<string> {
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<string> {
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<string> {
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<string> {
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<string> {
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<string> {
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}` : ''}`;
}
+4 -4
View File
@@ -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,
+41 -1
View File
@@ -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<string> {
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<string> {
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<string> {
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') ?? '';
}