mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-04-08 02:52:39 -05:00
🐛 Fixed relative image URLs becoming absolute URLs on save (#10025)
closes #10024 - Updated input serializers for posts/tags/users to handle absolute urls conversion ------- 1. Ghost stores relative images urls 2. API V2 returns images with absolute urls 3. Ghost-Admin sends absolute urls back on any save e.g. update user **Current behavior**: This will override the relative image path in db to absolute, which in turn won't get updated in future if domain or protocol changes for e.g. **Fix**: On save/update, input serializers converts any absolute image url paths back to relative if the base URL from image fields matches the configured URL
This commit is contained in:
parent
e865d2218c
commit
915d5612a1
7 changed files with 179 additions and 0 deletions
|
@ -13,5 +13,9 @@ module.exports = {
|
|||
|
||||
get users() {
|
||||
return require('./users');
|
||||
},
|
||||
|
||||
get tags() {
|
||||
return require('./tags');
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const _ = require('lodash');
|
||||
const debug = require('ghost-ignition').debug('api:v2:utils:serializers:input:posts');
|
||||
const url = require('./utils/url');
|
||||
|
||||
module.exports = {
|
||||
all(apiConfig, frame) {
|
||||
|
@ -79,6 +80,8 @@ module.exports = {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
frame.data.posts[0] = url.forPost(Object.assign({}, frame.data.posts[0]), frame.options);
|
||||
},
|
||||
|
||||
edit(apiConfig, frame) {
|
||||
|
|
14
core/server/api/v2/utils/serializers/input/tags.js
Normal file
14
core/server/api/v2/utils/serializers/input/tags.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
const debug = require('ghost-ignition').debug('api:v2:utils:serializers:input:tags');
|
||||
const url = require('./utils/url');
|
||||
|
||||
module.exports = {
|
||||
add(apiConfig, frame) {
|
||||
debug('add');
|
||||
frame.data.tags[0] = url.forTag(Object.assign({}, frame.data.tags[0]));
|
||||
},
|
||||
|
||||
edit(apiConfig, frame) {
|
||||
debug('edit');
|
||||
this.add(apiConfig, frame);
|
||||
}
|
||||
};
|
|
@ -1,4 +1,5 @@
|
|||
const debug = require('ghost-ignition').debug('api:v2:utils:serializers:input:users');
|
||||
const url = require('./utils/url');
|
||||
|
||||
module.exports = {
|
||||
read(apiConfig, frame) {
|
||||
|
@ -19,5 +20,7 @@ module.exports = {
|
|||
if (frame.data.users[0].password) {
|
||||
delete frame.data.users[0].password;
|
||||
}
|
||||
|
||||
frame.data.users[0] = url.forUser(Object.assign({}, frame.data.users[0]));
|
||||
}
|
||||
};
|
||||
|
|
67
core/server/api/v2/utils/serializers/input/utils/url.js
Normal file
67
core/server/api/v2/utils/serializers/input/utils/url.js
Normal file
|
@ -0,0 +1,67 @@
|
|||
const {absoluteToRelative, getBlogUrl, STATIC_IMAGE_URL_PREFIX} = require('../../../../../../services/url/utils');
|
||||
|
||||
const handleImageUrl = (imageUrl) => {
|
||||
const blogUrl = getBlogUrl().replace(/^http(s?):\/\//, '').replace(/\/$/, '');
|
||||
const imageUrlAbsolute = imageUrl.replace(/^http(s?):\/\//, '');
|
||||
const imagePathRe = new RegExp(`^${blogUrl}/${STATIC_IMAGE_URL_PREFIX}`);
|
||||
if (imagePathRe.test(imageUrlAbsolute)) {
|
||||
return absoluteToRelative(imageUrl);
|
||||
}
|
||||
return imageUrl;
|
||||
};
|
||||
|
||||
const forPost = (attrs, options) => {
|
||||
if (attrs.feature_image) {
|
||||
attrs.feature_image = handleImageUrl(attrs.feature_image);
|
||||
}
|
||||
|
||||
if (attrs.og_image) {
|
||||
attrs.og_image = handleImageUrl(attrs.og_image);
|
||||
}
|
||||
|
||||
if (attrs.twitter_image) {
|
||||
attrs.twitter_image = handleImageUrl(attrs.twitter_image);
|
||||
}
|
||||
|
||||
if (options && options.withRelated) {
|
||||
options.withRelated.forEach((relation) => {
|
||||
if (relation === 'tags' && attrs.tags) {
|
||||
attrs.tags = attrs.tags.map(tag => forTag(tag));
|
||||
}
|
||||
|
||||
if (relation === 'author' && attrs.author) {
|
||||
attrs.author = forUser(attrs.author, options);
|
||||
}
|
||||
|
||||
if (relation === 'authors' && attrs.authors) {
|
||||
attrs.authors = attrs.authors.map(author => forUser(author, options));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return attrs;
|
||||
};
|
||||
|
||||
const forUser = (attrs) => {
|
||||
if (attrs.profile_image) {
|
||||
attrs.profile_image = handleImageUrl(attrs.profile_image);
|
||||
}
|
||||
|
||||
if (attrs.cover_image) {
|
||||
attrs.cover_image = handleImageUrl(attrs.cover_image);
|
||||
}
|
||||
|
||||
return attrs;
|
||||
};
|
||||
|
||||
const forTag = (attrs) => {
|
||||
if (attrs.feature_image) {
|
||||
attrs.feature_image = handleImageUrl(attrs.feature_image);
|
||||
}
|
||||
|
||||
return attrs;
|
||||
};
|
||||
|
||||
module.exports.forPost = forPost;
|
||||
module.exports.forUser = forUser;
|
||||
module.exports.forTag = forTag;
|
|
@ -465,6 +465,7 @@ module.exports.redirect301 = redirect301;
|
|||
module.exports.createUrl = createUrl;
|
||||
module.exports.deduplicateDoubleSlashes = deduplicateDoubleSlashes;
|
||||
module.exports.getApiPath = getApiPath;
|
||||
module.exports.getBlogUrl = getBlogUrl;
|
||||
|
||||
/**
|
||||
* If you request **any** image in Ghost, it get's served via
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const should = require('should');
|
||||
const serializers = require('../../../../../../../server/api/v2/utils/serializers');
|
||||
const configUtils = require('../../../../../../utils/configUtils');
|
||||
|
||||
describe('Unit: v2/utils/serializers/input/posts', function () {
|
||||
it('default', function () {
|
||||
|
@ -75,4 +76,90 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
|
|||
serializers.input.posts.all(apiConfig, frame);
|
||||
frame.options.filter.should.eql('page:false');
|
||||
});
|
||||
|
||||
describe('Ensure relative urls are returned for standard image urls', function () {
|
||||
after(function () {
|
||||
configUtils.restore();
|
||||
});
|
||||
|
||||
it('when blog url is without subdir', function () {
|
||||
configUtils.set({url: 'https://mysite.com'});
|
||||
const apiConfig = {};
|
||||
const frame = {
|
||||
options: {
|
||||
context: {
|
||||
user: 0,
|
||||
api_key_id: 1
|
||||
},
|
||||
withRelated: ['tags', 'authors']
|
||||
},
|
||||
data: {
|
||||
posts: [
|
||||
{
|
||||
id: 'id1',
|
||||
feature_image: 'https://mysite.com/content/images/image.jpg',
|
||||
og_image: 'https://mysite.com/mycustomstorage/images/image.jpg',
|
||||
twitter_image: 'https://mysite.com/blog/content/images/image.jpg',
|
||||
tags: [{
|
||||
id: 'id3',
|
||||
feature_image: 'http://mysite.com/content/images/image.jpg'
|
||||
}],
|
||||
authors: [{
|
||||
id: 'id4',
|
||||
name: 'Ghosty',
|
||||
profile_image: 'https://somestorage.com/blog/images/image.jpg'
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
serializers.input.posts.edit(apiConfig, frame);
|
||||
let postData = frame.data.posts[0];
|
||||
postData.feature_image.should.eql('/content/images/image.jpg');
|
||||
postData.og_image.should.eql('https://mysite.com/mycustomstorage/images/image.jpg');
|
||||
postData.twitter_image.should.eql('https://mysite.com/blog/content/images/image.jpg');
|
||||
postData.tags[0].feature_image.should.eql('/content/images/image.jpg');
|
||||
postData.authors[0].profile_image.should.eql('https://somestorage.com/blog/images/image.jpg');
|
||||
});
|
||||
|
||||
it('when blog url is with subdir', function () {
|
||||
configUtils.set({url: 'https://mysite.com/blog'});
|
||||
const apiConfig = {};
|
||||
const frame = {
|
||||
options: {
|
||||
context: {
|
||||
user: 0,
|
||||
api_key_id: 1
|
||||
},
|
||||
withRelated: ['tags', 'authors']
|
||||
},
|
||||
data: {
|
||||
posts: [
|
||||
{
|
||||
id: 'id1',
|
||||
feature_image: 'https://mysite.com/blog/content/images/image.jpg',
|
||||
og_image: 'https://mysite.com/content/images/image.jpg',
|
||||
twitter_image: 'https://mysite.com/mycustomstorage/images/image.jpg',
|
||||
tags: [{
|
||||
id: 'id3',
|
||||
feature_image: 'http://mysite.com/blog/mycustomstorage/content/images/image.jpg'
|
||||
}],
|
||||
authors: [{
|
||||
id: 'id4',
|
||||
name: 'Ghosty',
|
||||
profile_image: 'https://somestorage.com/blog/content/images/image.jpg'
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
serializers.input.posts.edit(apiConfig, frame);
|
||||
let postData = frame.data.posts[0];
|
||||
postData.feature_image.should.eql('/blog/content/images/image.jpg');
|
||||
postData.og_image.should.eql('https://mysite.com/content/images/image.jpg');
|
||||
postData.twitter_image.should.eql('https://mysite.com/mycustomstorage/images/image.jpg');
|
||||
postData.tags[0].feature_image.should.eql('http://mysite.com/blog/mycustomstorage/content/images/image.jpg');
|
||||
postData.authors[0].profile_image.should.eql('https://somestorage.com/blog/content/images/image.jpg');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue