wicked_pdf 2.1.0 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,229 @@
1
+ class WickedPdf
2
+ class OptionParser
3
+ BINARY_VERSION_WITHOUT_DASHES = Gem::Version.new('0.12.0')
4
+
5
+ attr_reader :binary_version
6
+
7
+ def initialize(binary_version = WickedPdf::DEFAULT_BINARY_VERSION)
8
+ @binary_version = binary_version
9
+ end
10
+
11
+ def parse(options)
12
+ [
13
+ parse_extra(options),
14
+ parse_others(options),
15
+ parse_global(options),
16
+ parse_outline(options.delete(:outline)),
17
+ parse_header_footer(:header => options.delete(:header),
18
+ :footer => options.delete(:footer),
19
+ :layout => options[:layout]),
20
+ parse_cover(options.delete(:cover)),
21
+ parse_toc(options.delete(:toc)),
22
+ parse_basic_auth(options)
23
+ ].flatten
24
+ end
25
+
26
+ def valid_option(name)
27
+ if binary_version < BINARY_VERSION_WITHOUT_DASHES
28
+ "--#{name}"
29
+ else
30
+ name
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def parse_extra(options)
37
+ return [] if options[:extra].nil?
38
+ return options[:extra].split if options[:extra].respond_to?(:split)
39
+
40
+ options[:extra]
41
+ end
42
+
43
+ def parse_basic_auth(options)
44
+ if options[:basic_auth]
45
+ user, passwd = Base64.decode64(options[:basic_auth]).split(':')
46
+ ['--username', user, '--password', passwd]
47
+ else
48
+ []
49
+ end
50
+ end
51
+
52
+ def parse_header_footer(options)
53
+ r = []
54
+ unless options.blank?
55
+ %i[header footer].collect do |hf|
56
+ next if options[hf].blank?
57
+
58
+ opt_hf = options[hf]
59
+ r += make_options(opt_hf, %i[center font_name left right], hf.to_s)
60
+ r += make_options(opt_hf, %i[font_size spacing], hf.to_s, :numeric)
61
+ r += make_options(opt_hf, [:line], hf.to_s, :boolean)
62
+ if options[hf] && options[hf][:content]
63
+ @hf_tempfiles = [] unless defined?(@hf_tempfiles)
64
+ @hf_tempfiles.push(tf = WickedPdf::Tempfile.new("wicked_#{hf}_pdf.html"))
65
+ tf.write options[hf][:content]
66
+ tf.flush
67
+ options[hf][:html] = {}
68
+ options[hf][:html][:url] = "file:///#{tf.path}"
69
+ end
70
+ unless opt_hf[:html].blank?
71
+ r += make_option("#{hf}-html", opt_hf[:html][:url]) unless opt_hf[:html][:url].blank?
72
+ end
73
+ end
74
+ end
75
+ r
76
+ end
77
+
78
+ def parse_cover(argument)
79
+ arg = argument.to_s
80
+ return [] if arg.blank?
81
+
82
+ # Filesystem path or URL - hand off to wkhtmltopdf
83
+ if argument.is_a?(Pathname) || (arg[0, 4] == 'http')
84
+ [valid_option('cover'), arg]
85
+ else # HTML content
86
+ @hf_tempfiles ||= []
87
+ @hf_tempfiles << tf = WickedPdf::Tempfile.new('wicked_cover_pdf.html')
88
+ tf.write arg
89
+ tf.flush
90
+ [valid_option('cover'), tf.path]
91
+ end
92
+ end
93
+
94
+ def parse_toc(options)
95
+ return [] if options.nil?
96
+
97
+ r = [valid_option('toc')]
98
+ unless options.blank?
99
+ r += make_options(options, %i[font_name header_text], 'toc')
100
+ r += make_options(options, [:xsl_style_sheet])
101
+ r += make_options(options, %i[depth
102
+ header_fs
103
+ text_size_shrink
104
+ l1_font_size
105
+ l2_font_size
106
+ l3_font_size
107
+ l4_font_size
108
+ l5_font_size
109
+ l6_font_size
110
+ l7_font_size
111
+ level_indentation
112
+ l1_indentation
113
+ l2_indentation
114
+ l3_indentation
115
+ l4_indentation
116
+ l5_indentation
117
+ l6_indentation
118
+ l7_indentation], 'toc', :numeric)
119
+ r += make_options(options, %i[no_dots
120
+ disable_links
121
+ disable_back_links], 'toc', :boolean)
122
+ r += make_options(options, %i[disable_dotted_lines
123
+ disable_toc_links], nil, :boolean)
124
+ end
125
+ r
126
+ end
127
+
128
+ def parse_outline(options)
129
+ r = []
130
+ unless options.blank?
131
+ r = make_options(options, [:outline], '', :boolean)
132
+ r += make_options(options, [:outline_depth], '', :numeric)
133
+ end
134
+ r
135
+ end
136
+
137
+ def parse_margins(options)
138
+ make_options(options, %i[top bottom left right], 'margin', :numeric)
139
+ end
140
+
141
+ def parse_global(options)
142
+ r = []
143
+ unless options.blank?
144
+ r += make_options(options, %i[orientation
145
+ dpi
146
+ page_size
147
+ page_width
148
+ title
149
+ log_level])
150
+ r += make_options(options, %i[lowquality
151
+ grayscale
152
+ no_pdf_compression
153
+ quiet], '', :boolean)
154
+ r += make_options(options, %i[image_dpi
155
+ image_quality
156
+ page_height], '', :numeric)
157
+ r += parse_margins(options.delete(:margin))
158
+ end
159
+ r
160
+ end
161
+
162
+ def parse_others(options)
163
+ r = []
164
+ unless options.blank?
165
+ r += make_options(options, %i[proxy
166
+ username
167
+ password
168
+ encoding
169
+ user_style_sheet
170
+ viewport_size
171
+ window_status])
172
+ r += make_options(options, %i[cookie
173
+ post], '', :name_value)
174
+ r += make_options(options, %i[redirect_delay
175
+ zoom
176
+ page_offset
177
+ javascript_delay], '', :numeric)
178
+ r += make_options(options, %i[book
179
+ default_header
180
+ disable_javascript
181
+ enable_plugins
182
+ disable_internal_links
183
+ disable_external_links
184
+ keep_relative_links
185
+ print_media_type
186
+ disable_local_file_access
187
+ enable_local_file_access
188
+ disable_smart_shrinking
189
+ use_xserver
190
+ no_background
191
+ images
192
+ no_images
193
+ no_stop_slow_scripts], '', :boolean)
194
+ end
195
+ r
196
+ end
197
+
198
+ def make_options(options, names, prefix = '', type = :string)
199
+ return [] if options.nil?
200
+
201
+ names.collect do |o|
202
+ if options[o].blank?
203
+ []
204
+ else
205
+ make_option("#{prefix.blank? ? '' : prefix + '-'}#{o}",
206
+ options[o],
207
+ type)
208
+ end
209
+ end
210
+ end
211
+
212
+ def make_option(name, value, type = :string)
213
+ return value.collect { |v| make_option(name, v, type) } if value.is_a?(Array)
214
+
215
+ if type == :name_value
216
+ parts = value.to_s.split(' ')
217
+ ["--#{name.tr('_', '-')}", *parts]
218
+ elsif type == :boolean
219
+ if value
220
+ ["--#{name.tr('_', '-')}"]
221
+ else
222
+ []
223
+ end
224
+ else
225
+ ["--#{name.tr('_', '-')}", value.to_s]
226
+ end
227
+ end
228
+ end
229
+ end
@@ -1,21 +1,5 @@
1
1
  class WickedPdf
