wicked_pdf 2.1.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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