Skip to content

Commit

Permalink
extract relative url parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
Janpot committed Jul 4, 2020
1 parent 2e9cdc8 commit 8cf6b12
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 39 deletions.
12 changes: 6 additions & 6 deletions packages/next/client/page-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import mitt from '../next-server/lib/mitt'
import { isDynamicRoute } from './../next-server/lib/router/utils/is-dynamic'
import { getRouteMatcher } from './../next-server/lib/router/utils/route-matcher'
import { getRouteRegex } from './../next-server/lib/router/utils/route-regex'
import { searchParamsToUrlQuery } from './../next-server/lib/router/utils/search-params-to-url-query'
import { parseRelativeUrl } from './../next-server/lib/router/utils/parse-relative-url'
import getAssetPathFromRoute from './../next-server/lib/router/utils/get-asset-path-from-route'
import searchParamsToUrlQuery from './../next-server/lib/router/utils/search-params-to-url-query'

function hasRel(rel, link) {
try {
Expand Down Expand Up @@ -110,12 +111,11 @@ export default class PageLoader {
* @param {string} asPath the URL as shown in browser (virtual path); used for dynamic routes
*/
getDataHref(href, asPath, ssg) {
const { pathname: hrefPathname, searchParams, search } = new URL(
href,
'http://n'
const { pathname: hrefPathname, searchParams, search } = parseRelativeUrl(
href
)
const query = searchParamsToUrlQuery(searchParams)
const { pathname: asPathname } = new URL(asPath, 'http://n')
const { pathname: asPathname } = parseRelativeUrl(asPath)
const route = normalizeRoute(hrefPathname)

const getHrefForSlug = (/** @type string */ path) => {
Expand Down Expand Up @@ -180,7 +180,7 @@ export default class PageLoader {
* @param {string} asPath the URL as shown in browser (virtual path); used for dynamic routes
*/
prefetchData(href, asPath) {
const { pathname: hrefPathname } = new URL(href, 'http://n')
const { pathname: hrefPathname } = parseRelativeUrl(href)
const route = normalizeRoute(hrefPathname)
return this.promisedSsgManifest.then(
(s, _dataHref) =>
Expand Down
59 changes: 27 additions & 32 deletions packages/next/next-server/lib/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import {
import { isDynamicRoute } from './utils/is-dynamic'
import { getRouteMatcher } from './utils/route-matcher'
import { getRouteRegex } from './utils/route-regex'
import searchParamsToUrlQuery from './utils/search-params-to-url-query'
import { searchParamsToUrlQuery } from './utils/search-params-to-url-query'
import { parseRelativeUrl } from './utils/parse-relative-url'
import {
normalizeTrailingSlash,
removePathTrailingSlash,
Expand Down Expand Up @@ -62,12 +63,19 @@ function prepareUrlAs(router: NextRouter, url: Url, as: Url) {
}
}

function parseRelativeUrl(url: string) {
const parsed = new URL(url, 'http://n')
if (parsed.origin !== 'http://n') {
throw new Error(`Absolute URL not allowed: "${url}"`)
function tryParseRelativeUrl(
url: string
): null | ReturnType<typeof parseRelativeUrl> {
try {
return parseRelativeUrl(url)
} catch (err) {
if (process.env.NODE_ENV !== 'production') {
throw new Error(
`Invalid href passed to router: ${url} https://err.sh/vercel/next.js/invalid-href-passed`
)
}
return null
}
return parsed
}

type ComponentRes = { page: ComponentType; mod: any }
Expand Down Expand Up @@ -302,7 +310,7 @@ export default class Router implements BaseRouter {
}

const { url, as, options } = e.state
const { pathname } = new URL(url, 'http://n')
const { pathname } = parseRelativeUrl(url)

// Make sure we don't re-render on initial load,
// can be caused by navigating back from an external site
Expand Down Expand Up @@ -427,18 +435,11 @@ export default class Router implements BaseRouter {
return resolve(true)
}

let pathname: string
let searchParams: URLSearchParams
try {
;({ pathname, searchParams } = parseRelativeUrl(url))
} catch (err) {
if (process.env.NODE_ENV !== 'production') {
throw new Error(
`Invalid href passed to router: ${url} https://err.sh/vercel/next.js/invalid-href-passed`
)
}
return
}
const parsed = tryParseRelativeUrl(url)

if (!parsed) return

let { pathname, searchParams } = parsed
const query = searchParamsToUrlQuery(searchParams)

// url and as should always be prefixed with basePath by this
Expand All @@ -462,7 +463,7 @@ export default class Router implements BaseRouter {
const cleanedAs = delBasePath(as)

if (isDynamicRoute(route)) {
const { pathname: asPathname } = new URL(cleanedAs, 'http://n')
const { pathname: asPathname } = parseRelativeUrl(cleanedAs)
const routeRegex = getRouteRegex(route)
const routeMatch = getRouteMatcher(routeRegex)(asPathname)
if (!routeMatch) {
Expand Down Expand Up @@ -781,17 +782,11 @@ export default class Router implements BaseRouter {
options: PrefetchOptions = {}
): Promise<void> {
return new Promise((resolve, reject) => {
let pathname: string
try {
;({ pathname } = parseRelativeUrl(url))
} catch (err) {
if (process.env.NODE_ENV !== 'production') {
throw new Error(
`Invalid href passed to router: ${url} https://err.sh/vercel/next.js/invalid-href-passed`
)
}
return
}
const parsed = tryParseRelativeUrl(url)

if (!parsed) return

const { pathname } = parsed

// Prefetch is not supported in development mode because it would trigger on-demand-entries
if (process.env.NODE_ENV !== 'production') {
Expand Down Expand Up @@ -850,7 +845,7 @@ export default class Router implements BaseRouter {
}

_getStaticData = (dataHref: string): Promise<object> => {
let { pathname } = new URL(dataHref, 'http://n')
let { pathname } = parseRelativeUrl(dataHref)
pathname = prepareRoute(pathname)

return process.env.NODE_ENV === 'production' && this.sdc[pathname]
Expand Down
24 changes: 24 additions & 0 deletions packages/next/next-server/lib/router/utils/parse-relative-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const DUMMY_BASE = new URL('http://n')

/**
* Parses path-relative urls (e.g. `/hello/world?foo=bar`). If url isn't path-relative
* (e.g. `./hello`) then at least base must be.
* Absolute urls are rejected.
*/
export function parseRelativeUrl(url: string, base?: string) {
const resolvedBase = base ? new URL(base, DUMMY_BASE) : DUMMY_BASE
const { pathname, searchParams, search, hash, href, origin } = new URL(
url,
resolvedBase
)
if (origin !== DUMMY_BASE.origin) {
throw new Error('Invalid relative URL')
}
return {
pathname,
searchParams,
search,
hash,
href: href.slice(DUMMY_BASE.origin.length),
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ParsedUrlQuery } from 'querystring'

export default function searchParamsToUrlQuery(
export function searchParamsToUrlQuery(
searchParams: URLSearchParams
): ParsedUrlQuery {
const query: ParsedUrlQuery = {}
Expand Down

0 comments on commit 8cf6b12

Please sign in to comment.