pdfkit 0.8.2 → 0.8.7.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.

data/lib/pdfkit/pdfkit.rb CHANGED
@@ -1,40 +1,53 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'shellwords'
2
- require 'rbconfig'
4
+ require 'tempfile'
3
5
 
4
6
  class PDFKit
5
- class NoExecutableError < StandardError
7
+ class Error < StandardError; end
8
+
9
+ class NoExecutableError < Error
6
10
  def initialize
7
- msg = "No wkhtmltopdf executable found at #{PDFKit.configuration.wkhtmltopdf}\n"
8
- msg << ">> Please install wkhtmltopdf - https://github.com/pdfkit/PDFKit/wiki/Installing-WKHTMLTOPDF"
11
+ msg = "No wkhtmltopdf executable found at #{PDFKit.configuration.wkhtmltopdf}\n" \
12
+ ">> Please install wkhtmltopdf - https://github.com/pdfkit/PDFKit/wiki/Installing-WKHTMLTOPDF"
9
13
  super(msg)
10
14
  end
11
15
  end
12
16
 
13
- class ImproperSourceError < StandardError
17
+ class ImproperSourceError < Error
14
18
  def initialize(msg)
15
19
  super("Improper Source: #{msg}")
16
20
  end
17
21
  end
18
22
 
23
+ class ImproperWkhtmltopdfExitStatus < Error
24
+ def initialize(invoke)
25
+ super("Command failed (exitstatus=#{$?.exitstatus}): #{invoke}")
26
+ end
27
+ end
28
+
19
29
  attr_accessor :source, :stylesheets
20
- attr_reader :options
30
+ attr_reader :renderer
21
31
 
22
32
  def initialize(url_file_or_html, options = {})
23
33
  @source = Source.new(url_file_or_html)
24
34
 
25
35
  @stylesheets = []
26
36
 
27
- @options = PDFKit.configuration.default_options.merge(options)
28
- @options.delete(:quiet) if PDFKit.configuration.verbose?
29
- @options.merge! find_options_in_meta(url_file_or_html) unless source.url?
30
- @options = normalize_options(@options)
37
+ options = PDFKit.configuration.default_options.merge(options)
38
+ options.delete(:quiet) if PDFKit.configuration.verbose?
39
+ options.merge! find_options_in_meta(url_file_or_html) unless source.url?
40
+ @root_url = options.delete(:root_url)
41
+ @protocol = options.delete(:protocol)
42
+ @renderer = WkHTMLtoPDF.new options
43
+ @renderer.normalize_options
31
44
 
32
- raise NoExecutableError.new unless File.exists?(PDFKit.configuration.wkhtmltopdf)
45
+ raise NoExecutableError unless File.exist?(PDFKit.configuration.wkhtmltopdf)
33
46
  end
34
47
 
35
48
  def command(path = nil)
36
- args = @options.to_a.flatten.compact
37
- shell_escaped_command = [executable, shell_escape_for_os(args)].join ' '
49
+ args = @renderer.options_for_command
50
+ shell_escaped_command = [executable, OS::shell_escape_for_os(args)].join ' '
38
51
 
39
52
  # In order to allow for URL parameters (e.g. https://www.google.com/search?q=pdfkit) we do
40
53
  # not escape the source. The user is responsible for ensuring that no vulnerabilities exist
@@ -45,17 +58,17 @@ class PDFKit
45
58
  "#{shell_escaped_command} #{input_for_command} #{output_for_command}"
46
59
  end
47
60
 
61
+ def options
62
+ # TODO(cdwort,sigmavirus24): Replace this with an attr_reader for @renderer instead in 1.0.0
63
+ @renderer.options
64
+ end
65
+
48
66
  def executable
49
- default = PDFKit.configuration.wkhtmltopdf
50
- return default if default !~ /^\// # its not a path, so nothing we can do
51
- if File.exist?(default)
52
- default
53
- else
54
- default.split('/').last
55
- end
67
+ PDFKit.configuration.executable
56
68
  end
57
69
 
58
70
  def to_pdf(path=nil)
71
+ preprocess_html
59
72
  append_stylesheets
60
73
 
61
74
  invoke = command(path)
@@ -68,7 +81,7 @@ class PDFKit
68
81
 
69
82
  # $? is thread safe per
70
83
  # http://stackoverflow.com/questions/2164887/thread-safe-external-process-in-ruby-plus-checking-exitstatus
