-
-
Notifications
You must be signed in to change notification settings - Fork 82
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use Net:HTTP instead of Typhoeus #26
Changes from all commits
6cf3878
f2b4199
6238850
1981d13
435989a
9b4919d
1a376a6
b43c9af
9c4bd22
ae92895
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,12 +5,12 @@ module RemoteTheme | |
class Downloader | ||
HOST = "https://codeload.github.com".freeze | ||
PROJECT_URL = "https://github.com/benbalter/jekyll-remote-theme".freeze | ||
TYPHOEUS_OPTIONS = { | ||
:headers => { | ||
:user_agent => "Jekyll Remote Theme/#{VERSION} (+#{PROJECT_URL})", | ||
}, | ||
:verbose => (Jekyll.logger.level == :debug), | ||
}.freeze | ||
USER_AGENT = "Jekyll Remote Theme/#{VERSION} (+#{PROJECT_URL})".freeze | ||
MAX_FILE_SIZE = 1 * (1024 * 1024 * 1024) # Size in bytes (1 GB) | ||
NET_HTTP_ERRORS = [ | ||
Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, | ||
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, | ||
].freeze | ||
|
||
attr_reader :theme | ||
private :theme | ||
|
@@ -27,8 +27,6 @@ def run | |
|
||
download | ||
unzip | ||
|
||
@downloaded = true | ||
end | ||
|
||
def downloaded? | ||
|
@@ -38,16 +36,40 @@ def downloaded? | |
private | ||
|
||
def zip_file | ||
@zip_file ||= Tempfile.new([TEMP_PREFIX, ".zip"]) | ||
@zip_file ||= Tempfile.new([TEMP_PREFIX, ".zip"], :binmode => true) | ||
end | ||
|
||
def download | ||
Jekyll.logger.debug LOG_KEY, "Downloading #{zip_url} to #{zip_file.path}" | ||
request = Typhoeus::Request.new zip_url, TYPHOEUS_OPTIONS | ||
request.on_headers { |response| raise_if_unsuccessful(response) } | ||
request.on_body { |chunk| zip_file.write(chunk) } | ||
request.on_complete { |response| raise_if_unsuccessful(response) } | ||
request.run | ||
Net::HTTP.start(zip_url.host, zip_url.port, :use_ssl => true) do |http| | ||
http.request(request) do |response| | ||
raise_unless_sucess(response) | ||
enforce_max_file_size(response.content_length) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I looked into this and there is not a clean way to do this with our clean implementation. We don't have any limit on master, so what's currently implemented should be some protection, and we can look into adding better chunk support, if necessary, in a subsequent pass. |
||
response.read_body do |chunk| | ||
zip_file.write chunk | ||
end | ||
end | ||
end | ||
@downloaded = true | ||
rescue *NET_HTTP_ERRORS => e | ||
raise DownloadError, e.message | ||
end | ||
|
||
def request | ||
return @request if defined? @request | ||
@request = Net::HTTP::Get.new zip_url.request_uri | ||
@request["User-Agent"] = USER_AGENT | ||
@request | ||
end | ||
|
||
def raise_unless_sucess(response) | ||
return if response.is_a?(Net::HTTPSuccess) | ||
raise DownloadError, "#{response.code} - #{response.message}" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be helpful to include the URL that failed to return a helpful response? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would theoretically be in the log output immediately above. |
||
end | ||
|
||
def enforce_max_file_size(size) | ||
return unless size && size > MAX_FILE_SIZE | ||
raise DownloadError, "Maximum file size of #{MAX_FILE_SIZE} bytes exceeded" | ||
end | ||
|
||
def unzip | ||
|
@@ -59,13 +81,14 @@ def unzip | |
Zip::File.open(zip_file) do |archive| | ||
archive.each { |file| file.extract path_without_name_and_ref(file.name) } | ||
end | ||
|
||
ensure | ||
zip_file.close | ||
zip_file.unlink | ||
end | ||
|
||
# Full URL to codeload zip download endpoint for the given theme | ||
def zip_url | ||
Addressable::URI.join( | ||
@zip_url ||= Addressable::URI.join( | ||
HOST, "#{theme.owner}/", "#{theme.name}/", "zip/", theme.git_ref | ||
).normalize | ||
end | ||
|
@@ -78,17 +101,6 @@ def theme_dir_empty? | |
Dir["#{theme.root}/*"].empty? | ||
end | ||
|
||
def raise_if_unsuccessful(response) | ||
if response.timed_out? | ||
raise DownloadError, "Request timed out" | ||
elsif response.code.zero? | ||
raise DownloadError, response.return_message | ||
elsif response.code != 200 | ||
msg = "Request failed with #{response.code} - #{response.status_message}" | ||
raise DownloadError, msg | ||
end | ||
end | ||
|
||
# Codeload generated zip files contain a top level folder in the form of | ||
# THEME_NAME-GIT_REF/. While requests for Git repos are case insensitive, | ||
# the zip subfolder will respect the case in the repository's name, thus | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have any thoughts on whether this should be configurable? Maybe my host’s disks aren’t quite so large?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assume you're talking about the max file size? I put it in largely as a guard against abuse. Not sure how a malicious user might create a never-ending zip since we control the source server, but figured it'd be better to be safe than sorry.