jekyll-remote-theme 0.5.1 → 0.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.
- checksums.yaml +4 -4
- data/lib/jekyll-remote-theme/downloader.rb +59 -51
- data/lib/jekyll-remote-theme/http.rb +48 -0
- data/lib/jekyll-remote-theme/munger.rb +19 -6
- data/lib/jekyll-remote-theme/theme.rb +1 -5
- data/lib/jekyll-remote-theme/version.rb +1 -1
- data/lib/jekyll-remote-theme.rb +1 -0
- metadata +6 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e8b60407212cb023740ee7d4258e9a02c862bed37246db406607af4d3e6228e8
|
|
4
|
+
data.tar.gz: 448d9d30e4f0e0a2efa300c1ab7bdd8199ff82fcc4afd97264227892d6ab5fe3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 36eacae972f56890144e128ec284067fa33f03f528b4f70047b0c60e4f90e80f04426990a6abb6ea87d1d6cbecac1b12effec5dcafbe89c7eb6abd9d1160c357
|
|
7
|
+
data.tar.gz: '0871deb358244f78aa294b171af43415eb0bed1367c3224554992c1723dbae092d48d0bc321c116fd0297b1e22d7847e030599c748a6fef5b5192a9f1ccdfa93'
|
|
@@ -6,6 +6,9 @@ module Jekyll
|
|
|
6
6
|
PROJECT_URL = "https://github.com/benbalter/jekyll-remote-theme"
|
|
7
7
|
USER_AGENT = "Jekyll Remote Theme/#{VERSION} (+#{PROJECT_URL})"
|
|
8
8
|
MAX_FILE_SIZE = 1 * (1024 * 1024 * 1024) # Size in bytes (1 GB)
|
|
9
|
+
# Limit on the theme's total size once extracted, to stop zip bombs
|
|
10
|
+
MAX_EXTRACTED_SIZE = 2 * (1024 * 1024 * 1024) # Size in bytes (2 GB)
|
|
11
|
+
EXTRACT_CHUNK_SIZE = 64 * 1024
|
|
9
12
|
NET_HTTP_ERRORS = [
|
|
10
13
|
Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::OpenTimeout,
|
|
11
14
|
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError,
|
|
@@ -45,23 +48,30 @@ module Jekyll
|
|
|
45
48
|
@zip_file ||= Tempfile.new([TEMP_PREFIX, ".zip"], :binmode => true)
|
|
46
49
|
end
|
|
47
50
|
|
|
48
|
-
# rubocop:disable Metrics/AbcSize
|
|
49
51
|
def download
|
|
50
52
|
Jekyll.logger.debug LOG_KEY, "Downloading #{zip_url} to #{zip_file.path}"
|
|
51
|
-
|
|
53
|
+
HTTP.start(zip_url, :use_ssl => true) do |http|
|
|
52
54
|
http.request(request) do |response|
|
|
53
55
|
raise_unless_success(response)
|
|
54
56
|
enforce_max_file_size(response.content_length)
|
|
55
|
-
response
|
|
56
|
-
zip_file.write chunk
|
|
57
|
-
end
|
|
57
|
+
write_body(response)
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
60
|
@downloaded = true
|
|
61
61
|
rescue *NET_HTTP_ERRORS => e
|
|
62
62
|
raise DownloadError, e.message
|
|
63
63
|
end
|
|
64
|
-
|
|
64
|
+
|
|
65
|
+
# Content-Length is optional (e.g. chunked responses), so also count the
|
|
66
|
+
# bytes as they arrive and stop once the limit is exceeded.
|
|
67
|
+
def write_body(response)
|
|
68
|
+
bytes = 0
|
|
69
|
+
response.read_body do |chunk|
|
|
70
|
+
bytes += chunk.bytesize
|
|
71
|
+
enforce_max_file_size(bytes)
|
|
72
|
+
zip_file.write chunk
|
|
73
|
+
end
|
|
74
|
+
end
|
|
65
75
|
|
|
66
76
|
def request
|
|
67
77
|
return @request if defined? @request
|
|
@@ -97,67 +107,65 @@ module Jekyll
|
|
|
97
107
|
# File IO is already open, rewind pointer to start of file to read
|
|
98
108
|
zip_file.rewind
|
|
99
109
|
|
|
110
|
+
# Extract each entry by hand rather than calling Zip::Entry#extract,
|
|
111
|
+
# whose signature changed incompatibly in rubyzip 3.0 (the destination
|
|
112
|
+
# is now resolved relative to a `destination_directory:` keyword,
|
|
113
|
+
# mangling the absolute paths we pass). Reading each entry's stream and
|
|
114
|
+
# writing it ourselves behaves identically across rubyzip 1.x–3.x.
|
|
115
|
+
@extracted_size = 0
|
|
100
116
|
Zip::File.open(zip_file) do |archive|
|
|
101
|
-
archive.each { |
|
|
117
|
+
archive.each { |entry| extract_entry(entry) }
|
|
102
118
|
end
|
|
103
119
|
ensure
|
|
104
120
|
zip_file.close
|
|
105
121
|
zip_file.unlink
|
|
106
122
|
end
|
|
107
123
|
|
|
108
|
-
#
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
:path => [theme.owner, theme.name, "zip", theme.git_ref].join("/")
|
|
114
|
-
).normalize
|
|
115
|
-
end
|
|
124
|
+
# Writes a single zip entry to its destination within theme.root.
|
|
125
|
+
# path_without_name_and_ref sandboxes the destination, guarding against
|
|
126
|
+
# Zip Slip.
|
|
127
|
+
def extract_entry(entry)
|
|
128
|
+
return if entry.name.end_with?("/") # skip directory entries
|
|
116
129
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
# Extracts proxy settings from environment variables
|
|
123
|
-
def proxy_uri
|
|
124
|
-
return @proxy_uri if defined?(@proxy_uri)
|
|
125
|
-
|
|
126
|
-
proxy_env = find_proxy_env_var
|
|
127
|
-
@proxy_uri = parse_proxy_uri(proxy_env)
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def find_proxy_env_var
|
|
131
|
-
# Check for HTTPS proxy first if the URL uses HTTPS
|
|
132
|
-
if theme.scheme == "https"
|
|
133
|
-
ENV["https_proxy"] || ENV["HTTPS_PROXY"] || ENV["http_proxy"] || ENV["HTTP_PROXY"]
|
|
134
|
-
else
|
|
135
|
-
ENV["http_proxy"] || ENV["HTTP_PROXY"]
|
|
130
|
+
enforce_max_extracted_size(@extracted_size + entry.size)
|
|
131
|
+
dest = path_without_name_and_ref(entry.name)
|
|
132
|
+
FileUtils.mkdir_p File.dirname(dest)
|
|
133
|
+
File.open(dest, "wb") do |output|
|
|
134
|
+
entry.get_input_stream { |input| copy_entry(entry, input, output) }
|
|
136
135
|
end
|
|
137
136
|
end
|
|
138
137
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
138
|
+
# Copies an entry in chunks, counting the bytes actually inflated rather
|
|
139
|
+
# than trusting the sizes in the archive. Zip.validate_entry_sizes only
|
|
140
|
+
# applies to Zip::Entry#extract, so the same check is made here: an entry
|
|
141
|
+
# may not inflate to more than its declared size.
|
|
142
|
+
def copy_entry(entry, input, output)
|
|
143
|
+
written = 0
|
|
144
|
+
while (chunk = input.read(EXTRACT_CHUNK_SIZE))
|
|
145
|
+
written += chunk.bytesize
|
|
146
|
+
if written > entry.size
|
|
147
|
+
raise DownloadError, "Zip entry #{entry.name} is larger than its declared size"
|
|
148
|
+
end
|
|
146
149
|
|
|
147
|
-
|
|
148
|
-
|
|
150
|
+
@extracted_size += chunk.bytesize
|
|
151
|
+
enforce_max_extracted_size(@extracted_size)
|
|
152
|
+
output.write(chunk)
|
|
153
|
+
end
|
|
149
154
|
end
|
|
150
155
|
|
|
151
|
-
def
|
|
152
|
-
|
|
153
|
-
end
|
|
156
|
+
def enforce_max_extracted_size(size)
|
|
157
|
+
return unless size > MAX_EXTRACTED_SIZE
|
|
154
158
|
|
|
155
|
-
|
|
156
|
-
proxy_uri&.user
|
|
159
|
+
raise DownloadError, "Maximum extracted theme size of #{MAX_EXTRACTED_SIZE} bytes exceeded"
|
|
157
160
|
end
|
|
158
161
|
|
|
159
|
-
|
|
160
|
-
|
|
162
|
+
# Full URL to codeload zip download endpoint for the given theme
|
|
163
|
+
def zip_url
|
|
164
|
+
@zip_url ||= Addressable::URI.new(
|
|
165
|
+
:scheme => theme.scheme,
|
|
166
|
+
:host => "codeload.#{theme.host}",
|
|
167
|
+
:path => [theme.owner, theme.name, "zip", theme.git_ref].join("/")
|
|
168
|
+
).normalize
|
|
161
169
|
end
|
|
162
170
|
|
|
163
171
|
def theme_dir_exists?
|
|
@@ -165,7 +173,7 @@ module Jekyll
|
|
|
165
173
|
end
|
|
166
174
|
|
|
167
175
|
def theme_dir_empty?
|
|
168
|
-
Dir
|
|
176
|
+
Dir.empty?(theme.root)
|
|
169
177
|
end
|
|
170
178
|
|
|
171
179
|
# Codeload generated zip files contain a top level folder in the form of
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
module RemoteTheme
|
|
5
|
+
# Net::HTTP setup shared by the theme download and the GitHub API lookup
|
|
6
|
+
# for @latest, so both use the same proxy settings and timeouts.
|
|
7
|
+
module HTTP
|
|
8
|
+
OPEN_TIMEOUT = 10 # seconds
|
|
9
|
+
READ_TIMEOUT = 60 # seconds
|
|
10
|
+
# Proxy environment variables to try, by request scheme
|
|
11
|
+
PROXY_SCHEMES = {
|
|
12
|
+
"https" => %w(https http).freeze,
|
|
13
|
+
"http" => %w(http).freeze,
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
# Opens a connection to the host of `uri` (an Addressable::URI) and
|
|
19
|
+
# yields the Net::HTTP session.
|
|
20
|
+
def start(uri, use_ssl: uri.scheme == "https", env: ENV, &block)
|
|
21
|
+
proxy = proxy_uri(uri, env)
|
|
22
|
+
Net::HTTP.start(
|
|
23
|
+
uri.host, uri.port,
|
|
24
|
+
proxy&.host, proxy&.port, proxy&.user, proxy&.password,
|
|
25
|
+
:use_ssl => use_ssl,
|
|
26
|
+
:open_timeout => OPEN_TIMEOUT,
|
|
27
|
+
:read_timeout => READ_TIMEOUT,
|
|
28
|
+
&block
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# The proxy to use for `uri`, from the environment, or nil. HTTPS
|
|
33
|
+
# requests use https_proxy, falling back to http_proxy. Hosts listed in
|
|
34
|
+
# no_proxy/NO_PROXY, and loopback addresses, bypass the proxy.
|
|
35
|
+
def proxy_uri(uri, env = ENV)
|
|
36
|
+
PROXY_SCHEMES.fetch(uri.scheme, PROXY_SCHEMES["http"]).each do |scheme|
|
|
37
|
+
target = URI::Generic.build(:scheme => scheme, :host => uri.host,
|
|
38
|
+
:port => uri.inferred_port)
|
|
39
|
+
proxy = target.find_proxy(env)
|
|
40
|
+
return proxy if proxy
|
|
41
|
+
end
|
|
42
|
+
nil
|
|
43
|
+
rescue URI::Error
|
|
44
|
+
nil
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -20,11 +20,13 @@ module Jekyll
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
Jekyll.logger.info LOG_KEY, "Using theme #{theme.name_with_owner}"
|
|
23
|
+
# after_reset fires on every rebuild (e.g. `jekyll serve`). Only the
|
|
24
|
+
# first run downloads the theme, so only it registers the cleanup.
|
|
23
25
|
unless munged?
|
|
24
26
|
downloader.run
|
|
25
27
|
configure_theme
|
|
28
|
+
enqueue_theme_cleanup
|
|
26
29
|
end
|
|
27
|
-
enqueue_theme_cleanup
|
|
28
30
|
|
|
29
31
|
theme
|
|
30
32
|
end
|
|
@@ -35,8 +37,10 @@ module Jekyll
|
|
|
35
37
|
site.theme&.is_a?(Jekyll::RemoteTheme::Theme)
|
|
36
38
|
end
|
|
37
39
|
|
|
40
|
+
# On a rebuild, reuse the theme configured by the first run rather than
|
|
41
|
+
# building a new one, which would create another temp directory.
|
|
38
42
|
def theme
|
|
39
|
-
@theme ||= Theme.new(raw_theme)
|
|
43
|
+
@theme ||= munged? ? site.theme : Theme.new(raw_theme)
|
|
40
44
|
end
|
|
41
45
|
|
|
42
46
|
def raw_theme
|
|
@@ -63,13 +67,22 @@ module Jekyll
|
|
|
63
67
|
end
|
|
64
68
|
|
|
65
69
|
# Initialize GitHub metadata munger if it was loaded after :after_init hook
|
|
70
|
+
# or if the existing munger needs to be updated for the current site
|
|
66
71
|
def initialize_github_metadata
|
|
67
72
|
return unless defined?(Jekyll::GitHubMetadata::SiteGitHubMunger)
|
|
68
|
-
return if Jekyll::GitHubMetadata::SiteGitHubMunger.global_munger
|
|
69
73
|
|
|
70
|
-
munger
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
# Create a new munger if one doesn't exist or if it's for a different site
|
|
75
|
+
current_munger = Jekyll::GitHubMetadata::SiteGitHubMunger.global_munger
|
|
76
|
+
needs_init = current_munger.nil? ||
|
|
77
|
+
(Jekyll::GitHubMetadata.respond_to?(:site) &&
|
|
78
|
+
Jekyll::GitHubMetadata.site != site)
|
|
79
|
+
|
|
80
|
+
if needs_init
|
|
81
|
+
Jekyll.logger.debug LOG_KEY, "Initializing GitHub metadata munger"
|
|
82
|
+
munger = Jekyll::GitHubMetadata::SiteGitHubMunger.new(site)
|
|
83
|
+
munger.munge!
|
|
84
|
+
Jekyll::GitHubMetadata::SiteGitHubMunger.global_munger = munger
|
|
85
|
+
end
|
|
73
86
|
end
|
|
74
87
|
|
|
75
88
|
def enqueue_theme_cleanup
|
|
@@ -171,11 +171,7 @@ module Jekyll
|
|
|
171
171
|
end
|
|
172
172
|
|
|
173
173
|
def make_api_request(api_url)
|
|
174
|
-
|
|
175
|
-
api_url.host,
|
|
176
|
-
api_url.port,
|
|
177
|
-
:use_ssl => api_url.scheme == "https"
|
|
178
|
-
) do |http|
|
|
174
|
+
HTTP.start(api_url) do |http|
|
|
179
175
|
request = Net::HTTP::Get.new(api_url.request_uri)
|
|
180
176
|
request["Accept"] = "application/vnd.github.v3+json"
|
|
181
177
|
request["User-Agent"] = Downloader::USER_AGENT
|
data/lib/jekyll-remote-theme.rb
CHANGED
|
@@ -16,6 +16,7 @@ module Jekyll
|
|
|
16
16
|
class DownloadError < StandardError; end
|
|
17
17
|
|
|
18
18
|
autoload :Downloader, "jekyll-remote-theme/downloader"
|
|
19
|
+
autoload :HTTP, "jekyll-remote-theme/http"
|
|
19
20
|
autoload :MockGemspec, "jekyll-remote-theme/mock_gemspec"
|
|
20
21
|
autoload :Munger, "jekyll-remote-theme/munger"
|
|
21
22
|
autoload :Theme, "jekyll-remote-theme/theme"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jekyll-remote-theme
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ben Balter
|
|
@@ -92,7 +92,7 @@ dependencies:
|
|
|
92
92
|
version: 1.3.0
|
|
93
93
|
- - "<"
|
|
94
94
|
- !ruby/object:Gem::Version
|
|
95
|
-
version: '
|
|
95
|
+
version: '4.0'
|
|
96
96
|
type: :runtime
|
|
97
97
|
prerelease: false
|
|
98
98
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -102,7 +102,7 @@ dependencies:
|
|
|
102
102
|
version: 1.3.0
|
|
103
103
|
- - "<"
|
|
104
104
|
- !ruby/object:Gem::Version
|
|
105
|
-
version: '
|
|
105
|
+
version: '4.0'
|
|
106
106
|
- !ruby/object:Gem::Dependency
|
|
107
107
|
name: jekyll_test_plugin_malicious
|
|
108
108
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -279,6 +279,7 @@ extra_rdoc_files: []
|
|
|
279
279
|
files:
|
|
280
280
|
- lib/jekyll-remote-theme.rb
|
|
281
281
|
- lib/jekyll-remote-theme/downloader.rb
|
|
282
|
+
- lib/jekyll-remote-theme/http.rb
|
|
282
283
|
- lib/jekyll-remote-theme/mock_gemspec.rb
|
|
283
284
|
- lib/jekyll-remote-theme/munger.rb
|
|
284
285
|
- lib/jekyll-remote-theme/theme.rb
|
|
@@ -294,14 +295,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
294
295
|
requirements:
|
|
295
296
|
- - ">="
|
|
296
297
|
- !ruby/object:Gem::Version
|
|
297
|
-
version:
|
|
298
|
+
version: '3.0'
|
|
298
299
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
299
300
|
requirements:
|
|
300
301
|
- - ">="
|
|
301
302
|
- !ruby/object:Gem::Version
|
|
302
303
|
version: '0'
|
|
303
304
|
requirements: []
|
|
304
|
-
rubygems_version:
|
|
305
|
+
rubygems_version: 3.6.9
|
|
305
306
|
specification_version: 4
|
|
306
307
|
summary: Jekyll plugin for building Jekyll sites with any GitHub-hosted theme
|
|
307
308
|
test_files: []
|