71
- raise "command failed (exitstatus=#{$?.exitstatus}): #{invoke}" if empty_result?(path, result) or !successful?($?)
84
+ raise ImproperWkhtmltopdfExitStatus, invoke if empty_result?(path, result) || !successful?($?)
72
85
  return result
73
86
  end
74
87
 
@@ -79,14 +92,9 @@ class PDFKit
79
92
 
80
93
  protected
81
94
 
82
- # Pulled from:
83
- # https://github.com/wkhtmltopdf/wkhtmltopdf/blob/ebf9b6cfc4c58a31349fb94c568b254fac37b3d3/README_WKHTMLTOIMAGE#L27
84
- REPEATABLE_OPTIONS = %w[--allow --cookie --custom-header --post --post-file --run-script]
85
- SPECIAL_OPTIONS = %w[cover toc]
86
-
87
95
  def find_options_in_meta(content)
88
96
  # Read file if content is a File
89
- content = content.read if content.is_a?(File)
97
+ content = content.read if content.is_a?(File) || content.is_a?(Tempfile)
90
98
 
91
99
  found = {}
92
100
  content.scan(/<meta [^>]*>/) do |meta|
@@ -108,80 +116,38 @@ class PDFKit
108
116
  end
109
117
 
110
118
  def style_tag_for(stylesheet)
111
- "<style>#{File.read(stylesheet)}</style>"
119
+ style = "<style>#{File.read(stylesheet)}</style>"
120
+ style = style.html_safe if style.respond_to?(:html_safe)
121
+ style
122
+ end
123
+
124
+ def preprocess_html
125
+ if @source.html?
126
+ processed_html = PDFKit::HTMLPreprocessor.process(@source.to_s, @root_url, @protocol)
127
+ @source = Source.new(processed_html)
128
+ end
112
129
  end
113
130
 
114
131
  def append_stylesheets
115
- raise ImproperSourceError.new('Stylesheets may only be added to an HTML source') if stylesheets.any? && !@source.html?
132
+ raise ImproperSourceError, 'Stylesheets may only be added to an HTML source' if stylesheets.any? && !@source.html?
116
133
 
117
134
  stylesheets.each do |stylesheet|
118
135
  if @source.to_s.match(/<\/head>/)
119
- @source = Source.new(@source.to_s.gsub(/(<\/head>)/) {|s| style_tag_for(stylesheet) + s })
136
+ @source = Source.new(@source.to_s.gsub(/(<\/head>)/) {|s|
137
+ style_tag_for(stylesheet) + (s.respond_to?(:html_safe) ? s.html_safe : s)
138
+ })
120
139
  else
121
140
  @source.to_s.insert(0, style_tag_for(stylesheet))
122
141
  end
123
142
  end
124
143
  end
125
144
 
126
- def normalize_options(options)
127
- normalized_options = {}
128
-
129
- options.each do |key, value|
130
- next if !value
131
-
132
- # The actual option for wkhtmltopdf
133
- normalized_key = normalize_arg key
134
- normalized_key = "--#{normalized_key}" unless SPECIAL_OPTIONS.include?(normalized_key)
135
-
136
- # If the option is repeatable, attempt to normalize all values
137
- if REPEATABLE_OPTIONS.include? normalized_key
138
- normalize_repeatable_value(normalized_key, value) do |normalized_unique_key, normalized_value|
139
- normalized_options[normalized_unique_key] = normalized_value
140
- end
141
- else # Otherwise, just normalize it like usual
142
- normalized_options[normalized_key] = normalize_value(value)
143
- end
144
- end
145
-
146
- normalized_options
147
- end
148
-
149
- def normalize_arg(arg)
150
- arg.to_s.downcase.gsub(/[^a-z0-9]/,'-')
151
- end
152
-
153
- def normalize_value(value)
154
- case value
155
- when nil
156
- nil
157
- when TrueClass, 'true' #ie, ==true, see http://www.ruby-doc.org/core-1.9.3/TrueClass.html
158
- nil
159
- when Hash
160
- value.to_a.flatten.collect{|x| normalize_value(x)}.compact
161
- when Array
162
- value.flatten.collect{|x| x.to_s}
163
- else
164
- (host_is_windows? && value.to_s.index(' ')) ? "'#{ value.to_s }'" : value.to_s
165
- end
166
- end
167
-
168
- def normalize_repeatable_value(option_name, value)
169
- case value
170
- when Hash, Array
171
- value.each do |(key, val)|
172
- yield [[option_name, normalize_value(key)], normalize_value(val)]
173
- end
174
- else
175
- yield [[option_name, normalize_value(value)], nil]
176
- end
177
- end
178
-
179
145
  def successful?(status)
