pdfkit 0.8.2 → 0.8.4.3.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of pdfkit might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/.github/workflows/stale.yml +19 -0
- data/.ruby-version +1 -1
- data/.travis.yml +12 -8
- data/CHANGELOG.md +30 -0
- data/Gemfile +1 -1
- data/README.md +31 -10
- data/lib/pdfkit.rb +3 -0
- data/lib/pdfkit/configuration.rb +36 -3
- data/lib/pdfkit/html_preprocessor.rb +23 -0
- data/lib/pdfkit/middleware.rb +36 -28
- data/lib/pdfkit/os.rb +19 -0
- data/lib/pdfkit/pdfkit.rb +40 -101
- data/lib/pdfkit/source.rb +2 -1
- data/lib/pdfkit/version.rb +1 -1
- data/lib/pdfkit/wkhtmltopdf.rb +80 -0
- data/pdfkit.gemspec +6 -6
- data/spec/configuration_spec.rb +83 -12
- data/spec/html_preprocessor_spec.rb +69 -0
- data/spec/middleware_spec.rb +164 -71
- data/spec/os_spec.rb +65 -0
- data/spec/pdfkit_spec.rb +33 -2
- data/spec/source_spec.rb +25 -0
- metadata +39 -44
data/lib/pdfkit/source.rb
CHANGED
data/lib/pdfkit/version.rb
CHANGED
@@ -0,0 +1,80 @@
|
|
1
|
+
class PDFKit
|
2
|
+
class WkHTMLtoPDF
|
3
|
+
attr_reader :options
|
4
|
+
# Pulled from:
|
5
|
+
# https://github.com/wkhtmltopdf/wkhtmltopdf/blob/ebf9b6cfc4c58a31349fb94c568b254fac37b3d3/README_WKHTMLTOIMAGE#L27
|
6
|
+
REPEATABLE_OPTIONS = %w[--allow --cookie --custom-header --post --post-file --run-script]
|
7
|
+
SPECIAL_OPTIONS = %w[cover toc]
|
8
|
+
|
9
|
+
def initialize(options)
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def normalize_options
|
14
|
+
# TODO(cdwort,sigmavirus24): Make this method idempotent in a future release so it can be called repeatedly
|
15
|
+
normalized_options = {}
|
16
|
+
|
17
|
+
@options.each do |key, value|
|
18
|
+
next if !value
|
19
|
+
|
20
|
+
# The actual option for wkhtmltopdf
|
21
|
+
normalized_key = normalize_arg key
|
22
|
+
normalized_key = "--#{normalized_key}" unless SPECIAL_OPTIONS.include?(normalized_key)
|
23
|
+
|
24
|
+
# If the option is repeatable, attempt to normalize all values
|
25
|
+
if REPEATABLE_OPTIONS.include? normalized_key
|
26
|
+
normalize_repeatable_value(normalized_key, value) do |normalized_unique_key, normalized_value|
|
27
|
+
normalized_options[normalized_unique_key] = normalized_value
|
28
|
+
end
|
29
|
+
else # Otherwise, just normalize it like usual
|
30
|
+
normalized_options[normalized_key] = normalize_value(value)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
@options = normalized_options
|
35
|
+
end
|
36
|
+
|
37
|
+
def error_handling?
|
38
|
+
@options.key?('--ignore-load-errors') ||
|
39
|
+
# wkhtmltopdf v0.10.0 beta4 replaces ignore-load-errors with load-error-handling
|
40
|
+
# https://code.google.com/p/wkhtmltopdf/issues/detail?id=55
|
41
|
+
%w(skip ignore).include?(@options['--load-error-handling'])
|
42
|
+
end
|
43
|
+
|
44
|
+
def options_for_command
|
45
|
+
@options.to_a.flatten.compact
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def normalize_arg(arg)
|
51
|
+
arg.to_s.downcase.gsub(/[^a-z0-9]/,'-')
|
52
|
+
end
|
53
|
+
|
54
|
+
def normalize_value(value)
|
55
|
+
case value
|
56
|
+
when nil
|
57
|
+
nil
|
58
|
+
when TrueClass, 'true' #ie, ==true, see http://www.ruby-doc.org/core-1.9.3/TrueClass.html
|
59
|
+
nil
|
60
|
+
when Hash
|
61
|
+
value.to_a.flatten.collect{|x| normalize_value(x)}.compact
|
62
|
+
when Array
|
63
|
+
value.flatten.collect{|x| x.to_s}
|
64
|
+
else
|
65
|
+
(OS::host_is_windows? && value.to_s.index(' ')) ? "'#{ value.to_s }'" : value.to_s
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def normalize_repeatable_value(option_name, value)
|
70
|
+
case value
|
71
|
+
when Hash, Array
|
72
|
+
value.each do |(key, val)|
|
73
|
+
yield [[option_name, normalize_value(key)], normalize_value(val)]
|
74
|
+
end
|
75
|
+
else
|
76
|
+
yield [[option_name, normalize_value(value)], nil]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/pdfkit.gemspec
CHANGED
@@ -11,20 +11,20 @@ Gem::Specification.new do |s|
|
|
11
11
|
s.homepage = "https://github.com/pdfkit/pdfkit"
|
12
12
|
s.summary = "HTML+CSS -> PDF"
|
13
13
|
s.description = "Uses wkhtmltopdf to create PDFs using HTML"
|
14
|
-
|
15
|
-
s.rubyforge_project = "pdfkit"
|
14
|
+
s.license = "MIT"
|
16
15
|
|
17
16
|
s.files = `git ls-files`.split("\n")
|
18
17
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
19
|
s.require_paths = ["lib"]
|
21
20
|
|
22
|
-
|
23
|
-
|
21
|
+
s.requirements << "wkhtmltopdf"
|
22
|
+
|
23
|
+
# Development Dependencies
|
24
|
+
s.add_development_dependency(%q<activesupport>, [">= 4.1.11"])
|
24
25
|
s.add_development_dependency(%q<mocha>, [">= 0.9.10"])
|
25
26
|
s.add_development_dependency(%q<rack-test>, [">= 0.5.6"])
|
26
|
-
s.add_development_dependency(%q<
|
27
|
-
s.add_development_dependency(%q<rake>, ["~>0.9.2"])
|
27
|
+
s.add_development_dependency(%q<rake>, ["~>12.3.3"])
|
28
28
|
s.add_development_dependency(%q<rdoc>, ["~> 4.0.1"])
|
29
29
|
s.add_development_dependency(%q<rspec>, ["~> 3.0"])
|
30
30
|
end
|
data/spec/configuration_spec.rb
CHANGED
@@ -3,26 +3,74 @@ require 'spec_helper'
|
|
3
3
|
describe PDFKit::Configuration do
|
4
4
|
subject { PDFKit::Configuration.new }
|
5
5
|
describe "#wkhtmltopdf" do
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
context "when explicitly configured" do
|
7
|
+
it "uses configured value and don't detect" do
|
8
|
+
expect(subject).not_to receive(:default_wkhtmltopdf)
|
9
|
+
subject.wkhtmltopdf = "./Gemfile" # Need a file which exists
|
10
|
+
expect(subject.wkhtmltopdf).to eq("./Gemfile")
|
11
|
+
end
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
it "falls back to detected binary if configured path doesn't exists" do
|
14
|
+
expect(subject).to receive(:default_wkhtmltopdf).twice.and_return("/bin/fallback")
|
15
|
+
expect(subject).to receive(:warn).with(/No executable found/)
|
16
|
+
subject.wkhtmltopdf = "./missing-file" # Need a file which doesn't exist
|
17
|
+
expect(subject.wkhtmltopdf).to eq("/bin/fallback")
|
18
|
+
end
|
15
19
|
end
|
16
20
|
|
17
21
|
context "when not explicitly configured" do
|
18
|
-
|
19
|
-
#
|
20
|
-
|
21
|
-
|
22
|
+
context "when running inside bundler" do
|
23
|
+
# Simulate the presence of bundler even if it's not here
|
24
|
+
before { stub_const("Bundler::GemfileError", Class) }
|
25
|
+
|
26
|
+
it "detects the existance of bundler" do
|
27
|
+
expect(subject).to receive(:`).with('bundle exec which wkhtmltopdf').and_return("c:\\windows\\path.exe\n")
|
28
|
+
expect(subject.wkhtmltopdf).to eq('c:\windows\path.exe')
|
29
|
+
end
|
30
|
+
|
31
|
+
it "falls back if bundler path fails" do
|
32
|
+
# This happens when there is a wrong (buggy) version of bundler for example
|
33
|
+
expect(subject).to receive(:`).with('bundle exec which wkhtmltopdf').and_return("")
|
34
|
+
expect(subject).to receive(:`).with('which wkhtmltopdf').and_return("c:\\windows\\path.exe\n")
|
35
|
+
expect(subject.wkhtmltopdf).to eq('c:\windows\path.exe')
|
36
|
+
end
|
37
|
+
|
38
|
+
it "returns last line of 'bundle exec which' output" do
|
39
|
+
# Happens when the user does not have a HOME directory on their system and runs bundler < 2
|
40
|
+
expect(subject).to receive(:`).with('bundle exec which wkhtmltopdf').and_return(<<~EOT
|
41
|
+
`/home/myuser` is not a directory.
|
42
|
+
Bundler will use `/tmp/bundler/home/myuser' as your home directory temporarily.
|
43
|
+
/usr/bin/wkhtmltopdf
|
44
|
+
EOT
|
45
|
+
)
|
46
|
+
expect(subject.wkhtmltopdf).to eq('/usr/bin/wkhtmltopdf')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "when running without bundler" do
|
51
|
+
# Simulate the absence of bundler even if it's there
|
52
|
+
before { hide_const("Bundler::GemfileError") }
|
53
|
+
|
54
|
+
it "detects the existance of bundler" do
|
55
|
+
expect(subject).not_to receive(:`).with('bundle exec which wkhtmltopdf')
|
56
|
+
expect(subject).to receive(:`).with('which wkhtmltopdf').and_return('c:\windows\path.exe')
|
57
|
+
expect(subject.wkhtmltopdf).to eq('c:\windows\path.exe')
|
58
|
+
end
|
22
59
|
end
|
23
60
|
end
|
24
61
|
end
|
25
62
|
|
63
|
+
describe "#executable" do
|
64
|
+
it "returns wkhtmltopdf by default" do
|
65
|
+
expect(subject.executable).to eql subject.wkhtmltopdf
|
66
|
+
end
|
67
|
+
|
68
|
+
it "uses xvfb-run wrapper when option of using xvfb is configured" do
|
69
|
+
expect(subject).to receive(:using_xvfb?).and_return(true)
|
70
|
+
expect(subject.executable).to include 'xvfb-run'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
26
74
|
describe "#default_options" do
|
27
75
|
it "sets defaults for the command options" do
|
28
76
|
expect(subject.default_options[:disable_smart_shrinking]).to eql false
|
@@ -40,6 +88,13 @@ describe PDFKit::Configuration do
|
|
40
88
|
expect(subject.default_options[:quiet]).to eql false
|
41
89
|
expect(subject.default_options[:is_awesome]).to eql true
|
42
90
|
end
|
91
|
+
|
92
|
+
it "merges additional options with existing defaults" do
|
93
|
+
subject.default_options = { quiet: false, is_awesome: true }
|
94
|
+
expect(subject.default_options[:quiet]).to eql false
|
95
|
+
expect(subject.default_options[:is_awesome]).to eql true
|
96
|
+
expect(subject.default_options[:disable_smart_shrinking]).to eql false
|
97
|
+
end
|
43
98
|
end
|
44
99
|
|
45
100
|
describe "#root_url" do
|
@@ -64,6 +119,22 @@ describe PDFKit::Configuration do
|
|
64
119
|
end
|
65
120
|
end
|
66
121
|
|
122
|
+
describe "#using_xvfb?" do
|
123
|
+
it "can be configured to true" do
|
124
|
+
subject.use_xvfb = true
|
125
|
+
expect(subject.using_xvfb?).to eql true
|
126
|
+
end
|
127
|
+
|
128
|
+
it "defaults to false" do
|
129
|
+
expect(subject.using_xvfb?).to eql false
|
130
|
+
end
|
131
|
+
|
132
|
+
it "can be configured to false" do
|
133
|
+
subject.use_xvfb = false
|
134
|
+
expect(subject.using_xvfb?).to eql false
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
67
138
|
describe "#verbose?" do
|
68
139
|
it "can be configured to true" do
|
69
140
|
subject.verbose = true
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PDFKit::HTMLPreprocessor do
|
4
|
+
describe "#process" do
|
5
|
+
let(:preprocessor) { PDFKit::HTMLPreprocessor }
|
6
|
+
let(:root_url) { 'http://example.com/' } # This mirrors Middleware#root_url's response
|
7
|
+
let(:protocol) { 'http' }
|
8
|
+
|
9
|
+
it "correctly parses host-relative url with single quotes" do
|
10
|
+
original_body = %{<html><head><link href='/stylesheets/application.css' media='screen' rel='stylesheet' type='text/css' /></head><body><img alt='test' src="/test.png" /></body></html>}
|
11
|
+
body = preprocessor.process original_body, root_url, protocol
|
12
|
+
expect(body).to eq("<html><head><link href='http://example.com/stylesheets/application.css' media='screen' rel='stylesheet' type='text/css' /></head><body><img alt='test' src=\"http://example.com/test.png\" /></body></html>")
|
13
|
+
end
|
14
|
+
|
15
|
+
it "correctly parses host-relative url with double quotes" do
|
16
|
+
original_body = %{<link href="/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />}
|
17
|
+
body = preprocessor.process original_body, root_url, protocol
|
18
|
+
expect(body).to eq("<link href=\"http://example.com/stylesheets/application.css\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />")
|
19
|
+
end
|
20
|
+
|
21
|
+
it "correctly parses protocol-relative url with single quotes" do
|
22
|
+
original_body = %{<link href='//fonts.googleapis.com/css?family=Open+Sans:400,600' rel='stylesheet' type='text/css'>}
|
23
|
+
body = preprocessor.process original_body, root_url, protocol
|
24
|
+
expect(body).to eq("<link href='http://fonts.googleapis.com/css?family=Open+Sans:400,600' rel='stylesheet' type='text/css'>")
|
25
|
+
end
|
26
|
+
|
27
|
+
it "correctly parses protocol-relative url with double quotes" do
|
28
|
+
original_body = %{<link href="//fonts.googleapis.com/css?family=Open+Sans:400,600" rel='stylesheet' type='text/css'>}
|
29
|
+
body = preprocessor.process original_body, root_url, protocol
|
30
|
+
expect(body).to eq("<link href=\"http://fonts.googleapis.com/css?family=Open+Sans:400,600\" rel='stylesheet' type='text/css'>")
|
31
|
+
end
|
32
|
+
|
33
|
+
it "correctly parses multiple tags where first one is root url" do
|
34
|
+
original_body = %{<a href='/'><img src='/logo.jpg' ></a>}
|
35
|
+
body = preprocessor.process original_body, root_url, protocol
|
36
|
+
expect(body).to eq "<a href='http://example.com/'><img src='http://example.com/logo.jpg' ></a>"
|
37
|
+
end
|
38
|
+
|
39
|
+
it "returns the body even if there are no valid substitutions found" do
|
40
|
+
original_body = "NO MATCH"
|
41
|
+
body = preprocessor.process original_body, root_url, protocol
|
42
|
+
expect(body).to eq("NO MATCH")
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'when root_url is nil' do
|
46
|
+
it "returns the body safely, without interpolating" do
|
47
|
+
original_body = %{<link href='//fonts.googleapis.com/css?family=Open+Sans:400,600' rel='stylesheet' type='text/css'><a href='/'><img src='/logo.jpg'></a>}
|
48
|
+
body = preprocessor.process original_body, nil, protocol
|
49
|
+
expect(body).to eq(%{<link href='http://fonts.googleapis.com/css?family=Open+Sans:400,600' rel='stylesheet' type='text/css'><a href='/'><img src='/logo.jpg'></a>})
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'when protocol is nil' do
|
54
|
+
it "returns the body safely, without interpolating" do
|
55
|
+
original_body = %{<link href='//fonts.googleapis.com/css?family=Open+Sans:400,600' rel='stylesheet' type='text/css'><a href='/'><img src='/logo.jpg'></a>}
|
56
|
+
body = preprocessor.process original_body, root_url, nil
|
57
|
+
expect(body).to eq(%{<link href='//fonts.googleapis.com/css?family=Open+Sans:400,600' rel='stylesheet' type='text/css'><a href='http://example.com/'><img src='http://example.com/logo.jpg'></a>})
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'when root_url and protocol are both nil' do
|
62
|
+
it "returns the body safely, without interpolating" do
|
63
|
+
original_body = %{<link href='//fonts.googleapis.com/css?family=Open+Sans:400,600' rel='stylesheet' type='text/css'><a href='/'><img src='/logo.jpg'></a>}
|
64
|
+
body = preprocessor.process original_body, nil, nil
|
65
|
+
expect(body).to eq original_body
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/spec/middleware_spec.rb
CHANGED
@@ -21,6 +21,35 @@ describe PDFKit::Middleware do
|
|
21
21
|
end
|
22
22
|
|
23
23
|
describe "#call" do
|
24
|
+
|
25
|
+
describe 'threadsafety' do
|
26
|
+
before { mock_app }
|
27
|
+
it 'is threadsafe' do
|
28
|
+
n = 30
|
29
|
+
extensions = Array.new(n) { rand > 0.5 ? 'html' : 'pdf' }
|
30
|
+
actual_content_types = Hash.new
|
31
|
+
|
32
|
+
threads = (0...n).map { |i|
|
33
|
+
Thread.new do
|
34
|
+
resp = get("http://www.example.org/public/test.#{extensions[i]}")
|
35
|
+
actual_content_types[i] = resp.content_type
|
36
|
+
end
|
37
|
+
}
|
38
|
+
|
39
|
+
threads.each(&:join)
|
40
|
+
|
41
|
+
extensions.each_with_index do |extension, index|
|
42
|
+
result = actual_content_types[index]
|
43
|
+
case extension
|
44
|
+
when 'html', 'txt', 'csv'
|
45
|
+
expect(result).to eq("text/#{extension}")
|
46
|
+
when 'pdf'
|
47
|
+
expect(result).to eq('application/pdf')
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
24
53
|
describe "caching" do
|
25
54
|
let(:headers) do
|
26
55
|
{
|
@@ -236,22 +265,22 @@ describe PDFKit::Middleware do
|
|
236
265
|
end
|
237
266
|
|
238
267
|
describe "saving generated pdf to disk" do
|
239
|
-
|
268
|
+
before do
|
240
269
|
#make sure tests don't find an old test_save.pdf
|
241
270
|
File.delete('spec/test_save.pdf') if File.exists?('spec/test_save.pdf')
|
242
271
|
expect(File.exists?('spec/test_save.pdf')).to eq(false)
|
243
|
-
|
272
|
+
end
|
244
273
|
|
245
274
|
context "when header PDFKit-save-pdf is present" do
|
246
275
|
it "saves the .pdf to disk" do
|
247
|
-
|
276
|
+
headers = { 'PDFKit-save-pdf' => 'spec/test_save.pdf' }
|
248
277
|
mock_app({}, {only: '/public'}, headers)
|
249
|
-
|
278
|
+
get 'http://www.example.org/public/test_save.pdf'
|
250
279
|
expect(File.exists?('spec/test_save.pdf')).to eq(true)
|
251
|
-
|
280
|
+
end
|
252
281
|
|
253
282
|
it "does not raise when target directory does not exist" do
|
254
|
-
|
283
|
+
headers = { 'PDFKit-save-pdf' => '/this/dir/does/not/exist/spec/test_save.pdf' }
|
255
284
|
mock_app({}, {only: '/public'}, headers)
|
256
285
|
expect {
|
257
286
|
get 'http://www.example.com/public/test_save.pdf'
|
@@ -262,15 +291,122 @@ describe PDFKit::Middleware do
|
|
262
291
|
context "when header PDFKit-save-pdf is not present" do
|
263
292
|
it "does not saved the .pdf to disk" do
|
264
293
|
mock_app({}, {only: '/public'}, {} )
|
265
|
-
|
294
|
+
get 'http://www.example.org/public/test_save.pdf'
|
266
295
|
expect(File.exists?('spec/test_save.pdf')).to eq(false)
|
267
296
|
end
|
268
297
|
end
|
269
298
|
end
|
299
|
+
|
300
|
+
describe 'javascript delay' do
|
301
|
+
context 'when header PDFKit-javascript-delay is present' do
|
302
|
+
it 'passes header value through to PDFKit initialiser' do
|
303
|
+
expect(PDFKit).to receive(:new).with('Hello world!', {
|
304
|
+
root_url: 'http://www.example.com/', protocol: 'http', javascript_delay: 4321
|
305
|
+
}).and_call_original
|
306
|
+
|
307
|
+
headers = { 'PDFKit-javascript-delay' => '4321' }
|
308
|
+
mock_app({}, { only: '/public' }, headers)
|
309
|
+
get 'http://www.example.com/public/test_save.pdf'
|
310
|
+
end
|
311
|
+
|
312
|
+
it 'handles invalid content in header' do
|
313
|
+
expect(PDFKit).to receive(:new).with('Hello world!', {
|
314
|
+
root_url: 'http://www.example.com/', protocol: 'http', javascript_delay: 0
|
315
|
+
}).and_call_original
|
316
|
+
|
317
|
+
headers = { 'PDFKit-javascript-delay' => 'invalid' }
|
318
|
+
mock_app({}, { only: '/public' }, headers)
|
319
|
+
get 'http://www.example.com/public/test_save.pdf'
|
320
|
+
end
|
321
|
+
|
322
|
+
it 'overrides default option' do
|
323
|
+
expect(PDFKit).to receive(:new).with('Hello world!', {
|
324
|
+
root_url: 'http://www.example.com/', protocol: 'http', javascript_delay: 4321
|
325
|
+
}).and_call_original
|
326
|
+
|
327
|
+
headers = { 'PDFKit-javascript-delay' => '4321' }
|
328
|
+
mock_app({ javascript_delay: 1234 }, { only: '/public' }, headers)
|
329
|
+
get 'http://www.example.com/public/test_save.pdf'
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
context 'when header PDFKit-javascript-delay is not present' do
|
334
|
+
it 'passes through default option' do
|
335
|
+
expect(PDFKit).to receive(:new).with('Hello world!', {
|
336
|
+
root_url: 'http://www.example.com/', protocol: 'http', javascript_delay: 1234
|
337
|
+
}).and_call_original
|
338
|
+
|
339
|
+
mock_app({ javascript_delay: 1234 }, { only: '/public' }, { })
|
340
|
+
get 'http://www.example.com/public/test_save.pdf'
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
describe ":disposition" do
|
346
|
+
describe "doesn't overwrite existing value" do
|
347
|
+
let(:headers) do
|
348
|
+
super().merge({
|
349
|
+
'Content-Disposition' => 'attachment; filename=report-20200101.pdf'
|
350
|
+
})
|
351
|
+
end
|
352
|
+
|
353
|
+
specify do
|
354
|
+
mock_app({}, { :disposition => 'inline' })
|
355
|
+
get 'http://www.example.org/public/test.pdf'
|
356
|
+
expect(last_response.headers["Content-Disposition"]).to eq('attachment; filename=report-20200101.pdf')
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
describe "inline or blank" do
|
361
|
+
context "default" do
|
362
|
+
specify do
|
363
|
+
mock_app
|
364
|
+
get 'http://www.example.org/public/test.pdf'
|
365
|
+
expect(last_response.headers["Content-Disposition"]).to eq("inline")
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
context "inline" do
|
370
|
+
specify do
|
371
|
+
mock_app({}, { :disposition => 'inline' })
|
372
|
+
get 'http://www.example.org/public/test.pdf'
|
373
|
+
expect(last_response.headers["Content-Disposition"]).to eq("inline")
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
describe "attachment" do
|
379
|
+
context "attachment" do
|
380
|
+
specify do
|
381
|
+
mock_app({}, { :disposition => 'attachment' })
|
382
|
+
get 'http://www.example.org/public/test.pdf'
|
383
|
+
expect(last_response.headers["Content-Disposition"]).to eq("attachment")
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
context "attachment with filename" do
|
388
|
+
specify do
|
389
|
+
mock_app({}, { :disposition => 'attachment; filename=report.pdf' })
|
390
|
+
get 'http://www.example.org/public/test.pdf'
|
391
|
+
expect(last_response.headers["Content-Disposition"]).to eq("attachment; filename=report.pdf")
|
392
|
+
end
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
describe "error handling" do
|
398
|
+
specify do
|
399
|
+
mock_app
|
400
|
+
allow(PDFKit).to receive(:new).and_raise(StandardError.new("Something went wrong"))
|
401
|
+
get 'http://www.example.org/public/test.pdf'
|
402
|
+
expect(last_response.status).to eq(500)
|
403
|
+
expect(last_response.body).to eq("Something went wrong")
|
404
|
+
end
|
405
|
+
end
|
270
406
|
end
|
271
407
|
|
272
|
-
|
273
|
-
|
408
|
+
describe "remove .pdf from PATH_INFO and REQUEST_URI" do
|
409
|
+
before { mock_app }
|
274
410
|
|
275
411
|
context "matching" do
|
276
412
|
|
@@ -319,80 +455,37 @@ describe PDFKit::Middleware do
|
|
319
455
|
end
|
320
456
|
end
|
321
457
|
|
322
|
-
describe "#
|
458
|
+
describe "#root_url and #protocol" do
|
323
459
|
before do
|
324
460
|
@pdf = PDFKit::Middleware.new({})
|
325
461
|
@env = { 'REQUEST_URI' => 'http://example.com/document.pdf', 'rack.url_scheme' => 'http', 'HTTP_HOST' => 'example.com' }
|
326
462
|
end
|
327
463
|
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
end
|
333
|
-
|
334
|
-
it "correctly parses relative url with double quotes" do
|
335
|
-
@body = %{<link href="/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />}
|
336
|
-
body = @pdf.send :translate_paths, @body, @env
|
337
|
-
expect(body).to eq("<link href=\"http://example.com/stylesheets/application.css\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />")
|
338
|
-
end
|
339
|
-
|
340
|
-
it "correctly parses relative url with double quotes" do
|
341
|
-
@body = %{<link href='//fonts.googleapis.com/css?family=Open+Sans:400,600' rel='stylesheet' type='text/css'>}
|
342
|
-
body = @pdf.send :translate_paths, @body, @env
|
343
|
-
expect(body).to eq("<link href='http://fonts.googleapis.com/css?family=Open+Sans:400,600' rel='stylesheet' type='text/css'>")
|
344
|
-
end
|
345
|
-
|
346
|
-
it "correctly parses multiple tags where first one is root url" do
|
347
|
-
@body = %{<a href='/'><img src='/logo.jpg' ></a>}
|
348
|
-
body = @pdf.send :translate_paths, @body, @env
|
349
|
-
expect(body).to eq "<a href='http://example.com/'><img src='http://example.com/logo.jpg' ></a>"
|
350
|
-
end
|
464
|
+
context 'when root_url is not configured' do
|
465
|
+
it "infers the root_url and protocol from the environment" do
|
466
|
+
root_url = @pdf.send(:root_url, @env)
|
467
|
+
protocol = @pdf.send(:protocol, @env)
|
351
468
|
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
expect(body).to eq("NO MATCH")
|
469
|
+
expect(root_url).to eq('http://example.com/')
|
470
|
+
expect(protocol).to eq('http')
|
471
|
+
end
|
356
472
|
end
|
357
|
-
end
|
358
473
|
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
474
|
+
context 'when root_url is configured' do
|
475
|
+
before do
|
476
|
+
PDFKit.configuration.root_url = 'http://example.net/'
|
477
|
+
end
|
478
|
+
after do
|
479
|
+
PDFKit.configuration.root_url = nil
|
365
480
|
end
|
366
|
-
end
|
367
481
|
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
expect(body).to eq("<html><head><link href='http://example.net/stylesheets/application.css' media='screen' rel='stylesheet' type='text/css' /></head><body><img alt='test' src=\"http://example.net/test.png\" /></body></html>")
|
372
|
-
end
|
482
|
+
it "takes the root_url from the configuration, and infers the protocol from the environment" do
|
483
|
+
root_url = @pdf.send(:root_url, @env)
|
484
|
+
protocol = @pdf.send(:protocol, @env)
|
373
485
|
|
374
|
-
|
375
|
-
|
376
|
-
config.root_url = nil
|
486
|
+
expect(root_url).to eq('http://example.net/')
|
487
|
+
expect(protocol).to eq('http')
|
377
488
|
end
|
378
489
|
end
|
379
490
|
end
|
380
|
-
|
381
|
-
it "does not get stuck rendering each request as pdf" do
|
382
|
-
mock_app
|
383
|
-
# false by default. No requests.
|
384
|
-
expect(@app.send(:rendering_pdf?)).to eq(false)
|
385
|
-
|
386
|
-
# Remain false on a normal request
|
387
|
-
get 'http://www.example.org/public/file'
|
388
|
-
expect(@app.send(:rendering_pdf?)).to eq(false)
|
389
|
-
|
390
|
-
# Return true on a pdf request.
|
391
|
-
get 'http://www.example.org/public/file.pdf'
|
392
|
-
expect(@app.send(:rendering_pdf?)).to eq(true)
|
393
|
-
|
394
|
-
# Restore to false on any non-pdf request.
|
395
|
-
get 'http://www.example.org/public/file'
|
396
|
-
expect(@app.send(:rendering_pdf?)).to eq(false)
|
397
|
-
end
|
398
491
|
end
|