Fix image webhook rewriting for uploads

This rewrote URLs to be absolute URLs. However, for uploads (the most
common case), we actually need them to point to not just the GitLab
instance, but the project they're from. Thankfully, we can normally get
that information from the object we're building the hook for.
This commit is contained in:
Sean McGivern 2018-10-15 11:42:15 +01:00
parent c7fd95584e
commit 0bcfd0adb3
6 changed files with 113 additions and 53 deletions

View File

@ -0,0 +1,5 @@
---
title: Fix auto-corrected upload URLs in webhooks
merge_request: 22361
author:
type: fixed

View File

@ -73,8 +73,8 @@ Below are described the supported events.
Triggered when you push to the repository except when pushing tags.
> **Note:** When more than 20 commits are pushed at once, the `commits` web hook
attribute will only contain the first 20 for performance reasons. Loading
> **Note:** When more than 20 commits are pushed at once, the `commits` web hook
attribute will only contain the first 20 for performance reasons. Loading
detailed commit data is expensive. Note that despite only 20 commits being
present in the `commits` attribute, the `total_commits_count` attribute will
contain the actual total.
@ -1157,10 +1157,11 @@ its description:
```
It will appear in the webhook body as the below (assuming that GitLab is
installed at gitlab.example.com):
installed at gitlab.example.com, and the project is at
example-group/example-project):
```markdown
![image](https://gitlab.example.com/uploads/$sha/image.png)
![image](https://gitlab.example.com/example-group/example-project/uploads/$sha/image.png)
```
This will not rewrite URLs that already are pointing to HTTP, HTTPS, or

View File

@ -25,6 +25,7 @@ module Gitlab
markdown_text.gsub(MARKDOWN_SIMPLE_IMAGE) do
if $~[:image]
url = $~[:url]
url = "#{uploads_prefix}#{url}" if url.start_with?('/uploads')
url = "/#{url}" unless url.start_with?('/')
"![#{$~[:title]}](#{Gitlab.config.gitlab.url}#{url})"
@ -33,6 +34,16 @@ module Gitlab
end
end
end
def uploads_prefix
project&.full_path || ''
end
def project
return unless object.respond_to?(:project)
object.project
end
end
end
end

View File

@ -8,57 +8,94 @@ describe Gitlab::HookData::BaseBuilder do
end
end
subject { subclass.new(nil) }
using RSpec::Parameterized::TableSyntax
where do
{
'relative image URL' => {
input: '![an image](foo.png)',
output: "![an image](#{Gitlab.config.gitlab.url}/foo.png)"
},
'HTTP URL' => {
input: '![an image](http://example.com/foo.png)',
output: '![an image](http://example.com/foo.png)'
},
'HTTPS URL' => {
input: '![an image](https://example.com/foo.png)',
output: '![an image](https://example.com/foo.png)'
},
'protocol-relative URL' => {
input: '![an image](//example.com/foo.png)',
output: '![an image](//example.com/foo.png)'
},
'URL reference by title' => {
input: "![foo]\n\n[foo]: foo.png",
output: "![foo]\n\n[foo]: foo.png"
},
'URL reference by label' => {
input: "![][foo]\n\n[foo]: foo.png",
output: "![][foo]\n\n[foo]: foo.png"
},
'in Markdown inline code block' => {
input: '`![an image](foo.png)`',
output: "`![an image](#{Gitlab.config.gitlab.url}/foo.png)`"
},
'in HTML tag on the same line' => {
input: '<p>![an image](foo.png)</p>',
output: "<p>![an image](#{Gitlab.config.gitlab.url}/foo.png)</p>"
},
'in Markdown multi-line code block' => {
input: "```\n![an image](foo.png)\n```",
output: "```\n![an image](foo.png)\n```"
},
'in HTML tag on different lines' => {
input: "<p>\n![an image](foo.png)\n</p>",
output: "<p>\n![an image](foo.png)\n</p>"
context 'with an upload prefix specified' do
let(:project_with_path) { double(full_path: 'baz/bar') }
let(:object_with_project) { double(project: project_with_path) }
subject { subclass.new(object_with_project) }
where do
{
'relative image URL' => {
input: '![an image](foo.png)',
output: "![an image](#{Gitlab.config.gitlab.url}/foo.png)"
},
'absolute upload URL' => {
input: '![an image](/uploads/foo.png)',
output: "![an image](#{Gitlab.config.gitlab.url}/baz/bar/uploads/foo.png)"
},
'absolute non-upload URL' => {
input: '![an image](/downloads/foo.png)',
output: "![an image](#{Gitlab.config.gitlab.url}/downloads/foo.png)"
}
}
}
end
with_them do
it { expect(subject.absolute_image_urls(input)).to eq(output) }
end
end
with_them do
it { expect(subject.absolute_image_urls(input)).to eq(output) }
context 'without an upload prefix specified' do
subject { subclass.new(nil) }
where do
{
'relative image URL' => {
input: '![an image](foo.png)',
output: "![an image](#{Gitlab.config.gitlab.url}/foo.png)"
},
'absolute upload URL' => {
input: '![an image](/uploads/foo.png)',
output: "![an image](#{Gitlab.config.gitlab.url}/uploads/foo.png)"
},
'absolute non-upload URL' => {
input: '![an image](/downloads/foo.png)',
output: "![an image](#{Gitlab.config.gitlab.url}/downloads/foo.png)"
},
'HTTP URL' => {
input: '![an image](http://example.com/foo.png)',
output: '![an image](http://example.com/foo.png)'
},
'HTTPS URL' => {
input: '![an image](https://example.com/foo.png)',
output: '![an image](https://example.com/foo.png)'
},
'protocol-relative URL' => {
input: '![an image](//example.com/foo.png)',
output: '![an image](//example.com/foo.png)'
},
'URL reference by title' => {
input: "![foo]\n\n[foo]: foo.png",
output: "![foo]\n\n[foo]: foo.png"
},
'URL reference by label' => {
input: "![][foo]\n\n[foo]: foo.png",
output: "![][foo]\n\n[foo]: foo.png"
},
'in Markdown inline code block' => {
input: '`![an image](foo.png)`',
output: "`![an image](#{Gitlab.config.gitlab.url}/foo.png)`"
},
'in HTML tag on the same line' => {
input: '<p>![an image](foo.png)</p>',
output: "<p>![an image](#{Gitlab.config.gitlab.url}/foo.png)</p>"
},
'in Markdown multi-line code block' => {
input: "```\n![an image](foo.png)\n```",
output: "```\n![an image](foo.png)\n```"
},
'in HTML tag on different lines' => {
input: "<p>\n![an image](foo.png)\n</p>",
output: "<p>\n![an image](foo.png)\n</p>"
}
}
end
with_them do
it { expect(subject.absolute_image_urls(input)).to eq(output) }
end
end
end
end

View File

@ -46,7 +46,10 @@ describe Gitlab::HookData::IssueBuilder do
let(:builder) { described_class.new(issue_with_description) }
it 'sets the image to use an absolute URL' do
expect(data[:description]).to eq("test![Issue_Image](#{Settings.gitlab.url}/uploads/abc/Issue_Image.png)")
expected_path = "#{issue_with_description.project.path_with_namespace}/uploads/abc/Issue_Image.png"
expect(data[:description])
.to eq("test![Issue_Image](#{Settings.gitlab.url}/#{expected_path})")
end
end
end

View File

@ -58,11 +58,14 @@ describe Gitlab::HookData::MergeRequestBuilder do
end
context 'when the MR has an image in the description' do
let(:mr_with_description) { create(:merge_request, description: 'test![Issue_Image](/uploads/abc/Issue_Image.png)') }
let(:mr_with_description) { create(:merge_request, description: 'test![MR_Image](/uploads/abc/MR_Image.png)') }
let(:builder) { described_class.new(mr_with_description) }
it 'sets the image to use an absolute URL' do
expect(data[:description]).to eq("test![Issue_Image](#{Settings.gitlab.url}/uploads/abc/Issue_Image.png)")
expected_path = "#{mr_with_description.project.path_with_namespace}/uploads/abc/MR_Image.png"
expect(data[:description])
.to eq("test![MR_Image](#{Settings.gitlab.url}/#{expected_path})")
end
end
end