grover 1.1.7 → 1.1.9

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e791010e46d99d4347edc987fc2a7b90b08d038a1449cf927b57b828e58f1d59
4
- data.tar.gz: 62837cb867ec1728c000c91b33352f01a1c4beb5b0823647dff74946d09c0cbd
3
+ metadata.gz: 82b9a3edaa4f0d20c326f3e79d61b1d2a0190454ac9ffb6867191a9607ec1ea0
4
+ data.tar.gz: 4e71234c41bf19c88a5b44c0a043a1c2ae5a74f94d0606fb0cd627c02e8511a2
5
5
  SHA512:
6
- metadata.gz: 1471dda6f9817f921ae1f7ef88cbdf0ffab143448b1057ef1e3f71f5d91a9fce95b77fbece05f10c0823b51fbc181a4138ae5a6c6db437074c2d95143e90fa4d
7
- data.tar.gz: fad1c63de120fb414aa23ffcf0b2adec6e419550ec2add1c5008ce3e1d855343934bbae4de1e9a14f8c74e974699ad7a74aad2a22bc2d6ef7ba1cd8850a73b36
6
+ metadata.gz: 737079399ecd8ef4c6a88c535495a2aa52fcb9e2e8fa0ca171d3453b58134d795237e606487a9f3dd06afad73baac8fac834a76a33445a1099e168f929f13835
7
+ data.tar.gz: 767e6ebf33ce60336d9afd71859e004b54765910136387ed81b44a12489de7728ff88d0b5f33890dea45314a58476972d31a1f3e9e5e7834f6d68979acf0eb19
@@ -7,7 +7,7 @@ class Grover
7
7
  class Configuration
8
8
  attr_accessor :options, :meta_tag_prefix, :ignore_path, :ignore_request,
9
9
  :root_url, :use_pdf_middleware, :use_png_middleware,
10
- :use_jpeg_middleware
10
+ :use_jpeg_middleware, :node_env_vars, :allow_file_uris
11
11
 
12
12
  def initialize
13
13
  @options = {}
@@ -18,6 +18,8 @@ class Grover
18
18
  @use_pdf_middleware = true
19
19
  @use_png_middleware = false
20
20
  @use_jpeg_middleware = false
21
+ @node_env_vars = {}
22
+ @allow_file_uris = false
21
23
  end
22
24
  end
23
25
  end
data/lib/grover/errors.rb CHANGED
@@ -15,4 +15,5 @@ class Grover
15
15
  const_set name, Class.new(Error)
16
16
  end
17
17
  end
18
+ UnsafeConfigurationError = Class.new(Error)
18
19
  end
@@ -26,7 +26,7 @@ const fs = require('fs');
26
26
  const os = require('os');
27
27
  const path = require('path');
28
28
 