2
2
  module PdfHelper
3
- def self.included(base)
4
- # Protect from trying to augment modules that appear
5
- # as the result of adding other gems.
6
- return if base != ActionController::Base
7
-
8
- base.class_eval do
9
- alias_method_chain :render, :wicked_pdf
10
- alias_method_chain :render_to_string, :wicked_pdf
11
- if respond_to?(:after_action)
12
- after_action :clean_temp_files
13
- else
14
- after_filter :clean_temp_files
15
- end
16
- end
17
- end
18
-
19
3
  def self.prepended(base)
20
4
  # Protect from trying to augment modules that appear
21
5
  # as the result of adding other gems.
@@ -26,52 +10,52 @@ class WickedPdf
26
10
  end
27
11
  end
28
12
 
29
- def render(options = nil, *args, &block)
30
- render_with_wicked_pdf(options, *args, &block)
31
- end
32
-
33
- def render_to_string(options = nil, *args, &block)
34
- render_to_string_with_wicked_pdf(options, *args, &block)
35
- end
36
-
37
- def render_with_wicked_pdf(options = nil, *args, &block)
13
+ def render(*args)
14
+ options = args.first
38
15
  if options.is_a?(Hash) && options.key?(:pdf)
39
- options[:basic_auth] = set_basic_auth(options)
40
- make_and_send_pdf(options.delete(:pdf), (WickedPdf.config || {}).merge(options))
41
- elsif respond_to?(:render_without_wicked_pdf)
42
- # support alias_method_chain (module included)
43
- render_without_wicked_pdf(options, *args, &block)
16
+ render_with_wicked_pdf(options)
44
17
  else
