mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-16 15:45:13 +00:00
Merge branch 'main' into v1-fix-ogimage
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"gitbook": patch
|
||||
---
|
||||
|
||||
Fix security issue with injection of "javacript:` url in the back button of PDFs
|
||||
@@ -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}` : ''}`;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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}` : ''}`;
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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') ?? '';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user