180
146
  return true if status.success?
181
147
 
182
148
  # Some of the codes: https://code.google.com/p/wkhtmltopdf/issues/detail?id=1088
183
149
  # returned when assets are missing (404): https://code.google.com/p/wkhtmltopdf/issues/detail?id=548
184
- return true if status.exitstatus == 2 && error_handling?
150
+ return true if status.exitstatus == 2 && @renderer.error_handling?
185
151
 
186
152
  false
187
153
  end
@@ -189,25 +155,4 @@ class PDFKit
189
155
  def empty_result?(path, result)
190
156
  (path && File.size(path) == 0) || (path.nil? && result.to_s.strip.empty?)
191
157
  end
192
-
193
- def error_handling?
194
- @options.key?('--ignore-load-errors') ||
195
- # wkhtmltopdf v0.10.0 beta4 replaces ignore-load-errors with load-error-handling
196
- # https://code.google.com/p/wkhtmltopdf/issues/detail?id=55
197
- %w(skip ignore).include?(@options['--load-error-handling'])
198
- end
199
-
200
- def host_is_windows?
201
- @host_is_windows ||= !(RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince/).nil?
202
- end
203
-
204
- def shell_escape_for_os(args)
205
- if (host_is_windows?)
206
- # Windows reserved shell characters are: & | ( ) < > ^
207
- # See http://technet.microsoft.com/en-us/library/cc723564.aspx#XSLTsection123121120120
208
- args.map { |arg| arg.gsub(/([&|()<>^])/,'^\1') }.join(" ")
209
- else
210
- args.shelljoin
211
- end
212
- end
213
158
  end
data/lib/pdfkit/source.rb CHANGED
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tempfile'
1
4
  require 'uri'
2
5
 
3
6
  class PDFKit
@@ -6,6 +9,8 @@ class PDFKit
6
9
 
7
10
  def initialize(url_file_or_html)
8
11
  @source = url_file_or_html
12
+ # @source is assumed to be modifiable, so make sure it is.
13
+ @source = @source.dup if @source.is_a?(String) && @source.frozen?
9
14
  end
10
15
 
11
16
  def url?
@@ -13,7 +18,7 @@ class PDFKit
13
18
  end
14
19
 
15
20
  def file?
16
- @is_file ||= @source.kind_of?(File)
21
+ @is_file ||= @source.kind_of?(File) || @source.kind_of?(Tempfile)
17
22
  end
18
23
 
19
24
  def html?
@@ -37,11 +42,11 @@ class PDFKit
37
42
  private
38
43
 
39
44
  def shell_safe_url
40
- url_needs_escaping? ? URI::escape(@source) : @source
45
+ url_needs_escaping? ? URI::DEFAULT_PARSER.escape(@source) : @source
41
46
  end
42
47
 
43
48
  def url_needs_escaping?
44
- URI::decode(@source) == @source
49
+ URI::DEFAULT_PARSER.escape(URI::DEFAULT_PARSER.unescape(@source)) != @source
45
50
  end
46
51
  end
47
52
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class PDFKit
2
- VERSION = "0.8.2"
4
+ VERSION = '0.8.7.1'
3
5
  end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PDFKit