45
- # support inheritance (module prepended)
46
- method(:render).super_method.call(options, *args, &block)
18
+ super
47
19
  end
48
20
  end
49
21
 
50
- def render_to_string_with_wicked_pdf(options = nil, *args, &block)
22
+ def render_to_string(*args)
23
+ options = args.first
51
24
  if options.is_a?(Hash) && options.key?(:pdf)
52
- options[:basic_auth] = set_basic_auth(options)
53
- options.delete :pdf
54
- make_pdf((WickedPdf.config || {}).merge(options))
55
- elsif respond_to?(:render_to_string_without_wicked_pdf)
56
- # support alias_method_chain (module included)
57
- render_to_string_without_wicked_pdf(options, *args, &block)
25
+ render_to_string_with_wicked_pdf(options, *args, &block)
58
26
  else
59
- # support inheritance (module prepended)
60
- method(:render_to_string).super_method.call(options, *args, &block)
27
+ super
61
28
  end
62
29
  end
63
30
 
31
+ def render_with_wicked_pdf(options)
32
+ raise ArgumentError, 'missing keyword: pdf' unless options.is_a?(Hash) && options.key?(:pdf)
33
+
34
+ options[:basic_auth] = set_basic_auth(options)
35
+ make_and_send_pdf(options.delete(:pdf), (WickedPdf.config || {}).merge(options))
36
+ end
37
+
38
+ def render_to_string_with_wicked_pdf(options)
39
+ raise ArgumentError, 'missing keyword: pdf' unless options.is_a?(Hash) && options.key?(:pdf)
40
+
41
+ options[:basic_auth] = set_basic_auth(options)
42
+ options.delete :pdf
43
+ make_pdf((WickedPdf.config || {}).merge(options))
44
+ end
45
+
64
46
  private
65
47
 
66
48
  def set_basic_auth(options = {})
67
49
  options[:basic_auth] ||= WickedPdf.config.fetch(:basic_auth) { false }
68
50
  return unless options[:basic_auth] && request.env['HTTP_AUTHORIZATION']
51
+
69
52
  request.env['HTTP_AUTHORIZATION'].split(' ').last
70
53
  end
71
54
 
72
55
  def clean_temp_files
73
56
  return unless defined?(@hf_tempfiles)
74
- @hf_tempfiles.each(&:close!)
57
+
58
+ @hf_tempfiles.each(&:close)
75
59
  end
76
60
 
77
61
  def make_pdf(options = {})
@@ -119,10 +103,11 @@ class WickedPdf
119
103
  # Given an options hash, prerenders content for the header and footer sections
120
104
  # to temp files and return a new options hash including the URLs to these files.
121
105
  def prerender_header_and_footer(options)
122
- [:header, :footer].each do |hf|
106
+ %i[header footer].each do |hf|
123
107
  next unless options[hf] && options[hf][:html] && options[hf][:html][:template]
