camo-rb 0.0.1
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 +7 -0
- data/bin/camorb +20 -0
- data/bin/generate_url +20 -0
- data/bin/rspec +29 -0
- data/lib/camo.rb +10 -0
- data/lib/camo/client.rb +97 -0
- data/lib/camo/errors.rb +49 -0
- data/lib/camo/headers_utils.rb +39 -0
- data/lib/camo/logger.rb +73 -0
- data/lib/camo/mime_type_utils.rb +49 -0
- data/lib/camo/request.rb +78 -0
- data/lib/camo/server.rb +79 -0
- data/lib/camo/version.rb +5 -0
- metadata +57 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b410ac3bfb745c4cb2ef0aaa82f435c465258ec7aa888ec1dce17e767d132307
|
4
|
+
data.tar.gz: 06110020de592ca9708ab8fc6c341cc1c569cae0c3bc26263e1670e16b18c578
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1edb89a1a15b0fd13071bbe511253e71f975858f37078ec699f2c1319d32c05303ae860018bc5a9bc3d50fcb99b29e854842013353fdf56aa15bc608150810d8
|
7
|
+
data.tar.gz: d6dd14481dc67966dbf4bc1c91057272d0f9beaeefa7f718fb46251fd1d2b48851f43a838718945dfc06a403090ea31ac21bbc6e34b1c6b9faeaf0abef222b42
|
data/bin/camorb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "rack/handler/falcon"
|
5
|
+
require_relative "../environment"
|
6
|
+
|
7
|
+
host = "0.0.0.0"
|
8
|
+
port = ENV["CAMORB_PORT"] || 9292
|
9
|
+
|
10
|
+
trap("SIGINT") do
|
11
|
+
puts "\nExiting gracefully..."
|
12
|
+
exit
|
13
|
+
end
|
14
|
+
|
15
|
+
begin
|
16
|
+
app = Camo::Server.new(ENV["CAMORB_KEY"])
|
17
|
+
Rack::Handler::Falcon.run(app, host: host, port: port)
|
18
|
+
rescue Camo::Errors::AppError => e
|
19
|
+
abort("Error: #{e.message}")
|
20
|
+
end
|
data/bin/generate_url
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "openssl"
|
5
|
+
|
6
|
+
def encode_url(url)
|
7
|
+
url.bytes.map { |byte| "%02x" % byte }.join
|
8
|
+
end
|
9
|
+
|
10
|
+
def digest(url, key)
|
11
|
+
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha1"), key, url)
|
12
|
+
end
|
13
|
+
|
14
|
+
url = String(ARGV[0])
|
15
|
+
abort("Error: Wrong format\nExample:\n\nCAMORB_KEY=somekey ./generate_url http://google.com/logo.png") if url.empty?
|
16
|
+
key = String(ENV["CAMORB_KEY"])
|
17
|
+
abort("Key is required. Use the environment variable `CAMORB_KEY` to define it.") if key.empty?
|
18
|
+
result = "/#{digest(url, key)}/#{encode_url(url)}"
|
19
|
+
result = "#{ENV["CAMORB_HOST"]}#{result}" if ENV["CAMORB_HOST"]
|
20
|
+
puts result
|
data/bin/rspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rspec' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rspec-core", "rspec")
|
data/lib/camo.rb
ADDED
data/lib/camo/client.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
require "faraday"
|
2
|
+
|
3
|
+
module Camo
|
4
|
+
class Client
|
5
|
+
include Rack::Utils
|
6
|
+
include HeadersUtils
|
7
|
+
include MimeTypeUtils
|
8
|
+
|
9
|
+
ALLOWED_TRANSFERRED_HEADERS = HeaderHash[%w[Host Accept Accept-Encoding]]
|
10
|
+
KEEP_ALIVE = ["1", "true", true].include?(ENV.fetch("CAMORB_KEEP_ALIVE", false))
|
11
|
+
MAX_REDIRECTS = ENV.fetch("CAMORB_MAX_REDIRECTS", 4)
|
12
|
+
SOCKET_TIMEOUT = ENV.fetch("CAMORB_SOCKET_TIMEOUT", 10)
|
13
|
+
CONTENT_LENGTH_LIMIT = ENV.fetch("CAMORB_LENGTH_LIMIT", 5242880).to_i
|
14
|
+
|
15
|
+
attr_reader :logger
|
16
|
+
|
17
|
+
def initialize(logger = Logger.stdio)
|
18
|
+
@logger = logger
|
19
|
+
end
|
20
|
+
|
21
|
+
def get(url, transferred_headers = {}, remaining_redirects = MAX_REDIRECTS)
|
22
|
+
logger.debug "Handling request to #{url}", {transferred_headers: transferred_headers, remaining_redirects: remaining_redirects}
|
23
|
+
|
24
|
+
url = URI.parse(url)
|
25
|
+
headers = build_request_headers(transferred_headers, url: url)
|
26
|
+
response = get_request(url, headers, timeout: SOCKET_TIMEOUT)
|
27
|
+
|
28
|
+
logger.debug "Request result", {status: response.status, headers: response.headers, body_bytesize: response.body.bytesize}
|
29
|
+
|
30
|
+
case response.status
|
31
|
+
when redirect?
|
32
|
+
redirect(response, headers, remaining_redirects)
|
33
|
+
when not_modified?
|
34
|
+
[response.status, response.headers]
|
35
|
+
else
|
36
|
+
validate_response!(response)
|
37
|
+
[response.status, response.headers, response.body]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def validate_response!(response)
|
44
|
+
raise Errors::ContentLengthExceededError if response.headers["content-length"].to_i > CONTENT_LENGTH_LIMIT
|
45
|
+
content_type = String(response.headers["content-type"])
|
46
|
+
raise Errors::EmptyContentTypeError if content_type.empty?
|
47
|
+
raise Errors::UnsupportedContentTypeError, content_type unless SUPPORTED_CONTENT_TYPES.include?(content_type)
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_request(url, headers, options = {})
|
51
|
+
Faraday.get(url, {}, headers) do |req|
|
52
|
+
options.each do |key, value|
|
53
|
+
req.options.public_send("#{key}=", value)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
rescue Faraday::TimeoutError
|
57
|
+
raise Errors::TimeoutError
|
58
|
+
end
|
59
|
+
|
60
|
+
def redirect(response, headers, remaining_redirects)
|
61
|
+
raise Errors::TooManyRedirectsError if remaining_redirects < 0
|
62
|
+
new_url = String(response.headers["location"])
|
63
|
+
logger.debug "Redirect to #{new_url}", {remaining_redirects: remaining_redirects}
|
64
|
+
raise Errors::RedirectWithoutLocationError if new_url.empty?
|
65
|
+
|
66
|
+
get(new_url, headers, remaining_redirects - 1)
|
67
|
+
end
|
68
|
+
|
69
|
+
def not_modified?
|
70
|
+
->(code) { code === 304 }
|
71
|
+
end
|
72
|
+
|
73
|
+
def redirect?
|
74
|
+
->(code) { [301, 302, 303, 307, 308].include? code }
|
75
|
+
end
|
76
|
+
|
77
|
+
def build_request_headers(headers, url:)
|
78
|
+
headers = headers.each_with_object({}) do |header, headers|
|
79
|
+
key = header[0].tr("_", "-")
|
80
|
+
headers[key] = header[1]
|
81
|
+
end
|
82
|
+
|
83
|
+
headers = headers
|
84
|
+
.select { |k, _| ALLOWED_TRANSFERRED_HEADERS.include?(k) }
|
85
|
+
.merge(default_request_headers)
|
86
|
+
|
87
|
+
if String(headers["Host"]).empty?
|
88
|
+
headers["Host"] = String(url.host)
|
89
|
+
headers["Host"] += ":#{url.port}" unless [80, 443].include?(url.port)
|
90
|
+
end
|
91
|
+
|
92
|
+
headers["Connection"] = KEEP_ALIVE ? "keep-alive" : "close"
|
93
|
+
|
94
|
+
HeaderHash[headers]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/lib/camo/errors.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module Camo
|
2
|
+
module Errors
|
3
|
+
class AppError < ::StandardError; end
|
4
|
+
|
5
|
+
class UndefinedKeyError < AppError
|
6
|
+
def initialize(message = "Key is required. Use the environment variable `CAMORB_KEY` to define it.")
|
7
|
+
super
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class ClientError < AppError; end
|
12
|
+
|
13
|
+
class RedirectWithoutLocationError < ClientError
|
14
|
+
def initialize(message = "Redirect with no location")
|
15
|
+
super
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class TooManyRedirectsError < ClientError
|
20
|
+
def initialize(message = "Too many redirects")
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class TimeoutError < ClientError
|
26
|
+
def initialize(message = "Request timeout")
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class ContentLengthExceededError < ClientError
|
32
|
+
def initialize(message = "Max Content-Length is exceeded")
|
33
|
+
super
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class UnsupportedContentTypeError < ClientError
|
38
|
+
def initialize(content_type, message = "Unsupported Content-Type: '#{content_type}'")
|
39
|
+
super(message)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class EmptyContentTypeError < ClientError
|
44
|
+
def initialize(message = "Empty Content-Type")
|
45
|
+
super
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Camo
|
2
|
+
module HeadersUtils
|
3
|
+
HOSTNAME = ENV.fetch("CAMORB_HOSTNAME", "unknown")
|
4
|
+
TIMING_ALLOW_ORIGIN = ENV.fetch("CAMORB_TIMING_ALLOW_ORIGIN", nil)
|
5
|
+
|
6
|
+
REQUEST_SECURITY_HEADERS = {
|
7
|
+
"X-Frame-Options" => "deny",
|
8
|
+
"X-XSS-Protection" => "1; mode=block",
|
9
|
+
"X-Content-Type-Options" => "nosniff",
|
10
|
+
"Content-Security-Policy" => "default-src 'none'; img-src data:; style-src 'unsafe-inline'"
|
11
|
+
}
|
12
|
+
|
13
|
+
RESPONSE_SECURITY_HEADERS = REQUEST_SECURITY_HEADERS.merge({
|
14
|
+
"Strict-Transport-Security" => "max-age=31536000; includeSubDomains"
|
15
|
+
})
|
16
|
+
|
17
|
+
def self.user_agent
|
18
|
+
ENV.fetch("CAMORB_HEADER_VIA", "CamoRB Asset Proxy #{Camo::Version::GEM}")
|
19
|
+
end
|
20
|
+
|
21
|
+
def default_response_headers
|
22
|
+
RESPONSE_SECURITY_HEADERS.merge({
|
23
|
+
"Camo-Host" => HOSTNAME,
|
24
|
+
"Timing-Allow-Origin" => TIMING_ALLOW_ORIGIN
|
25
|
+
}).compact
|
26
|
+
end
|
27
|
+
|
28
|
+
def default_request_headers
|
29
|
+
REQUEST_SECURITY_HEADERS.merge({
|
30
|
+
"Via" => user_agent,
|
31
|
+
"User-Agent" => user_agent
|
32
|
+
})
|
33
|
+
end
|
34
|
+
|
35
|
+
def user_agent
|
36
|
+
HeadersUtils.user_agent
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/camo/logger.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
module Camo
|
2
|
+
class Logger
|
3
|
+
attr_reader :outpipe, :errpipe
|
4
|
+
|
5
|
+
LOG_LEVELS = ["debug", "info", "error", "fatal"].freeze
|
6
|
+
LOG_LEVEL = (LOG_LEVELS.find { |level| level == ENV["CAMORB_LOG_LEVEL"] } || "info").freeze
|
7
|
+
|
8
|
+
def initialize(outpipe, errpipe)
|
9
|
+
@outpipe = outpipe
|
10
|
+
@errpipe = errpipe
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.stdio
|
14
|
+
new $stdout, $stderr
|
15
|
+
end
|
16
|
+
|
17
|
+
def debug(msg, params = {})
|
18
|
+
outpipe.puts(compile_output("debug", msg, params)) if debug?
|
19
|
+
end
|
20
|
+
|
21
|
+
def info(msg, params = {})
|
22
|
+
outpipe.puts(compile_output("info", msg, params)) if info?
|
23
|
+
end
|
24
|
+
|
25
|
+
def error(msg, params = {})
|
26
|
+
errpipe.puts(compile_output("error", msg, params)) if error?
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def debug?
|
32
|
+
LOG_LEVELS.find_index(LOG_LEVEL) <= LOG_LEVELS.find_index("debug")
|
33
|
+
end
|
34
|
+
|
35
|
+
def info?
|
36
|
+
LOG_LEVELS.find_index(LOG_LEVEL) <= LOG_LEVELS.find_index("info")
|
37
|
+
end
|
38
|
+
|
39
|
+
def error?
|
40
|
+
LOG_LEVELS.find_index(LOG_LEVEL) <= LOG_LEVELS.find_index("error")
|
41
|
+
end
|
42
|
+
|
43
|
+
def compile_output(level, msg, params)
|
44
|
+
output = []
|
45
|
+
output << "[#{level.upcase}]"
|
46
|
+
output << (msg.is_a?(Array) ? msg.join(", ") : msg)
|
47
|
+
|
48
|
+
if params.any?
|
49
|
+
output << "|"
|
50
|
+
output << convert_params_to_string(params)
|
51
|
+
end
|
52
|
+
|
53
|
+
output.join(" ")
|
54
|
+
end
|
55
|
+
|
56
|
+
def convert_params_to_string(params)
|
57
|
+
elements = []
|
58
|
+
|
59
|
+
params.each do |key, value|
|
60
|
+
compiled_value =
|
61
|
+
if value.is_a?(Hash)
|
62
|
+
convert_params_to_string(value)
|
63
|
+
else
|
64
|
+
"\"#{value}\""
|
65
|
+
end
|
66
|
+
|
67
|
+
elements << "#{key}: #{compiled_value}"
|
68
|
+
end
|
69
|
+
|
70
|
+
"{ #{elements.join(", ")} }"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Camo
|
2
|
+
module MimeTypeUtils
|
3
|
+
SUPPORTED_CONTENT_TYPES = %w[
|
4
|
+
image/bmp
|
5
|
+
image/cgm
|
6
|
+
image/g3fax
|
7
|
+
image/gif
|
8
|
+
image/ief
|
9
|
+
image/jp2
|
10
|
+
image/jpeg
|
11
|
+
image/jpg
|
12
|
+
image/pict
|
13
|
+
image/png
|
14
|
+
image/prs.btif
|
15
|
+
image/svg+xml
|
16
|
+
image/tiff
|
17
|
+
image/vnd.adobe.photoshop
|
18
|
+
image/vnd.djvu
|
19
|
+
image/vnd.dwg
|
20
|
+
image/vnd.dxf
|
21
|
+
image/vnd.fastbidsheet
|
22
|
+
image/vnd.fpx
|
23
|
+
image/vnd.fst
|
24
|
+
image/vnd.fujixerox.edmics-mmr
|
25
|
+
image/vnd.fujixerox.edmics-rlc
|
26
|
+
image/vnd.microsoft.icon
|
27
|
+
image/vnd.ms-modi
|
28
|
+
image/vnd.net-fpx
|
29
|
+
image/vnd.wap.wbmp
|
30
|
+
image/vnd.xiff
|
31
|
+
image/webp
|
32
|
+
image/x-cmu-raster
|
33
|
+
image/x-cmx
|
34
|
+
image/x-icon
|
35
|
+
image/x-macpaint
|
36
|
+
image/x-pcx
|
37
|
+
image/x-pict
|
38
|
+
image/x-portable-anymap
|
39
|
+
image/x-portable-bitmap
|
40
|
+
image/x-portable-graymap
|
41
|
+
image/x-portable-pixmap
|
42
|
+
image/x-quicktime
|
43
|
+
image/x-rgb
|
44
|
+
image/x-xbitmap
|
45
|
+
image/x-xpixmap
|
46
|
+
image/x-xwindowdump
|
47
|
+
].freeze
|
48
|
+
end
|
49
|
+
end
|
data/lib/camo/request.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require "rack/utils"
|
2
|
+
require "addressable/uri"
|
3
|
+
|
4
|
+
module Camo
|
5
|
+
class Request
|
6
|
+
include Rack::Utils
|
7
|
+
include HeadersUtils
|
8
|
+
|
9
|
+
SUPPORTED_PROTOCOLS = %w[http https]
|
10
|
+
|
11
|
+
attr_reader :key, :method, :protocol, :host, :path, :headers, :query_string, :params, :destination_url, :digest, :digest_type, :errors
|
12
|
+
|
13
|
+
def initialize(env, key)
|
14
|
+
@method = env["REQUEST_METHOD"]
|
15
|
+
@query_string = env["QUERY_STRING"]
|
16
|
+
@params = parse_query(@query_string)
|
17
|
+
@protocol = env["rack.url_scheme"] || "http"
|
18
|
+
@host = env["HTTP_HOST"]
|
19
|
+
@path = env["PATH_INFO"]
|
20
|
+
@headers = build_headers(env)
|
21
|
+
@key = key
|
22
|
+
|
23
|
+
@digest, encoded_url = path[1..].split("/", 2).map { |part| String(part) }
|
24
|
+
|
25
|
+
if encoded_url
|
26
|
+
@digest_type = "path"
|
27
|
+
@destination_url = Addressable::URI.parse(String(decode_hex(encoded_url)))
|
28
|
+
else
|
29
|
+
@digest_type = "query"
|
30
|
+
@destination_url = Addressable::URI.parse(String(params["url"]))
|
31
|
+
end
|
32
|
+
|
33
|
+
@errors = []
|
34
|
+
end
|
35
|
+
|
36
|
+
def url
|
37
|
+
"#{protocol}://#{host}#{path}#{query_string.empty? ? nil : "?#{query_string}"}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def valid_request?
|
41
|
+
validate_request
|
42
|
+
Array(errors).empty?
|
43
|
+
end
|
44
|
+
|
45
|
+
def valid_digest?
|
46
|
+
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha1"), key, destination_url) == digest
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def build_headers(env)
|
52
|
+
hash = env.select { |k, _| k.start_with?("HTTP_") }
|
53
|
+
hash = hash.each_with_object({}) do |header, headers|
|
54
|
+
headers[header[0].sub("HTTP_", "")] = header[1]
|
55
|
+
end
|
56
|
+
|
57
|
+
HeaderHash[hash]
|
58
|
+
end
|
59
|
+
|
60
|
+
def validate_request
|
61
|
+
@errors ||= []
|
62
|
+
|
63
|
+
errors << "Empty URL" if destination_url.empty?
|
64
|
+
errors << "Empty host" if !destination_url.empty? && String(destination_url.host).empty?
|
65
|
+
|
66
|
+
if destination_url.scheme && !SUPPORTED_PROTOCOLS.include?(destination_url.scheme)
|
67
|
+
errors << "Unsupported protocol: '#{destination_url.scheme}'"
|
68
|
+
end
|
69
|
+
|
70
|
+
errors << "Recursive request" if headers["VIA"] == user_agent
|
71
|
+
errors
|
72
|
+
end
|
73
|
+
|
74
|
+
def decode_hex(str)
|
75
|
+
[str].pack("H*")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/camo/server.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require "rack/utils"
|
2
|
+
require "openssl"
|
3
|
+
|
4
|
+
module Camo
|
5
|
+
class Server
|
6
|
+
include Rack::Utils
|
7
|
+
include HeadersUtils
|
8
|
+
|
9
|
+
ALLOWED_REMOTE_HEADERS = HeaderHash[%w[
|
10
|
+
Content-Type
|
11
|
+
Cache-Control
|
12
|
+
eTag
|
13
|
+
Expires
|
14
|
+
Last-Modified
|
15
|
+
Content-Length
|
16
|
+
Content-Encoding
|
17
|
+
].map(&:downcase)]
|
18
|
+
|
19
|
+
attr_reader :request, :key
|
20
|
+
|
21
|
+
def initialize(key)
|
22
|
+
@key = String(key)
|
23
|
+
raise Errors::UndefinedKeyError if @key.empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
def call(env)
|
27
|
+
build_request(env)
|
28
|
+
|
29
|
+
return [404, default_response_headers, []] unless request.method == "GET"
|
30
|
+
|
31
|
+
unless request.valid_digest?
|
32
|
+
logger.error("Invalid digest")
|
33
|
+
return [401, default_response_headers, ["Invalid digest"]]
|
34
|
+
end
|
35
|
+
|
36
|
+
unless request.valid_request?
|
37
|
+
logger.error(request.errors)
|
38
|
+
return [422, default_response_headers, request.errors.join(", ")]
|
39
|
+
end
|
40
|
+
|
41
|
+
logger.debug "Request", {
|
42
|
+
type: request.digest_type,
|
43
|
+
url: request.url,
|
44
|
+
headers: request.headers,
|
45
|
+
destination: request.destination_url,
|
46
|
+
digest: request.digest
|
47
|
+
}
|
48
|
+
|
49
|
+
status, headers, body = client.get(request.destination_url, request.headers)
|
50
|
+
headers = build_response_headers(headers)
|
51
|
+
logger.debug "Response", {status: status, headers: headers, body_bytesize: body.bytesize}
|
52
|
+
|
53
|
+
[status, headers, [body]]
|
54
|
+
rescue Errors::ClientError => e
|
55
|
+
logger.error(e.message)
|
56
|
+
[422, default_response_headers, e.message]
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def logger
|
62
|
+
@logger ||= Logger.stdio
|
63
|
+
end
|
64
|
+
|
65
|
+
def build_request(env)
|
66
|
+
@request ||= Request.new(env, key)
|
67
|
+
end
|
68
|
+
|
69
|
+
def client
|
70
|
+
@client ||= Client.new
|
71
|
+
end
|
72
|
+
|
73
|
+
def build_response_headers(headers)
|
74
|
+
headers
|
75
|
+
.select { |k, _| ALLOWED_REMOTE_HEADERS.include?(k.downcase) }
|
76
|
+
.merge(default_response_headers)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/camo/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: camo-rb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Vyacheslav Alexeev
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-06-05 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: A camo server is a special type of image proxy that proxies non-secure
|
14
|
+
images over SSL/TLS, in order to prevent mixed content warnings on secure pages.
|
15
|
+
The server works in conjunction with back-end code that rewrites image URLs and
|
16
|
+
signs them with an HMAC.
|
17
|
+
email: alexeev.corp@gmail.com
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- bin/camorb
|
23
|
+
- bin/generate_url
|
24
|
+
- bin/rspec
|
25
|
+
- lib/camo.rb
|
26
|
+
- lib/camo/client.rb
|
27
|
+
- lib/camo/errors.rb
|
28
|
+
- lib/camo/headers_utils.rb
|
29
|
+
- lib/camo/logger.rb
|
30
|
+
- lib/camo/mime_type_utils.rb
|
31
|
+
- lib/camo/request.rb
|
32
|
+
- lib/camo/server.rb
|
33
|
+
- lib/camo/version.rb
|
34
|
+
homepage: https://github.com/alexeevit/camo-rb
|
35
|
+
licenses:
|
36
|
+
- MIT
|
37
|
+
metadata: {}
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options: []
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
requirements: []
|
53
|
+
rubygems_version: 3.2.15
|
54
|
+
signing_key:
|
55
|
+
specification_version: 4
|
56
|
+
summary: An SSL/TLS image proxy that uses HMAC signed URLs.
|
57
|
+
test_files: []
|