4
+ class WkHTMLtoPDF
5
+ attr_reader :options
6
+ # Pulled from:
7
+ # https://github.com/wkhtmltopdf/wkhtmltopdf/blob/ebf9b6cfc4c58a31349fb94c568b254fac37b3d3/README_WKHTMLTOIMAGE#L27
8
+ REPEATABLE_OPTIONS = %w[--allow --cookie --custom-header --post --post-file --run-script].freeze
9
+ SPECIAL_OPTIONS = %w[cover toc].freeze
10
+
11
+ def initialize(options)
12
+ @options = options
13
+ end
14
+
15
+ def normalize_options
16
+ # TODO(cdwort,sigmavirus24): Make this method idempotent in a future release so it can be called repeatedly
17
+ normalized_options = {}
18
+
19
+ @options.each do |key, value|
20
+ next if !value
21
+
22
+ # The actual option for wkhtmltopdf
23
+ normalized_key = normalize_arg key
24
+ normalized_key = "--#{normalized_key}" unless SPECIAL_OPTIONS.include?(normalized_key)
25
+
26
+ # If the option is repeatable, attempt to normalize all values
27
+ if REPEATABLE_OPTIONS.include? normalized_key
28
+ normalize_repeatable_value(normalized_key, value) do |normalized_unique_key, normalized_value|
29
+ normalized_options[normalized_unique_key] = normalized_value
30
+ end
31
+ else # Otherwise, just normalize it like usual
32
+ normalized_options[normalized_key] = normalize_value(value)
33
+ end
34
+ end
35
+
36
+ @options = normalized_options
37
+ end
38
+
39
+ def error_handling?
40
+ @options.key?('--ignore-load-errors') ||
41
+ # wkhtmltopdf v0.10.0 beta4 replaces ignore-load-errors with load-error-handling
42
+ # https://code.google.com/p/wkhtmltopdf/issues/detail?id=55
43
+ %w(skip ignore).include?(@options['--load-error-handling'])
44
+ end
45
+
46
+ def options_for_command
47
+ @options.to_a.flatten.compact
48
+ end
49
+
50
+ private
51
+
52
+ def normalize_arg(arg)
53
+ arg.to_s.downcase.gsub(/[^a-z0-9]/,'-')
54
+ end
55
+
56
+ def normalize_value(value)
57
+ case value
58
+ when nil
59
+ nil
60
+ when TrueClass, 'true' #ie, ==true, see http://www.ruby-doc.org/core-1.9.3/TrueClass.html
61
+ nil
62
+ when Hash
63
+ value.to_a.flatten.collect{|x| normalize_value(x)}.compact
64
+ when Array
65
+ value.flatten.collect{|x| x.to_s}
66
+ else
67
+ (OS::host_is_windows? && value.to_s.index(' ')) ? "'#{ value.to_s }'" : value.to_s
68
+ end
69
+ end
70
+
71
+ def normalize_repeatable_value(option_name, value)
72
+ case value
73
+ when Hash, Array
74
+ value.each do |(key, val)|
75
+ yield [[option_name, normalize_value(key)], normalize_value(val)]
76
+ end
77
+ else
78
+ yield [[option_name, normalize_value(value)], nil]
79
+ end
80
+ end
81
+ end
82
+ end
data/lib/pdfkit.rb CHANGED
@@ -1,4 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'pdfkit/source'
2
4
  require 'pdfkit/pdfkit'
3
5
  require 'pdfkit/middleware'
6
+ require 'pdfkit/html_preprocessor'
7
+ require 'pdfkit/os'
4
8
  require 'pdfkit/configuration'
9
+ require 'pdfkit/wkhtmltopdf'
10
+ require 'pdfkit/version'
data/pdfkit.gemspec CHANGED
@@ -11,20 +11,22 @@ 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
- # Developmnet Dependencies
23
- s.add_development_dependency(%q<activesupport>, [">= 3.0.8"])
21
+ s.required_ruby_version = ">= 2.5"
22
+
23
+ s.requirements << "wkhtmltopdf"
24
+
25
+ # Development Dependencies
26
+ s.add_development_dependency(%q<activesupport>, [">= 4.1.11"])
24
27
  s.add_development_dependency(%q<mocha>, [">= 0.9.10"])
25
28
  s.add_development_dependency(%q<rack-test>, [">= 0.5.6"])
26
- s.add_development_dependency(%q<i18n>, ["~>0.6.11"]) # Ruby 1.9.2 compatibility
27
- s.add_development_dependency(%q<rake>, ["~>0.9.2"])
28
- s.add_development_dependency(%q<rdoc>, ["~> 4.0.1"])
29
+ s.add_development_dependency(%q<rake>, [">= 12.3.3"])
30
+ s.add_development_dependency(%q<rdoc>, [">= 4.0.1"])
29
31
  s.add_development_dependency(%q<rspec>, ["~> 3.0"])
30
32
  end
@@ -1,28 +1,78 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe PDFKit::Configuration do
4
6
  subject { PDFKit::Configuration.new }
5
7
  describe "#wkhtmltopdf" do