108
+
124
109
  @hf_tempfiles = [] unless defined?(@hf_tempfiles)
125
- @hf_tempfiles.push(tf = WickedPdfTempfile.new("wicked_#{hf}_pdf.html"))
110
+ @hf_tempfiles.push(tf = WickedPdf::Tempfile.new("wicked_#{hf}_pdf.html"))
126
111
  options[hf][:html][:layout] ||= options[:layout]
127
112
  render_opts = {
128
113
  :template => options[hf][:html][:template],
@@ -6,21 +6,12 @@ class WickedPdf
6
6
  if defined?(Rails.env)
7
7
  class WickedRailtie < Rails::Railtie
8
8
  initializer 'wicked_pdf.register', :after => 'remotipart.controller_helper' do |_app|
9
- ActiveSupport.on_load(:action_controller) do
10
- if ActionController::Base.respond_to?(:prepend) &&
11
- Object.method(:new).respond_to?(:super_method)
12
- ActionController::Base.send :prepend, PdfHelper
13
- else
14
- ActionController::Base.send :include, PdfHelper
15
- end
16
- ActionView::Base.send :include, WickedPdfHelper::Assets
17
- end
9
+ ActiveSupport.on_load(:action_controller) { ActionController::Base.send :prepend, PdfHelper }
10
+ ActiveSupport.on_load(:action_view) { include WickedPdfHelper::Assets }
18
11
  end
19
12
  end
20
13
 
21
- if Mime::Type.lookup_by_extension(:pdf).nil?
22
- Mime::Type.register('application/pdf', :pdf)
23
- end
14
+ Mime::Type.register('application/pdf', :pdf) if Mime::Type.lookup_by_extension(:pdf).nil?
24
15
 
25
16
  end
26
17
  end
@@ -1,13 +1,42 @@
1
1
  require 'tempfile'
2
2
 
3
3
  class WickedPdf
4
- class WickedPdfTempfile < Tempfile
5
- # ensures the Tempfile's filename always keeps its extension
4
+ class Tempfile < ::Tempfile
6
5
  def initialize(filename, temp_dir = nil)
7
6
  temp_dir ||= Dir.tmpdir
8
7
  extension = File.extname(filename)
9
- basename = File.basename(filename, extension)
8
+ basename = File.basename(filename, extension)
10
9
  super([basename, extension], temp_dir)
11
10
  end
11
+
12
+ def write_in_chunks(input_string)
13
+ binmode
14
+ string_io = StringIO.new(input_string)
15
+ write(string_io.read(chunk_size)) until string_io.eof?
16
+ close
17
+ self
18
+ rescue Errno::EINVAL => e
19
+ raise e, file_too_large_message
20
+ end
21
+
22
+ def read_in_chunks
23
+ rewind
24
+ binmode
25
+ output_string = ''
26
+ output_string << read(chunk_size) until eof?
27
+ output_string
28
+ rescue Errno::EINVAL => e
29
+ raise e, file_too_large_message
30
+ end
31
+
32
+ private
33
+
34
+ def chunk_size
35
+ 1024 * 1024
36
+ end
37
+
38
+ def file_too_large_message
39
+ 'The HTML file is too large! Try reducing the size or using the return_file option instead.'
40
+ end
12
41
  end
13
42
  end
@@ -1,3 +1,3 @@
1
1
  class WickedPdf
2
- VERSION = '2.1.0'.freeze
2
+ VERSION = '2.6.0'.freeze
3
3
  end
@@ -1,19 +1,46 @@
1
1
  require 'net/http'
2
- # If webpacker is used, need to check for version
3
- require 'webpacker/version' if defined?(Webpacker)
4
2
 
5
3
  class WickedPdf
6
4
  module WickedPdfHelper
7
5
  module Assets
8
6
  ASSET_URL_REGEX = /url\(['"]?([^'"]+?)['"]?\)/
9
7
 
8
+ class PropshaftAsset < SimpleDelegator
9
+ def content_type
10
+ super.to_s
11
+ end
12
+
13
+ def to_s
14
+ content
15
+ end
16
+
17
+ def filename
18
+ path.to_s
19
+ end
20
+ end
21
+
10
22
  def wicked_pdf_asset_base64(path)
11
23
  asset = find_asset(path)
12
24
  raise "Could not find asset '#{path}'" if asset.nil?
25
+
13
26
  base64 = Base64.encode64(asset.to_s).gsub(/\s+/, '')
14
27
  "data:#{asset.content_type};base64,#{Rack::Utils.escape(base64)}"
15
28
  end
16
29
 
30
+ # Using `image_tag` with URLs when generating PDFs (specifically large PDFs with lots of pages) can cause buffer/stack overflows.
31
+ #
32
+ def wicked_pdf_url_base64(url)
33
+ response = Net::HTTP.get_response(URI(url))
34
+
35
+ if response.is_a?(Net::HTTPSuccess)
36
+ base64 = Base64.encode64(response.body).gsub(/\s+/, '')
37
+ "data:#{response.content_type};base64,#{Rack::Utils.escape(base64)}"
38
+ else
39
+ Rails.logger.warn("[wicked_pdf] #{response.code} #{response.message}: #{url}")
40
+ nil
41
+ end
42
+ end
43
+
17
44
  def wicked_pdf_stylesheet_link_tag(*sources)
18
45
  stylesheet_contents = sources.collect do |source|
19
46
  source = WickedPdfHelper.add_extension(source, 'css')
@@ -31,6 +58,7 @@ class WickedPdf
31
58
 
32
59
  def wicked_pdf_stylesheet_pack_tag(*sources)
33
60
  return unless defined?(Webpacker)
61
+
34
62
  if running_in_development?
35
63
  stylesheet_pack_tag(*sources)
36
64
  else
@@ -118,6 +146,8 @@ class WickedPdf
118
146
  def find_asset(path)
119
147
  if Rails.application.assets.respond_to?(:find_asset)
120
148
  Rails.application.assets.find_asset(path, :base_path => Rails.application.root.to_s)
149
+ elsif defined?(Propshaft::Assembly) && Rails.application.assets.is_a?(Propshaft::Assembly)
150
+ PropshaftAsset.new(Rails.application.assets.load_path.find(path))
121
151
  else
122
152
  Sprockets::Railtie.build_environment(Rails.application).find_asset(path, :base_path => Rails.application.root.to_s)
123
153
  end
@@ -171,10 +201,15 @@ class WickedPdf
171
201
  end
172
202
 
173
203
  def webpacker_source_url(source)
174
- return unless defined?(Webpacker) && defined?(Webpacker::VERSION)
204
+ return unless webpacker_version
205
+
175
206
  # In Webpacker 3.2.0 asset_pack_url is introduced
176
- if Webpacker::VERSION >= '3.2.0'
177
- asset_pack_url(source)
207
+ if webpacker_version >= '3.2.0'
208
+ if (host = Rails.application.config.asset_host)
209
+ asset_pack_path(source, :host => host)
210
+ else
211
+ asset_pack_url(source)
212
+ end
178
213
  else
179
214
  source_path = asset_pack_path(source)
180
215
  # Remove last slash from root path
@@ -183,7 +218,8 @@ class WickedPdf
183
218
  end
184
219
 
185
220
  def running_in_development?
186
- return unless defined?(Webpacker)
221
+ return unless webpacker_version
222
+
187
223
  # :dev_server method was added in webpacker 3.0.0
188
224
  if Webpacker.respond_to?(:dev_server)
189
225
  Webpacker.dev_server.running?
@@ -191,6 +227,15 @@ class WickedPdf
191
227
  Rails.env.development? || Rails.env.test?
192
228
  end
193
229
  end
230
+
231
+ def webpacker_version
232
+ return unless defined?(Webpacker)
233
+
234
+ # If webpacker is used, need to check for version
235
+ require 'webpacker/version'
236
+
237
+ Webpacker::VERSION
238
+ end
194
239
  end
195
240
  end
196
241
  end