From 992fd4bf226e209ba6791b5abf91734f4ede1d9c Mon Sep 17 00:00:00 2001 From: ichaoX <11764590+ichaoX@users.noreply.github.com> Date: Wed, 11 Jan 2023 13:22:27 +0800 Subject: [PATCH] fix(history): Resolve the href in `` correctly (#3819) * Resolve the href in `` correctly. * In hash mode, the hash in base is automatically removed and the base trailing slash is distinguished. * By default the result of `router.resolve().href` is the same as the actual switched URL. --- examples/hash-mode/app.js | 2 +- src/history/base.js | 22 +++++++++++++++------- src/history/hash.js | 12 ++++++++++++ src/history/html5.js | 6 +++--- src/router.js | 4 +++- src/util/path.js | 15 +++++++++++++-- 6 files changed, 47 insertions(+), 14 deletions(-) diff --git a/examples/hash-mode/app.js b/examples/hash-mode/app.js index 16475ffc8..ba9f3f459 100644 --- a/examples/hash-mode/app.js +++ b/examples/hash-mode/app.js @@ -40,7 +40,7 @@ const Query = { template: '
query: "{{ $route.params.q }}"
' } // 3. Create the router const router = new VueRouter({ mode: 'hash', - base: __dirname, + base: require('path').join(__dirname, '/'), routes: [ { path: '/', component: Home }, // all paths are defined without the hash. { path: '/foo', component: Foo }, diff --git a/src/history/base.js b/src/history/base.js index 4751cd97a..68d6a8898 100644 --- a/src/history/base.js +++ b/src/history/base.js @@ -272,19 +272,27 @@ function normalizeBase (base: ?string): string { if (inBrowser) { // respect tag const baseEl = document.querySelector('base') - base = (baseEl && baseEl.getAttribute('href')) || '/' - // strip full URL origin - base = base.replace(/^https?:\/\/[^\/]+/, '') + base = (baseEl && baseEl.getAttribute('href') && typeof baseEl.href === 'string') ? baseEl.href : '' + if (base) { + const href = window.location.href + const locationOrigin = href.replace(/^([^\/]+:\/\/[^\/]*)?.*$/, '$1') + const baseOrigin = base.replace(/^([^\/]+:\/\/[^\/]*)?.*$/, '$1') + if (locationOrigin === baseOrigin) { + base = base.slice(baseOrigin.length) + } else { + // XXX: hash and history modes do not support cross-origin + base = locationOrigin + } + } } else { - base = '/' + base = '' } } // make sure there's the starting slash - if (base.charAt(0) !== '/') { + if (base && base.charAt(0) !== '/' && !base.match(/^[^\/]+:\/\//)) { base = '/' + base } - // remove trailing slash - return base.replace(/\/$/, '') + return base } function resolveQueue ( diff --git a/src/history/hash.js b/src/history/hash.js index e0ddf2d7c..a4b4b71d8 100644 --- a/src/history/hash.js +++ b/src/history/hash.js @@ -10,6 +10,7 @@ import { pushState, replaceState, supportsPushState } from '../util/push-state' export class HashHistory extends History { constructor (router: Router, base: ?string, fallback: boolean) { super(router, base) + this.base = normalizeHashBase(this.base, !base) // check history fallback deeplinking if (fallback && checkFallback(this.base)) { return @@ -135,6 +136,17 @@ function getUrl (path) { return `${base}#${path}` } +function normalizeHashBase (base: string, replace: ?boolean): string { + if (!base) return '' + if (replace) { + const hasOrigin = !!base.match(/^[^\/]+:\/\/[^\/]+/) + base = window.location.href + // XXX: keep origin for possible cross-origin cases + if (!hasOrigin) base = base.replace(/^[^\/]+:\/\/[^\/]+/, '') + } + return base.replace(/#.*$/, '') +} + function pushHash (path) { if (supportsPushState) { pushState(getUrl(path)) diff --git a/src/history/html5.js b/src/history/html5.js index faaa02fcd..b20185009 100644 --- a/src/history/html5.js +++ b/src/history/html5.js @@ -58,7 +58,7 @@ export class HTML5History extends History { push (location: RawLocation, onComplete?: Function, onAbort?: Function) { const { current: fromRoute } = this this.transitionTo(location, route => { - pushState(cleanPath(this.base + route.fullPath)) + pushState(cleanPath(route.fullPath, this.base)) handleScroll(this.router, route, fromRoute, false) onComplete && onComplete(route) }, onAbort) @@ -67,7 +67,7 @@ export class HTML5History extends History { replace (location: RawLocation, onComplete?: Function, onAbort?: Function) { const { current: fromRoute } = this this.transitionTo(location, route => { - replaceState(cleanPath(this.base + route.fullPath)) + replaceState(cleanPath(route.fullPath, this.base)) handleScroll(this.router, route, fromRoute, false) onComplete && onComplete(route) }, onAbort) @@ -75,7 +75,7 @@ export class HTML5History extends History { ensureURL (push?: boolean) { if (getLocation(this.base) !== this.current.fullPath) { - const current = cleanPath(this.base + this.current.fullPath) + const current = cleanPath(this.current.fullPath, this.base) push ? pushState(current) : replaceState(current) } } diff --git a/src/router.js b/src/router.js index 5f677dc82..3a9cbb224 100644 --- a/src/router.js +++ b/src/router.js @@ -279,7 +279,9 @@ function registerHook (list: Array, fn: Function): Function { function createHref (base: string, fullPath: string, mode) { var path = mode === 'hash' ? '#' + fullPath : fullPath - return base ? cleanPath(base + '/' + path) : path + // '/main.html#/foo' should not be converted to '/main.html/#/foo' + if (base && mode !== 'hash') base = base.replace(/\/?$/, '/') + return base ? cleanPath(path, base) : path } // We cannot remove this as it would be a breaking change diff --git a/src/util/path.js b/src/util/path.js index 9d048437f..efebc8967 100644 --- a/src/util/path.js +++ b/src/util/path.js @@ -69,6 +69,17 @@ export function parsePath (path: string): { } } -export function cleanPath (path: string): string { - return path.replace(/\/(?:\s*\/)+/g, '/') +export function cleanPath (path: string, base: ?string): string { + let prefix = '' + if (base) { + // allow base to specify an origin + const match = base.match(/^((?:[^\/]+:)?\/\/)/) + if (match) { + prefix = match[0] + path = base.slice(prefix.length) + path + } else { + path = base + path + } + } + return prefix + path.replace(/\/(?:\s*\/)+/g, '/') }