29
- const _processPage = (async (convertAction, urlOrHtml, options) => {
29
+ const _processPage = (async (convertAction, uriOrHtml, options) => {
30
30
  let browser, page, errors = [], tmpDir, wsConnection = false;
31
31
 
32
32
  try {
@@ -100,6 +100,7 @@ const _processPage = (async (convertAction, urlOrHtml, options) => {
100
100
  }
101
101
 
102
102
  // Setup timeout option (if provided)
103
+ if (options.timeout === null) delete options.timeout;
103
104
  let requestOptions = {};
104
105
  let requestTimeout = options.requestTimeout; delete options.requestTimeout;
105
106
  if (requestTimeout === undefined) requestTimeout = options.timeout;
@@ -172,10 +173,12 @@ const _processPage = (async (convertAction, urlOrHtml, options) => {
172
173
  }
173
174
 
174
175
  const waitUntil = options.waitUntil; delete options.waitUntil;
175
- if (urlOrHtml.match(/^http/i)) {
176
+ const allowFileUri = options.allowFileUri; delete options.allowFileUri;
177
+ const uriRegex = allowFileUri ? /^(https?|file):\/\//i : /^https?:\/\//i;
178
+ if (uriOrHtml.match(uriRegex)) {
176
179
  // Request is for a URL, so request it
177
180
  requestOptions.waitUntil = waitUntil || 'networkidle2';
178
- await page.goto(urlOrHtml, requestOptions);
181
+ await page.goto(uriOrHtml, requestOptions);
179
182
  } else {
180
183
  // Request is some HTML content. Use request interception to assign the body
181
184
  requestOptions.waitUntil = waitUntil || 'networkidle0';
@@ -187,7 +190,7 @@ const _processPage = (async (convertAction, urlOrHtml, options) => {
187
190
  request.continue();
188
191
  else {
189
192
  htmlIntercepted = true
190
- request.respond({ body: urlOrHtml === '' ? ' ' : urlOrHtml });
193
+ request.respond({ body: uriOrHtml === '' ? ' ' : uriOrHtml });
191
194
  }
192
195
  });
193
196
  const displayUrl = options.displayUrl; delete options.displayUrl;
@@ -24,11 +24,14 @@ class Grover
24
24
  dup._call(env)
25
25
  end
26
26
 
27
- def _call(env)
27
+ def _call(env) # rubocop:disable Metrics/MethodLength
28
28
  @request = Rack::Request.new(env)
29
29
  identify_request_type
30
30
 
31
- configure_env_for_grover_request(env) if grover_request?
31
+ if grover_request?
32
+ check_file_uri_configuration
33
+ configure_env_for_grover_request(env)
34
+ end
32
35
  status, headers, response = @app.call(env)
33
36
  response = update_response response, headers if grover_request? && html_content?(headers)
34
37
 
@@ -45,6 +48,14 @@ class Grover
45
48
 
46
49
  attr_reader :pdf_request, :png_request, :jpeg_request
47
50
 
51
+ def check_file_uri_configuration
52
+ return unless Grover.configuration.allow_file_uris
53
+
54
+ # The combination of middleware and allowing file URLs is exceptionally
55
+ # unsafe as it can lead to data exfiltration from the host system.
56
+ raise UnsafeConfigurationError, 'using `allow_file_uris` configuration with middleware is exceptionally unsafe'
57
+ end
58
+
48
59
  def identify_request_type
49
60
  @pdf_request = Grover.configuration.use_pdf_middleware && path_matches?(PDF_REGEX)
50
61
  @png_request = Grover.configuration.use_png_middleware && path_matches?(PNG_REGEX)
@@ -8,12 +8,12 @@ class Grover
8
8
  # Build options from Grover.configuration, meta_options, and passed-in options
9
9
  #
10
10
  class OptionsBuilder < Hash
11
- def initialize(options, url)
11
+ def initialize(options, uri)
12
12
  super()
13
- @url = url
13
+ @uri = uri
14
14
  combined = grover_configuration
15
15
  Utils.deep_merge! combined, Utils.deep_stringify_keys(options)
16
- Utils.deep_merge! combined, meta_options unless url_source?
16
+ Utils.deep_merge! combined, meta_options unless uri_source?
17
17
 
18
18
  update OptionsFixer.new(combined).run
19
19
  end
@@ -41,11 +41,11 @@ class Grover
41
41
  end
42
42
 
43
43
  def meta_tags
44
- Nokogiri::HTML(@url).xpath('//meta')
44
+ Nokogiri::HTML(@uri).xpath('//meta')
45
45
  end
46
46
 
47
- def url_source?
48
- @url.match(/\Ahttp/i)
47
+ def uri_source?
48
+ @uri.match?(%r{\A(https?|file)://}i)
49
49
  end
50
50
  end
51
51
  end
@@ -32,6 +32,7 @@ class Grover
32
32
 
33
33
  def spawn_process
34
34
  @stdin, @stdout, @stderr, @wait_thr = Open3.popen3(
35
+ Grover.configuration.node_env_vars,
35
36
  'node',
36
37
  File.expand_path(File.join(__dir__, 'js/processor.cjs')),
37
38
  chdir: app_root
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Grover
4
- VERSION = '1.1.7'
4
+ VERSION = '1.1.9'
5
5
  end
data/lib/grover.rb CHANGED
@@ -28,40 +28,40 @@ class Grover
28
28
  attr_reader :front_cover_path, :back_cover_path
29
29
 
30
30
  #
31
- # @param [String] url URL of the page to convert
31
+ # @param [String] uri URI of the page to convert
32
32
  # @param [Hash] options Optional parameters to pass to PDF processor
33
33
  # see https://github.com/puppeteer/puppeteer/blob/main/docs/api/puppeteer.pdfoptions.md
34
34
  # and https://github.com/puppeteer/puppeteer/blob/main/docs/api/puppeteer.screenshotoptions.md
35
35
  #
36
- def initialize(url, **options)
37
- @url = url.to_s
38
- @options = OptionsBuilder.new(options, @url)
36
+ def initialize(uri, **options)
37
+ @uri = uri.to_s
38
+ @options = OptionsBuilder.new(options, @uri)
39
39
  @root_path = @options.delete 'root_path'
40
40
  @front_cover_path = @options.delete 'front_cover_path'
41
41
  @back_cover_path = @options.delete 'back_cover_path'
42
42
  end
43
43
 
44
44
  #
45
- # Request URL with provided options and create PDF
45
+ # Request URI with provided options and create PDF
46
46
  #
47
47
  # @param [String] path Optional path to write the PDF to
48
48
  # @return [String] The resulting PDF data
49
49
  #
50
50
  def to_pdf(path = nil)
51
- processor.convert :pdf, @url, normalized_options(path: path)
51
+ processor.convert :pdf, @uri, normalized_options(path: path)
52
52
  end
53
53
 
54
54
  #
55
- # Request URL with provided options and render HTML
55
+ # Request URI with provided options and render HTML
56
56
  #
57
57
  # @return [String] The resulting HTML string
58
58
  #
59
59
  def to_html
60
- processor.convert :content, @url, normalized_options(path: nil)
60
+ processor.convert :content, @uri, normalized_options(path: nil)
61
61
  end
62
62
 
63
63
  #
64
- # Request URL with provided options and create screenshot
64
+ # Request URI with provided options and create screenshot
65
65
  #
66
66
  # @param [String] path Optional path to write the screenshot to
67
67
  # @param [String] format Optional format of the screenshot
@@ -70,11 +70,11 @@ class Grover
70
70
  def screenshot(path: nil, format: nil)
71
71
  options = normalized_options(path: path)
72
72
  options['type'] = format if %w[png jpeg].include? format
73
- processor.convert :screenshot, @url, options
73
+ processor.convert :screenshot, @uri, options
74
74
  end
75
75
 
76
76
  #
77
- # Request URL with provided options and create PNG
77
+ # Request URI with provided options and create PNG
78
78
  #
79
79
  # @param [String] path Optional path to write the screenshot to
80
80
  # @return [String] The resulting PNG data
@@ -84,7 +84,7 @@ class Grover
84
84
  end
85
85
 
86
86
  #
87
- # Request URL with provided options and create JPEG
87
+ # Request URI with provided options and create JPEG
88
88
  #
89
89
  # @param [String] path Optional path to write the screenshot to
90
90
  # @return [String] The resulting JPEG data
@@ -116,10 +116,10 @@ class Grover
116
116
  #
117
117
  def inspect
118
118
  format(
119
- '#<%<class_name>s:0x%<object_id>p @url="%<url>s">',
119
+ '#<%<class_name>s:0x%<object_id>p @uri="%<uri>s">',
120
120
  class_name: self.class.name,
121
121
  object_id: object_id,
122
- url: @url
122
+ uri: @uri
123
123
  )
124
124
  end
125
125
 
@@ -147,6 +147,7 @@ class Grover
147
147
  def normalized_options(path:)
148
148
  normalized_options = Utils.normalize_object @options, excluding: ['extraHTTPHeaders']
149
149
  normalized_options['path'] = path if path.is_a? ::String
150
+ normalized_options['allowFileUri'] = Grover.configuration.allow_file_uris == true
150
151
  normalized_options
151
152
  end
152
153
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grover
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.7
4
+ version: 1.1.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Bromwich
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-03-09 00:00:00.000000000 Z
11
+ date: 2024-07-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: combine_pdf
@@ -38,180 +38,6 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.0'
41
- - !ruby/object:Gem::Dependency
42
- name: childprocess
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: mini_magick
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '4.12'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '4.12'
69
- - !ruby/object:Gem::Dependency
70
- name: pdf-reader
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '2.11'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '2.11'
83
- - !ruby/object:Gem::Dependency
84
- name: puma
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '6.4'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '6.4'
97
- - !ruby/object:Gem::Dependency
98
- name: rack-test
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: '1.1'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: '1.1'
111
- - !ruby/object:Gem::Dependency
112
- name: rake
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: '13.0'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - "~>"
123
- - !ruby/object:Gem::Version
124
- version: '13.0'
125
- - !ruby/object:Gem::Dependency
126
- name: rspec
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - "~>"
130
- - !ruby/object:Gem::Version
131
- version: '3.12'
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - "~>"
137
- - !ruby/object:Gem::Version
138
- version: '3.12'
139
- - !ruby/object:Gem::Dependency
140
- name: rubocop
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - "~>"
144
- - !ruby/object:Gem::Version
145
- version: '1.43'
146
- type: :development
147
- prerelease: false
148
- version_requirements: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - "~>"
151
- - !ruby/object:Gem::Version
152
- version: '1.43'
153
- - !ruby/object:Gem::Dependency
154
- name: rubocop-rake
155
- requirement: !ruby/object:Gem::Requirement
156
- requirements:
157
- - - "~>"
158
- - !ruby/object:Gem::Version
159
- version: '0.6'
160
- type: :development
161
- prerelease: false
162
- version_requirements: !ruby/object:Gem::Requirement
163
- requirements:
164
- - - "~>"
165
- - !ruby/object:Gem::Version
166
- version: '0.6'
167
- - !ruby/object:Gem::Dependency
168
- name: rubocop-rspec
169
- requirement: !ruby/object:Gem::Requirement
170
- requirements:
171
- - - "~>"
172
- - !ruby/object:Gem::Version
173
- version: '2.18'
174
- type: :development
175
- prerelease: false
176
- version_requirements: !ruby/object:Gem::Requirement
177
- requirements:
178
- - - "~>"
179
- - !ruby/object:Gem::Version
180
- version: '2.18'
181
- - !ruby/object:Gem::Dependency
182
- name: sinatra
183
- requirement: !ruby/object:Gem::Requirement
184
- requirements:
185
- - - "~>"
186
- - !ruby/object:Gem::Version
187
- version: '3.2'
188
- type: :development
189
- prerelease: false
190
- version_requirements: !ruby/object:Gem::Requirement
191
- requirements:
192
- - - "~>"
193
- - !ruby/object:Gem::Version
194
- version: '3.2'
195
- - !ruby/object:Gem::Dependency
196
- name: simplecov
197
- requirement: !ruby/object:Gem::Requirement
198
- requirements:
199
- - - "~>"
200
- - !ruby/object:Gem::Version
201
- version: '0.17'
202
- - - "<"
203
- - !ruby/object:Gem::Version
204
- version: '0.18'
205
- type: :development
206
- prerelease: false
207
- version_requirements: !ruby/object:Gem::Requirement
208
- requirements:
209
- - - "~>"
210
- - !ruby/object:Gem::Version
211
- version: '0.17'
212
- - - "<"
213
- - !ruby/object:Gem::Version
214
- version: '0.18'
215
41
  description: Transform HTML into PDF/PNG/JPEG using Google Puppeteer/Chromium
216
42
  email:
217
43
  - abromwich@studiosity.com