cannon 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.bundle/config +2 -0
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/Gemfile +5 -0
- data/README +3 -0
- data/Rakefile +20 -0
- data/bin/bundler +16 -0
- data/bin/coderay +16 -0
- data/bin/htmldiff +16 -0
- data/bin/ldiff +16 -0
- data/bin/mustache +16 -0
- data/bin/pry +16 -0
- data/bin/rspec +16 -0
- data/cannon.gemspec +29 -0
- data/lib/cannon.rb +16 -0
- data/lib/cannon/app.rb +196 -0
- data/lib/cannon/concerns/path_cache.rb +48 -0
- data/lib/cannon/concerns/signature.rb +14 -0
- data/lib/cannon/config.rb +58 -0
- data/lib/cannon/cookie_jar.rb +99 -0
- data/lib/cannon/handler.rb +25 -0
- data/lib/cannon/middleware.rb +47 -0
- data/lib/cannon/middleware/content_type.rb +15 -0
- data/lib/cannon/middleware/cookies.rb +18 -0
- data/lib/cannon/middleware/files.rb +33 -0
- data/lib/cannon/middleware/flush_and_benchmark.rb +20 -0
- data/lib/cannon/middleware/request_logger.rb +13 -0
- data/lib/cannon/middleware/router.rb +19 -0
- data/lib/cannon/request.rb +36 -0
- data/lib/cannon/response.rb +165 -0
- data/lib/cannon/route.rb +80 -0
- data/lib/cannon/route_action.rb +92 -0
- data/lib/cannon/version.rb +3 -0
- data/lib/cannon/views.rb +28 -0
- data/spec/app_spec.rb +20 -0
- data/spec/config_spec.rb +41 -0
- data/spec/environments_spec.rb +68 -0
- data/spec/features/action_types_spec.rb +154 -0
- data/spec/features/cookies_spec.rb +62 -0
- data/spec/features/files_spec.rb +17 -0
- data/spec/features/method_types_spec.rb +104 -0
- data/spec/features/requests_spec.rb +59 -0
- data/spec/features/views_spec.rb +31 -0
- data/spec/fixtures/public/background.jpg +0 -0
- data/spec/fixtures/views/render_test.html +1 -0
- data/spec/fixtures/views/test.html +1 -0
- data/spec/spec_helper.rb +98 -0
- data/spec/support/cannon_test.rb +108 -0
- metadata +219 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module Signature
|
4
|
+
class CookieSecretNotSet < StandardError; end
|
5
|
+
|
6
|
+
def signature(value)
|
7
|
+
raise CookieSecretNotSet, 'Set config.cookies.secret to use signed cookies' if Cannon.config.cookies.secret.nil?
|
8
|
+
OpenSSL::HMAC.hexdigest(digest, Cannon.config.cookies.secret, value)
|
9
|
+
end
|
10
|
+
|
11
|
+
def digest
|
12
|
+
@digest ||= OpenSSL::Digest.new('sha1')
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Cannon
|
2
|
+
class UnknownLogLevel < StandardError; end
|
3
|
+
|
4
|
+
class Config
|
5
|
+
attr_accessor :middleware, :public_path, :view_path, :reload_on_request, :benchmark_requests, :port, :ip_address
|
6
|
+
attr_reader :logger, :log_level
|
7
|
+
|
8
|
+
DEFAULT_MIDDLEWARE = %w{RequestLogger Files Cookies Router ContentType}
|
9
|
+
|
10
|
+
LOG_LEVELS = {
|
11
|
+
unknown: Logger::UNKNOWN,
|
12
|
+
fatal: Logger::FATAL,
|
13
|
+
error: Logger::ERROR,
|
14
|
+
warn: Logger::WARN,
|
15
|
+
info: Logger::INFO,
|
16
|
+
debug: Logger::DEBUG,
|
17
|
+
}
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
self.ip_address = '127.0.0.1'
|
21
|
+
self.port = 5030
|
22
|
+
self.middleware = DEFAULT_MIDDLEWARE
|
23
|
+
self.public_path = 'public'
|
24
|
+
self.view_path = 'views'
|
25
|
+
self.reload_on_request = false
|
26
|
+
self.benchmark_requests = true
|
27
|
+
@log_level = :info
|
28
|
+
self.logger = Logger.new(STDOUT)
|
29
|
+
end
|
30
|
+
|
31
|
+
def logger=(value)
|
32
|
+
@logger = value
|
33
|
+
logger.datetime_format = '%Y-%m-%d %H:%M:%S'
|
34
|
+
logger.formatter = proc do |severity, datetime, progname, msg|
|
35
|
+
"#{msg}\n"
|
36
|
+
end
|
37
|
+
self.log_level = log_level
|
38
|
+
end
|
39
|
+
|
40
|
+
def log_level=(value)
|
41
|
+
raise UnknownLogLevel unless LOG_LEVELS.keys.include? value.to_sym
|
42
|
+
@log_level = value
|
43
|
+
logger.level = LOG_LEVELS[value.to_sym]
|
44
|
+
end
|
45
|
+
|
46
|
+
def cookies
|
47
|
+
@cookies ||= Cookies.new
|
48
|
+
end
|
49
|
+
|
50
|
+
class Cookies
|
51
|
+
attr_accessor :secret
|
52
|
+
|
53
|
+
def initialize
|
54
|
+
self.secret = nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'msgpack'
|
2
|
+
|
3
|
+
module Cannon
|
4
|
+
class CookieJar
|
5
|
+
include Signature
|
6
|
+
|
7
|
+
class EndOfString < Exception; end
|
8
|
+
|
9
|
+
def initialize(http_cookie: nil, cookies: nil, signed: false)
|
10
|
+
@http_cookie = http_cookie
|
11
|
+
@cookies = cookies
|
12
|
+
@signed = signed
|
13
|
+
|
14
|
+
self.define_singleton_method(:signed) do
|
15
|
+
@signed_cookies ||= CookieJar.new(cookies: cookies_with_signatures, signed: true)
|
16
|
+
end if !@signed
|
17
|
+
end
|
18
|
+
|
19
|
+
def [](cookie_name)
|
20
|
+
cookie = cookies[cookie_name]
|
21
|
+
if cookie
|
22
|
+
@signed ? verified_signature(cookie_name, cookie) : cookie['value']
|
23
|
+
else
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def verified_signature(name, cookie)
|
31
|
+
return cookie['value'] if cookie['verified']
|
32
|
+
|
33
|
+
if cookie['signature'] == signature(cookie['value'])
|
34
|
+
cookie['verified'] = true
|
35
|
+
cookie['value']
|
36
|
+
else
|
37
|
+
cookies.delete(name)
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def cookies_with_signatures
|
43
|
+
cookies.select { |k, v| v.include? 'signature' }
|
44
|
+
end
|
45
|
+
|
46
|
+
def cookies
|
47
|
+
@cookies ||= parse_cookies
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse_cookies
|
51
|
+
cookies = {}
|
52
|
+
return cookies if @http_cookie.nil? || @http_cookie == ''
|
53
|
+
|
54
|
+
begin
|
55
|
+
pos = 0
|
56
|
+
loop do
|
57
|
+
pos = read_whitespace(@http_cookie, pos)
|
58
|
+
name, pos = read_cookie_name(@http_cookie, pos)
|
59
|
+
value, pos = read_cookie_value(@http_cookie, pos)
|
60
|
+
begin
|
61
|
+
cookies[name.to_sym] = MessagePack.unpack(value)
|
62
|
+
rescue StandardError; end
|
63
|
+
end
|
64
|
+
rescue EndOfString
|
65
|
+
end
|
66
|
+
|
67
|
+
cookies
|
68
|
+
end
|
69
|
+
|
70
|
+
def read_whitespace(cookie, pos)
|
71
|
+
raise EndOfString if cookie[pos] == nil
|
72
|
+
pos = pos + 1 while cookie[pos] == ' ' && pos < cookie.length
|
73
|
+
pos
|
74
|
+
end
|
75
|
+
|
76
|
+
def read_cookie_name(cookie, pos)
|
77
|
+
start_pos = pos
|
78
|
+
pos = pos + 1 while !['=', nil].include?(cookie[pos])
|
79
|
+
return cookie[start_pos..(pos - 1)], pos + 1
|
80
|
+
end
|
81
|
+
|
82
|
+
def read_cookie_value(cookie, pos)
|
83
|
+
in_quotes = false
|
84
|
+
pos = pos + 1 and in_quotes = true if cookie[pos] == '"'
|
85
|
+
start_pos = pos
|
86
|
+
|
87
|
+
if in_quotes
|
88
|
+
pos = pos + 1 while pos < cookie.length && !(cookie[pos] == '"' && cookie[pos - 1] != '\\')
|
89
|
+
value = cookie[start_pos..(pos - 1)].gsub("\\\"", '"')
|
90
|
+
pos = pos + 1 while ![';', nil].include?(cookie[pos])
|
91
|
+
else
|
92
|
+
pos = pos + 1 while ![';', nil].include?(cookie[pos])
|
93
|
+
value = cookie[start_pos..(pos - 1)]
|
94
|
+
end
|
95
|
+
|
96
|
+
return value, pos + 1
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Cannon
|
2
|
+
class Handler < EventMachine::Connection
|
3
|
+
include EventMachine::HttpServer
|
4
|
+
|
5
|
+
def app
|
6
|
+
# magically defined by Cannon::App
|
7
|
+
self.class.app
|
8
|
+
end
|
9
|
+
|
10
|
+
def process_http_request
|
11
|
+
request = Request.new(self, app)
|
12
|
+
response = Response.new(self, app)
|
13
|
+
|
14
|
+
app.reload_environment if app.config.reload_on_request
|
15
|
+
|
16
|
+
app.middleware_runner.run(request, response) if middleware?
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def middleware?
|
22
|
+
app.config.middleware.size > 0
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'cannon/middleware/flush_and_benchmark'
|
2
|
+
require 'cannon/middleware/request_logger'
|
3
|
+
require 'cannon/middleware/files'
|
4
|
+
require 'cannon/middleware/router'
|
5
|
+
require 'cannon/middleware/content_type'
|
6
|
+
require 'cannon/middleware/cookies'
|
7
|
+
|
8
|
+
module Cannon
|
9
|
+
class MiddlewareRunner
|
10
|
+
include EventMachine::Deferrable
|
11
|
+
|
12
|
+
def initialize(ware, callback:, app:)
|
13
|
+
@app = app
|
14
|
+
@ware, @callback = instantiate(ware), callback
|
15
|
+
end
|
16
|
+
|
17
|
+
def run(request, response)
|
18
|
+
next_proc = -> do
|
19
|
+
setup_callback
|
20
|
+
self.succeed(request, response)
|
21
|
+
end
|
22
|
+
|
23
|
+
result = @ware.run(request, response, next_proc)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def setup_callback
|
29
|
+
set_deferred_status nil
|
30
|
+
callback do |request, response|
|
31
|
+
@callback.run(request, response) unless @callback.nil?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def instantiate(ware)
|
36
|
+
if ware.is_a?(String)
|
37
|
+
begin
|
38
|
+
Object.const_get(ware).new(@app)
|
39
|
+
rescue NameError
|
40
|
+
Object.const_get("Cannon::Middleware::#{ware}").new(@app)
|
41
|
+
end
|
42
|
+
else
|
43
|
+
ware.new(@app)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Cannon
|
2
|
+
module Middleware
|
3
|
+
class ContentType
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
end
|
7
|
+
|
8
|
+
def run(request, response, next_proc)
|
9
|
+
return next_proc.call unless response.headers['Content-Type'].nil?
|
10
|
+
response.headers['Content-Type'] = 'text/plain; charset=us-ascii'
|
11
|
+
next_proc.call
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Cannon
|
2
|
+
module Middleware
|
3
|
+
class Cookies
|
4
|
+
|
5
|
+
def initialize(app)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def run(request, response, next_proc)
|
10
|
+
request.define_singleton_method(:cookies) do
|
11
|
+
@cookie_jar ||= CookieJar.new(http_cookie: request.http_cookie)
|
12
|
+
end
|
13
|
+
|
14
|
+
next_proc.call
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Cannon
|
2
|
+
module Middleware
|
3
|
+
class Files
|
4
|
+
include PathCache
|
5
|
+
|
6
|
+
def initialize(app)
|
7
|
+
@app = app
|
8
|
+
|
9
|
+
self.cache = :files
|
10
|
+
self.base_path = build_base_path
|
11
|
+
end
|
12
|
+
|
13
|
+
def run(request, response, next_proc)
|
14
|
+
reload_cache if outdated_cache?
|
15
|
+
|
16
|
+
if path_array.include? request.path
|
17
|
+
file, content_type = *file_and_content_type("#{base_path}#{request.path}")
|
18
|
+
response.header('Content-Type', content_type)
|
19
|
+
response.send(file)
|
20
|
+
response.flush
|
21
|
+
else
|
22
|
+
next_proc.call
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def build_base_path
|
29
|
+
@app.config.public_path =~ /^\// ? @app.config.public_path : "#{Cannon.root}/#{@app.config.public_path}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Cannon
|
2
|
+
module Middleware
|
3
|
+
class FlushAndBenchmark
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
end
|
7
|
+
|
8
|
+
def run(request, response, next_proc)
|
9
|
+
response.flush unless response.flushed?
|
10
|
+
Cannon.logger.debug "Response took #{time_ago_in_ms(request.start_time)}ms" if @app.config.benchmark_requests
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def time_ago_in_ms(time_ago)
|
16
|
+
Time.at((Time.now - time_ago)).strftime('%6N').to_i/1000.0
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Cannon
|
2
|
+
module Middleware
|
3
|
+
class Router
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
end
|
7
|
+
|
8
|
+
def run(request, response, next_proc)
|
9
|
+
matched_route = @app.routes.find { |route| route.matches? request }
|
10
|
+
if matched_route.nil?
|
11
|
+
response.not_found
|
12
|
+
next_proc.call
|
13
|
+
else
|
14
|
+
matched_route.handle(request, response, next_proc)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module Cannon
|
4
|
+
class Request
|
5
|
+
attr_accessor :protocol, :method, :http_cookie, :content_type, :path, :uri, :query_string, :post_content, :headers,
|
6
|
+
:start_time
|
7
|
+
|
8
|
+
def initialize(http_server, app)
|
9
|
+
self.protocol = http_server.instance_variable_get('@http_protocol')
|
10
|
+
self.method = http_server.instance_variable_get('@http_request_method')
|
11
|
+
self.http_cookie = http_server.instance_variable_get('@http_cookie')
|
12
|
+
self.content_type = http_server.instance_variable_get('@http_content_type')
|
13
|
+
self.path = http_server.instance_variable_get('@http_path_info')
|
14
|
+
self.uri = http_server.instance_variable_get('@http_request_uri')
|
15
|
+
self.query_string = http_server.instance_variable_get('@http_query_string')
|
16
|
+
self.post_content = http_server.instance_variable_get('@http_post_content')
|
17
|
+
self.headers = http_server.instance_variable_get('@http_headers')
|
18
|
+
self.start_time = Time.now
|
19
|
+
end
|
20
|
+
|
21
|
+
def params
|
22
|
+
@params ||= parse_params
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def parse_params
|
28
|
+
case method.downcase
|
29
|
+
when 'get'
|
30
|
+
Hash[CGI::parse(query_string || '').map { |(k, v)| [k.to_sym, v.last] }]
|
31
|
+
else
|
32
|
+
Hash[CGI::parse(post_content || '').map { |(k, v)| [k.to_sym, v.count > 1 ? v : v.first] }]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'msgpack'
|
2
|
+
|
3
|
+
module Cannon
|
4
|
+
class Response
|
5
|
+
extend Forwardable
|
6
|
+
include Views
|
7
|
+
include Signature
|
8
|
+
|
9
|
+
attr_reader :delegated_response, :headers
|
10
|
+
attr_accessor :status
|
11
|
+
|
12
|
+
delegate :content => :delegated_response
|
13
|
+
delegate :content= => :delegated_response
|
14
|
+
|
15
|
+
HTTP_STATUS = {
|
16
|
+
continue: 100,
|
17
|
+
switching_protocols: 101,
|
18
|
+
ok: 200,
|
19
|
+
created: 201,
|
20
|
+
accepted: 202,
|
21
|
+
non_authoritative_information: 203,
|
22
|
+
no_content: 204,
|
23
|
+
reset_content: 205,
|
24
|
+
partial_content: 206,
|
25
|
+
multiple_choices: 300,
|
26
|
+
moved_permanently: 301,
|
27
|
+
found: 302,
|
28
|
+
see_other: 303,
|
29
|
+
not_modified: 304,
|
30
|
+
use_proxy: 305,
|
31
|
+
temporary_redirect: 307,
|
32
|
+
bad_request: 400,
|
33
|
+
unauthorized: 401,
|
34
|
+
payment_required: 402,
|
35
|
+
forbidden: 403,
|
36
|
+
not_found: 404,
|
37
|
+
method_not_allowed: 405,
|
38
|
+
not_acceptable: 406,
|
39
|
+
proxy_authentication_required: 407,
|
40
|
+
request_timeout: 408,
|
41
|
+
conflict: 409,
|
42
|
+
gone: 410,
|
43
|
+
length_required: 411,
|
44
|
+
precondition_failed: 412,
|
45
|
+
request_entity_too_large: 413,
|
46
|
+
request_uri_too_long: 414,
|
47
|
+
unsupported_media_type: 415,
|
48
|
+
requested_range_not_satisfied: 416,
|
49
|
+
expectation_failed: 417,
|
50
|
+
internal_server_error: 500,
|
51
|
+
not_implemented: 501,
|
52
|
+
bad_gateway: 502,
|
53
|
+
service_unavailable: 503,
|
54
|
+
gateway_timeout: 504,
|
55
|
+
http_version_not_supported: 505,
|
56
|
+
}
|
57
|
+
|
58
|
+
def initialize(http_server, app)
|
59
|
+
@app = app
|
60
|
+
@delegated_response = EventMachine::DelegatedHttpResponse.new(http_server)
|
61
|
+
@flushed = false
|
62
|
+
@headers = {}
|
63
|
+
@cookies = {}
|
64
|
+
|
65
|
+
initialize_views
|
66
|
+
|
67
|
+
self.status = :ok
|
68
|
+
end
|
69
|
+
|
70
|
+
def flushed?
|
71
|
+
@flushed
|
72
|
+
end
|
73
|
+
|
74
|
+
def send(content, status: self.status)
|
75
|
+
self.content ||= ''
|
76
|
+
self.status = status
|
77
|
+
delegated_response.status = converted_status(status)
|
78
|
+
delegated_response.content += content
|
79
|
+
end
|
80
|
+
|
81
|
+
def flush
|
82
|
+
unless flushed?
|
83
|
+
set_cookie_headers
|
84
|
+
delegated_response.headers = self.headers
|
85
|
+
delegated_response.send_response
|
86
|
+
@flushed = true
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def header(key, value)
|
91
|
+
headers[key] = value
|
92
|
+
end
|
93
|
+
|
94
|
+
def location_header(location)
|
95
|
+
header('Location', location)
|
96
|
+
end
|
97
|
+
|
98
|
+
def permanent_redirect(location)
|
99
|
+
location_header(location)
|
100
|
+
self.status = :moved_permanently
|
101
|
+
flush
|
102
|
+
end
|
103
|
+
|
104
|
+
def temporary_redirect(location)
|
105
|
+
location_header(location)
|
106
|
+
self.status = :found
|
107
|
+
flush
|
108
|
+
end
|
109
|
+
|
110
|
+
def not_found
|
111
|
+
send('Not Found', status: :not_found)
|
112
|
+
end
|
113
|
+
|
114
|
+
def internal_server_error(title:, content:)
|
115
|
+
html = "<html><head><title>Internal Server Error: #{title}</title></head><body><h1>#{title}</h1><p>#{content}</p></body></html>"
|
116
|
+
header('Content-Type', 'text/html')
|
117
|
+
send(html, status: :internal_server_error)
|
118
|
+
end
|
119
|
+
|
120
|
+
def cookie(cookie, value:, expires: nil, httponly: nil, signed: false)
|
121
|
+
cookie_options = {:value => value}
|
122
|
+
cookie_options[:expires] = expires unless expires.nil?
|
123
|
+
cookie_options[:httponly] = httponly unless httponly.nil?
|
124
|
+
cookie_options[:signed] = signed
|
125
|
+
@cookies[cookie] = cookie_options
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def set_cookie_headers
|
131
|
+
cookie_headers = (headers['Set-Cookie'] = [])
|
132
|
+
@cookies.each do |cookie, cookie_options|
|
133
|
+
cookie_headers << build_cookie_value(cookie, cookie_options)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def build_cookie_value(name, options)
|
138
|
+
cookie = "#{name}=#{cookie_value(options[:value], signed: options[:signed])}"
|
139
|
+
cookie << "; Expires=#{options[:expires].httpdate}" if options.include?(:expires)
|
140
|
+
cookie << '; HttpOnly' if options[:httponly] == true
|
141
|
+
cookie
|
142
|
+
end
|
143
|
+
|
144
|
+
def converted_status(status)
|
145
|
+
if status.is_a?(Symbol)
|
146
|
+
HTTP_STATUS[status] || status.to_s
|
147
|
+
elsif status.is_a?(Fixnum)
|
148
|
+
status
|
149
|
+
else
|
150
|
+
status.to_s
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def cookie_value(value, signed:)
|
155
|
+
cookie_hash = {'value' => value}
|
156
|
+
cookie_hash['signature'] = signature(value) if signed
|
157
|
+
escape_cookie_value(cookie_hash.to_msgpack)
|
158
|
+
end
|
159
|
+
|
160
|
+
def escape_cookie_value(value)
|
161
|
+
return value unless value.match(/([\x00-\x20\x7F",;\\])/)
|
162
|
+
"\"#{value.gsub(/([\\"])/, "\\\\\\1")}\""
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|