mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-24 23:48:13 -05:00
🐛 Fixed navigation url inputs when configured URL has special characters (#941)
closes https://github.com/TryGhost/Ghost/issues/9373 - using an `<a>` element to parse a URL does not behave as expected when the URL has special characters because the `host` attribute will show the Puny URL version. Eg. `exämple.com` will become `xn--exmple-cua.com` - `{{gh-navitem-url-input}}` was failing to manipulate the URL value because of the difference between the Puny URL encoded URL and the raw configured URL with unicode chars - uses the `URI` module that's bundled with the imported version of `google-caja` to parse the URL via regexes rather than relying on native browser parsing
This commit is contained in:
parent
85d4759044
commit
cc33fa899d
3 changed files with 48 additions and 15 deletions
|
@ -3,6 +3,10 @@ import {InvokeActionMixin} from 'ember-invoke-action';
|
||||||
import {computed} from '@ember/object';
|
import {computed} from '@ember/object';
|
||||||
import {run} from '@ember/runloop';
|
import {run} from '@ember/runloop';
|
||||||
|
|
||||||
|
// URI is attached to the window global as part of the
|
||||||
|
// google-caja html-css-sanitizer-bundle
|
||||||
|
const {URI} = window;
|
||||||
|
|
||||||
let joinUrlParts = function (url, path) {
|
let joinUrlParts = function (url, path) {
|
||||||
if (path[0] !== '/' && url.slice(-1) !== '/') {
|
if (path[0] !== '/' && url.slice(-1) !== '/') {
|
||||||
path = `/${path}`;
|
path = `/${path}`;
|
||||||
|
@ -86,17 +90,26 @@ export default TextField.extend(InvokeActionMixin, {
|
||||||
|
|
||||||
notifyUrlChanged() {
|
notifyUrlChanged() {
|
||||||
let url = this.get('value').trim();
|
let url = this.get('value').trim();
|
||||||
let urlParts = document.createElement('a');
|
let urlURI = URI.parse(url);
|
||||||
let baseUrl = this.get('baseUrl');
|
let baseUrl = this.get('baseUrl');
|
||||||
let baseUrlParts = document.createElement('a');
|
let baseURI = URI.parse(baseUrl);
|
||||||
|
|
||||||
|
function getHost(uri) {
|
||||||
|
let host = uri.getDomain();
|
||||||
|
|
||||||
|
if (uri.getPort()) {
|
||||||
|
host = `${host}:${uri.getPort()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
let urlHost = getHost(urlURI);
|
||||||
|
let baseHost = getHost(baseURI);
|
||||||
|
|
||||||
// ensure value property is trimmed
|
// ensure value property is trimmed
|
||||||
this.set('value', url);
|
this.set('value', url);
|
||||||
|
|
||||||
// leverage the browser's native URI parsing
|
|
||||||
urlParts.href = url;
|
|
||||||
baseUrlParts.href = baseUrl;
|
|
||||||
|
|
||||||
// if we have an email address, add the mailto:
|
// if we have an email address, add the mailto:
|
||||||
if (validator.isEmail(url)) {
|
if (validator.isEmail(url)) {
|
||||||
url = `mailto:${url}`;
|
url = `mailto:${url}`;
|
||||||
|
@ -110,12 +123,12 @@ export default TextField.extend(InvokeActionMixin, {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get our baseUrl relativity checks in order
|
// get our baseUrl relativity checks in order
|
||||||
let isOnSameHost = urlParts.host === baseUrlParts.host;
|
let isOnSameHost = urlHost === baseHost;
|
||||||
let isAnchorLink = url.match(/^#/);
|
let isAnchorLink = url.match(/^#/);
|
||||||
let isRelativeToBasePath = urlParts.pathname.indexOf(baseUrlParts.pathname) === 0;
|
let isRelativeToBasePath = urlURI.getPath() && urlURI.getPath().indexOf(baseURI.getPath()) === 0;
|
||||||
|
|
||||||
// if our pathname is only missing a trailing / mark it as relative
|
// if our path is only missing a trailing / mark it as relative
|
||||||
if (`${urlParts.pathname}/` === baseUrlParts.pathname) {
|
if (`${urlURI.getPath()}/` === baseURI.getPath()) {
|
||||||
isRelativeToBasePath = true;
|
isRelativeToBasePath = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,12 +136,12 @@ export default TextField.extend(InvokeActionMixin, {
|
||||||
if (!isAnchorLink && isOnSameHost && isRelativeToBasePath) {
|
if (!isAnchorLink && isOnSameHost && isRelativeToBasePath) {
|
||||||
url = url.replace(/^[a-zA-Z0-9-]+:/, '');
|
url = url.replace(/^[a-zA-Z0-9-]+:/, '');
|
||||||
url = url.replace(/^\/\//, '');
|
url = url.replace(/^\/\//, '');
|
||||||
url = url.replace(baseUrlParts.host, '');
|
url = url.replace(baseHost, '');
|
||||||
url = url.replace(baseUrlParts.pathname, '');
|
url = url.replace(baseURI.getPath(), '');
|
||||||
|
|
||||||
// handle case where url path is same as baseUrl path but missing trailing slash
|
// handle case where url path is same as baseUrl path but missing trailing slash
|
||||||
if (urlParts.pathname.slice(-1) !== '/') {
|
if (urlURI.getPath().slice(-1) !== '/') {
|
||||||
url = url.replace(baseUrlParts.pathname.slice(0, -1), '');
|
url = url.replace(baseURI.getPath().slice(0, -1), '');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url !== '' || !this.get('isNew')) {
|
if (url !== '' || !this.get('isNew')) {
|
||||||
|
|
|
@ -157,7 +157,7 @@ describe('Acceptance: Settings - Design', function () {
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
find('.gh-blognav-url:last input').val()
|
find('.gh-blognav-url:last input').val()
|
||||||
).to.equal(`${window.location.protocol}//${window.location.host}/new/`);
|
).to.equal(`${window.location.protocol}//${window.location.host}/new`);
|
||||||
|
|
||||||
await click('.gh-blognav-add');
|
await click('.gh-blognav-add');
|
||||||
|
|
||||||
|
|
|
@ -187,6 +187,26 @@ describe('Integration: Component: gh-navitem-url-input', function () {
|
||||||
expect($input.val()).to.equal(`${currentUrl} /test`);
|
expect($input.val()).to.equal(`${currentUrl} /test`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// https://github.com/TryGhost/Ghost/issues/9373
|
||||||
|
it('doesn\'t mangle urls when baseUrl has unicode characters', function () {
|
||||||
|
this.on('updateUrl', () => {
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.set('baseUrl', 'http://exämple.com');
|
||||||
|
|
||||||
|
this.render(hbs`
|
||||||
|
{{gh-navitem-url-input baseUrl=baseUrl url=url isNew=isNew update=(action "updateUrl") clearErrors=(action "clearErrors")}}
|
||||||
|
`);
|
||||||
|
let $input = this.$('input');
|
||||||
|
|
||||||
|
run(() => {
|
||||||
|
$input.val(`${currentUrl}/test`).trigger('input').trigger('blur');
|
||||||
|
});
|
||||||
|
|
||||||
|
expect($input.val()).to.equal(`${currentUrl}/test`);
|
||||||
|
});
|
||||||
|
|
||||||
it('triggers "update" action on blur', function () {
|
it('triggers "update" action on blur', function () {
|
||||||
let changeActionCallCount = 0;
|
let changeActionCallCount = 0;
|
||||||
this.on('updateUrl', () => {
|
this.on('updateUrl', () => {
|
||||||
|
|
Loading…
Add table
Reference in a new issue