railspress-engine 1.4.3 → 1.4.4
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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 48860d966cd3cdc8832ba51098ea94bf8b7264608105231af1b7871d4576d1c8
|
|
4
|
+
data.tar.gz: e4e9e1631fa9127c0c221e08c081e5280e85a7f7f7f3716814033f722aac921b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0523661e660f0d5490b95282ed786669282ea2edfe29a84397fafd10c22e1c599d383a9a40c4b3e2c1e9efa4476a82e24a5236a231c44d27041355986a100803
|
|
7
|
+
data.tar.gz: 699ae2eba634ef60e19f7c18b8f98bbd3470c61dcaa750eeff7d6ece5a07436bd33ab19e37740540f93f3621c3961bb3066abcb650717d1658cbc8b24232a97d
|
data/README.md
CHANGED
|
@@ -157,6 +157,16 @@ end
|
|
|
157
157
|
|
|
158
158
|
See [CONFIGURING.md](docs/CONFIGURING.md) for more authentication patterns including Devise integration.
|
|
159
159
|
|
|
160
|
+
## Import Security
|
|
161
|
+
|
|
162
|
+
RailsPress stores imported files inside engine-owned temporary directories.
|
|
163
|
+
|
|
164
|
+
ZIP image references cannot escape their extraction directory.
|
|
165
|
+
|
|
166
|
+
Remote header images accept public HTTP and HTTPS destinations. Private and redirected destinations are rejected.
|
|
167
|
+
|
|
168
|
+
Configure request and worker limits for additional availability protection.
|
|
169
|
+
|
|
160
170
|
## Quick Start
|
|
161
171
|
|
|
162
172
|
Access the admin at `/railspress/admin`. From there you can manage posts, entities, and blocks.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
module Railspress
|
|
2
2
|
module Admin
|
|
3
3
|
class ImportsController < BaseController
|
|
4
|
+
SUPPORTED_IMPORT_EXTENSIONS = %w[.md .markdown .txt .zip].freeze
|
|
5
|
+
|
|
4
6
|
before_action :validate_import_type, only: [ :show ]
|
|
5
7
|
|
|
6
8
|
def show
|
|
@@ -53,11 +55,18 @@ module Railspress
|
|
|
53
55
|
FileUtils.mkdir_p(upload_dir)
|
|
54
56
|
|
|
55
57
|
uploaded_files.map do |file|
|
|
56
|
-
path = upload_dir.join(file
|
|
58
|
+
path = upload_dir.join(stored_upload_name(file))
|
|
57
59
|
File.open(path, "wb") { |f| f.write(file.read) }
|
|
58
60
|
path.to_s
|
|
59
61
|
end
|
|
60
62
|
end
|
|
63
|
+
|
|
64
|
+
def stored_upload_name(file)
|
|
65
|
+
extension = File.extname(file.original_filename.to_s).downcase
|
|
66
|
+
extension = "" unless SUPPORTED_IMPORT_EXTENSIONS.include?(extension)
|
|
67
|
+
|
|
68
|
+
"#{SecureRandom.hex(16)}#{extension}"
|
|
69
|
+
end
|
|
61
70
|
end
|
|
62
71
|
end
|
|
63
72
|
end
|
|
@@ -30,12 +30,15 @@ module Railspress
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def cleanup_uploaded_files(file_paths)
|
|
33
|
-
tmp_dir = Rails.root.join("tmp")
|
|
33
|
+
tmp_dir = File.realpath(Rails.root.join("tmp"))
|
|
34
34
|
|
|
35
35
|
file_paths.each do |path|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
next unless File.exist?(path)
|
|
37
|
+
|
|
38
|
+
resolved_path = File.realpath(path)
|
|
39
|
+
next unless resolved_path.start_with?("#{tmp_dir}#{File::SEPARATOR}")
|
|
40
|
+
|
|
41
|
+
FileUtils.rm_f(resolved_path)
|
|
39
42
|
end
|
|
40
43
|
rescue => e
|
|
41
44
|
Rails.logger.warn "Failed to cleanup uploaded import files: #{e.message}"
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
require "yaml"
|
|
2
2
|
require "zip"
|
|
3
|
-
require "open-uri"
|
|
4
3
|
require "fileutils"
|
|
5
4
|
require "redcarpet"
|
|
5
|
+
require "ssrf_filter"
|
|
6
|
+
require "stringio"
|
|
6
7
|
|
|
7
8
|
module Railspress
|
|
8
9
|
class PostImportProcessor
|
|
@@ -211,29 +212,20 @@ module Railspress
|
|
|
211
212
|
filename = File.basename(uri.path)
|
|
212
213
|
filename = "header_image#{File.extname(uri.path)}" if filename.blank?
|
|
213
214
|
|
|
214
|
-
|
|
215
|
+
response = SsrfFilter.get(url, max_redirects: 0)
|
|
216
|
+
response.value
|
|
215
217
|
post.header_image.attach(
|
|
216
|
-
io:
|
|
218
|
+
io: StringIO.new(response.body),
|
|
217
219
|
filename: filename,
|
|
218
|
-
content_type:
|
|
220
|
+
content_type: response.content_type
|
|
219
221
|
)
|
|
220
|
-
rescue
|
|
222
|
+
rescue SsrfFilter::Error, URI::InvalidURIError, Net::HTTPError => e
|
|
221
223
|
Rails.logger.warn "Failed to download header image from #{url}: #{e.message}"
|
|
222
224
|
end
|
|
223
225
|
|
|
224
226
|
def attach_image_from_zip(post, relative_path, source_path, base_dir)
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
# Try relative to the markdown file first
|
|
229
|
-
image_path = File.expand_path(relative_path, source_dir)
|
|
230
|
-
|
|
231
|
-
# If not found, try relative to base_dir
|
|
232
|
-
unless File.exist?(image_path)
|
|
233
|
-
image_path = File.expand_path(relative_path, base_dir)
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
return unless File.exist?(image_path)
|
|
227
|
+
image_path = imported_image_path(relative_path, source_path, base_dir)
|
|
228
|
+
return unless image_path
|
|
237
229
|
|
|
238
230
|
ext = File.extname(image_path).downcase
|
|
239
231
|
return unless IMAGE_EXTENSIONS.include?(ext)
|
|
@@ -253,6 +245,23 @@ module Railspress
|
|
|
253
245
|
)
|
|
254
246
|
end
|
|
255
247
|
|
|
248
|
+
def imported_image_path(relative_path, source_path, base_dir)
|
|
249
|
+
import_root = File.realpath(base_dir)
|
|
250
|
+
source_dir = File.dirname(source_path)
|
|
251
|
+
|
|
252
|
+
[ source_dir, import_root ].each do |directory|
|
|
253
|
+
candidate = File.expand_path(relative_path.to_s, directory)
|
|
254
|
+
next unless File.file?(candidate)
|
|
255
|
+
|
|
256
|
+
resolved_candidate = File.realpath(candidate)
|
|
257
|
+
return resolved_candidate if resolved_candidate.start_with?("#{import_root}#{File::SEPARATOR}")
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
nil
|
|
261
|
+
rescue Errno::EACCES, Errno::ENOENT
|
|
262
|
+
nil
|
|
263
|
+
end
|
|
264
|
+
|
|
256
265
|
def extract_title_from_body(body)
|
|
257
266
|
# Look for first H1: # Title
|
|
258
267
|
if match = body.match(/^#\s+(.+)$/)
|
data/lib/railspress/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: railspress-engine
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.4.
|
|
4
|
+
version: 1.4.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Avi Flombaum
|
|
@@ -85,6 +85,20 @@ dependencies:
|
|
|
85
85
|
- - ">="
|
|
86
86
|
- !ruby/object:Gem::Version
|
|
87
87
|
version: '3.6'
|
|
88
|
+
- !ruby/object:Gem::Dependency
|
|
89
|
+
name: ssrf_filter
|
|
90
|
+
requirement: !ruby/object:Gem::Requirement
|
|
91
|
+
requirements:
|
|
92
|
+
- - "~>"
|
|
93
|
+
- !ruby/object:Gem::Version
|
|
94
|
+
version: '1.5'
|
|
95
|
+
type: :runtime
|
|
96
|
+
prerelease: false
|
|
97
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
98
|
+
requirements:
|
|
99
|
+
- - "~>"
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '1.5'
|
|
88
102
|
- !ruby/object:Gem::Dependency
|
|
89
103
|
name: rspec-rails
|
|
90
104
|
requirement: !ruby/object:Gem::Requirement
|