6
- it "can be configured" do
7
- subject.wkhtmltopdf = '/my/bin/wkhtmltopdf'
8
- expect(subject.wkhtmltopdf).to eql '/my/bin/wkhtmltopdf'
9
- end
8
+ context "when explicitly configured" do
9
+ it "uses configured value and don't detect" do
10
+ expect(subject).not_to receive(:default_wkhtmltopdf)
11
+ subject.wkhtmltopdf = "./Gemfile" # Need a file which exists
12
+ expect(subject.wkhtmltopdf).to eq("./Gemfile")
13
+ end
10
14
 
11
- # This test documents existing functionality. Feel free to fix.
12
- it "can be poorly configured" do
13
- subject.wkhtmltopdf = 1234
14
- expect(subject.wkhtmltopdf).to eql 1234
15
+ it "falls back to detected binary if configured path doesn't exists" do
16
+ expect(subject).to receive(:default_wkhtmltopdf).twice.and_return("/bin/fallback")
17
+ expect(subject).to receive(:warn).with(/No executable found/)
18
+ subject.wkhtmltopdf = "./missing-file" # Need a file which doesn't exist
19
+ expect(subject.wkhtmltopdf).to eq("/bin/fallback")
20
+ end
15
21
  end
16
22
 
17
23
  context "when not explicitly configured" do
18
- it "detects the existance of bundler" do
19
- # Test assumes bundler is installed in your test environment
20
- expect(subject).to receive(:`).with('bundle exec which wkhtmltopdf').and_return('c:\windows\path.exe')
21
- subject.wkhtmltopdf
24
+ context "when running inside bundler" do
25
+ # Simulate the presence of bundler even if it's not here
26
+ before { stub_const("Bundler::GemfileError", Class) }
27
+
28
+ it "detects the existance of bundler" do
29
+ expect(subject).to receive(:`).with('bundle exec which wkhtmltopdf').and_return("c:\\windows\\path.exe\n")
30
+ expect(subject.wkhtmltopdf).to eq('c:\windows\path.exe')
31
+ end
32
+
33
+ it "falls back if bundler path fails" do
34
+ # This happens when there is a wrong (buggy) version of bundler for example
35
+ expect(subject).to receive(:`).with('bundle exec which wkhtmltopdf').and_return("")
36
+ expect(subject).to receive(:`).with('which wkhtmltopdf').and_return("c:\\windows\\path.exe\n")
37
+ expect(subject.wkhtmltopdf).to eq('c:\windows\path.exe')
38
+ end
39
+
40
+ it "returns last line of 'bundle exec which' output" do
41
+ # Happens when the user does not have a HOME directory on their system and runs bundler < 2
42
+ expect(subject).to receive(:`).with('bundle exec which wkhtmltopdf').and_return(<<~EOT
43
+ `/home/myuser` is not a directory.
44
+ Bundler will use `/tmp/bundler/home/myuser' as your home directory temporarily.
45
+ /usr/bin/wkhtmltopdf
46
+ EOT
47
+ )
48
+ expect(subject.wkhtmltopdf).to eq('/usr/bin/wkhtmltopdf')
49
+ end
50
+ end
51
+
52
+ context "when running without bundler" do
53
+ # Simulate the absence of bundler even if it's there
54
+ before { hide_const("Bundler::GemfileError") }
55
+
56
+ it "detects the existance of bundler" do
57
+ expect(subject).not_to receive(:`).with('bundle exec which wkhtmltopdf')
58
+ expect(subject).to receive(:`).with('which wkhtmltopdf').and_return('c:\windows\path.exe')
59
+ expect(subject.wkhtmltopdf).to eq('c:\windows\path.exe')
60
+ end
22
61
  end
23
62
  end
24
63
  end
25
64
 
65
+ describe "#executable" do
66
+ it "returns wkhtmltopdf by default" do
67
+ expect(subject.executable).to eql subject.wkhtmltopdf
68
+ end
69
+
70
+ it "uses xvfb-run wrapper when option of using xvfb is configured" do
71
+ expect(subject).to receive(:using_xvfb?).and_return(true)
72
+ expect(subject.executable).to include 'xvfb-run'
73
+ end
74
+ end
75
+
26
76
  describe "#default_options" do
27
77
  it "sets defaults for the command options" do
28
78
  expect(subject.default_options[:disable_smart_shrinking]).to eql false
@@ -40,6 +90,13 @@ describe PDFKit::Configuration do
40
90
  expect(subject.default_options[:quiet]).to eql false
41
91
  expect(subject.default_options[:is_awesome]).to eql true
