camo-rb 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|