Skip to content

Commit 82f4564

Browse files
committed
Fix project search results for digits surrounded by colons
A file containing /:\d+:/ in its contents would break the search results if those contents were part of the results, because we were splitting on colons, which can't work with untrusted input. Changing to use the null byte as a separator is much safer.
1 parent 1df5c74 commit 82f4564

File tree

5 files changed

+39
-23
lines changed

5 files changed

+39
-23
lines changed

app/models/repository.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -932,7 +932,7 @@ def search_files_by_content(query, ref)
932932
return [] if empty? || query.blank?
933933

934934
offset = 2
935-
args = %W(grep -i -I -n --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref})
935+
args = %W(grep -i -I -n -z --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref})
936936

937937
run_git(args).first.scrub.split(/^--$/)
938938
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
title: Fix file search results when they match file contents with a number between
3+
two colons
4+
merge_request: 16462
5+
author:
6+
type: fixed

lib/gitlab/project_search_results.rb

+4-9
Original file line numberDiff line numberDiff line change
@@ -44,25 +44,20 @@ def self.parse_search_result(result)
4444
ref = nil
4545
filename = nil
4646
basename = nil
47+
data = ""
4748
startline = 0
4849

49-
result.each_line.each_with_index do |line, index|
50-
matches = line.match(/^(?<ref>[^:]*):(?<filename>.*):(?<startline>\d+):/)
51-
if matches
50+
result.strip.each_line.each_with_index do |line, index|
51+
prefix ||= line.match(/^(?<ref>[^:]*):(?<filename>.*)\x00(?<startline>\d+)\x00/)&.tap do |matches|
5252
ref = matches[:ref]
5353
filename = matches[:filename]
5454
startline = matches[:startline]
5555
startline = startline.to_i - index
5656
extname = Regexp.escape(File.extname(filename))
5757
basename = filename.sub(/#{extname}$/, '')
58-
break
5958
end
60-
end
61-
62-
data = ""
6359

64-
result.each_line do |line|
65-
data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
60+
data << line.sub(prefix.to_s, '')
6661
end
6762

6863
FoundBlob.new(

spec/lib/gitlab/project_search_results_spec.rb

+27-12
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,6 @@
7070

7171
subject { described_class.parse_search_result(search_result) }
7272

73-
it 'can correctly parse filenames including ":"' do
74-
special_char_result = "\nmaster:testdata/project::function1.yaml-1----\nmaster:testdata/project::function1.yaml:2:test: data1\n"
75-
76-
blob = described_class.parse_search_result(special_char_result)
77-
78-
expect(blob.ref).to eq('master')
79-
expect(blob.filename).to eq('testdata/project::function1.yaml')
80-
end
81-
8273
it "returns a valid FoundBlob" do
8374
is_expected.to be_an Gitlab::SearchResults::FoundBlob
8475
expect(subject.id).to be_nil
@@ -90,16 +81,40 @@
9081
expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n")
9182
end
9283

84+
context 'when the matching filename contains a colon' do
85+
let(:search_result) { "\nmaster:testdata/project::function1.yaml\x001\x00---\n" }
86+
87+
it 'returns a valid FoundBlob' do
88+
expect(subject.filename).to eq('testdata/project::function1.yaml')
89+
expect(subject.basename).to eq('testdata/project::function1')
90+
expect(subject.ref).to eq('master')
91+
expect(subject.startline).to eq(1)
92+
expect(subject.data).to eq('---')
93+
end
94+
end
95+
96+
context 'when the matching content contains a number surrounded by colons' do
97+
let(:search_result) { "\nmaster:testdata/foo.txt\x001\x00blah:9:blah" }
98+
99+
it 'returns a valid FoundBlob' do
100+
expect(subject.filename).to eq('testdata/foo.txt')
101+
expect(subject.basename).to eq('testdata/foo')
102+
expect(subject.ref).to eq('master')
103+
expect(subject.startline).to eq(1)
104+
expect(subject.data).to eq('blah:9:blah')
105+
end
106+
end
107+
93108
context "when filename has extension" do
94-
let(:search_result) { "master:CONTRIBUTE.md:5:- [Contribute to GitLab](#contribute-to-gitlab)\n" }
109+
let(:search_result) { "master:CONTRIBUTE.md\x005\x00- [Contribute to GitLab](#contribute-to-gitlab)\n" }
95110

96111
it { expect(subject.path).to eq('CONTRIBUTE.md') }
97112
it { expect(subject.filename).to eq('CONTRIBUTE.md') }
98113
it { expect(subject.basename).to eq('CONTRIBUTE') }
99114
end
100115

101116
context "when file under directory" do
102-
let(:search_result) { "master:a/b/c.md:5:a b c\n" }
117+
let(:search_result) { "master:a/b/c.md\x005\x00a b c\n" }
103118

104119
it { expect(subject.path).to eq('a/b/c.md') }
105120
it { expect(subject.filename).to eq('a/b/c.md') }
@@ -144,7 +159,7 @@
144159
end
145160

146161
it 'finds by content' do
147-
expect(results).to include("master:Title.md:1:Content\n")
162+
expect(results).to include("master:Title.md\x001\x00Content\n")
148163
end
149164
end
150165

spec/models/repository_spec.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -657,7 +657,7 @@ def expect_to_raise_storage_error
657657
subject { results.first }
658658

659659
it { is_expected.to be_an String }
660-
it { expect(subject.lines[2]).to eq("master:CHANGELOG:190: - Feature: Replace teams with group membership\n") }
660+
it { expect(subject.lines[2]).to eq("master:CHANGELOG\x00190\x00 - Feature: Replace teams with group membership\n") }
661661
end
662662
end
663663

0 commit comments

Comments
 (0)