42
92
  end
93
+
94
+ it "merges additional options with existing defaults" do
95
+ subject.default_options = { quiet: false, is_awesome: true }
96
+ expect(subject.default_options[:quiet]).to eql false
97
+ expect(subject.default_options[:is_awesome]).to eql true
98
+ expect(subject.default_options[:disable_smart_shrinking]).to eql false
99
+ end
43
100
  end
44
101
 
45
102
  describe "#root_url" do
@@ -64,6 +121,22 @@ describe PDFKit::Configuration do
64
121
  end
65
122
  end
66
123
 
124
+ describe "#using_xvfb?" do
125
+ it "can be configured to true" do
126
+ subject.use_xvfb = true
127
+ expect(subject.using_xvfb?).to eql true
128
+ end
129
+
130
+ it "defaults to false" do
131
+ expect(subject.using_xvfb?).to eql false
132
+ end
133
+
134
+ it "can be configured to false" do
135
+ subject.use_xvfb = false
136
+ expect(subject.using_xvfb?).to eql false
137
+ end
138
+ end
139
+
67
140
  describe "#verbose?" do
68
141
  it "can be configured to true" do
69
142
  subject.verbose = true
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe PDFKit::HTMLPreprocessor do
6
+ describe "#process" do
7
+ let(:preprocessor) { PDFKit::HTMLPreprocessor }
8
+ let(:root_url) { 'http://example.com/' } # This mirrors Middleware#root_url's response
9
+ let(:protocol) { 'http' }
10
+
11
+ it "correctly parses host-relative url with single quotes" do
12
+ 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>}
13
+ body = preprocessor.process original_body, root_url, protocol
14
+ 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>")
15
+ end
16
+
17
+ it "correctly parses host-relative url with double quotes" do
18
+ original_body = %{<link href="/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />}
19
+ body = preprocessor.process original_body, root_url, protocol
20
+ expect(body).to eq("<link href=\"http://example.com/stylesheets/application.css\" media=\"screen\" rel=\"stylesheet\" type=\"text/css\" />")
21
+ end
22
+
23
+ it "correctly parses protocol-relative url with single quotes" do
24
+ original_body = %{<link href='//fonts.googleapis.com/css?family=Open+Sans:400,600' rel='stylesheet' type='text/css'>}
25
+ body = preprocessor.process original_body, root_url, protocol
26
+ expect(body).to eq("<link href='http://fonts.googleapis.com/css?family=Open+Sans:400,600' rel='stylesheet' type='text/css'>")
27
+ end
28
+
29
+ it "correctly parses protocol-relative url with double quotes" do
30
+ original_body = %{<link href="//fonts.googleapis.com/css?family=Open+Sans:400,600" rel='stylesheet' type='text/css'>}
31
+ body = preprocessor.process original_body, root_url, protocol
32
+ expect(body).to eq("<link href=\"http://fonts.googleapis.com/css?family=Open+Sans:400,600\" rel='stylesheet' type='text/css'>")
33
+ end
34
+
35
+ it "correctly parses multiple tags where first one is root url" do
36
+ original_body = %{<a href='/'><img src='/logo.jpg' ></a>}
37
+ body = preprocessor.process original_body, root_url, protocol
38
+ expect(body).to eq "<a href='http://example.com/'><img src='http://example.com/logo.jpg' ></a>"
39
+ end
40
+
41
+ it "returns the body even if there are no valid substitutions found" do
42
+ original_body = "NO MATCH"
43
+ body = preprocessor.process original_body, root_url, protocol
44
+ expect(body).to eq("NO MATCH")
45
+ end
46
+
47
+ context 'when root_url is nil' do
48
+ it "returns the body safely, without interpolating" do
49
+ 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>}
50
+ body = preprocessor.process original_body, nil, protocol
51
+ 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>})
52
+ end
53
+ end
54
+
55
+ context 'when protocol is nil' do
56
+ it "returns the body safely, without interpolating" do
57
+ 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>}
58
+ body = preprocessor.process original_body, root_url, nil
59
+ 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>})
60
+ end
61
+ end
62
+
63
+ context 'when root_url and protocol are both nil' do
64
+ it "returns the body safely, without interpolating" do
65
+ 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>}
66
+ body = preprocessor.process original_body, nil, nil
67
+ expect(body).to eq original_body
68
+ end
69
+ end
70
+ end
71
+ end