(NOT FOR RELEASE) biblically accurate jwt tokens; minor extra features for requests; redirections, jumps, and request looping; rack application class; crosscalls; partial hijacking; session object; proper context description for dsl methods (DO NOT SHIP, VULNERABILITY FOUND)
This commit is contained in:
parent
1e546aa417
commit
e251d2def6
|
@ -0,0 +1,53 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
$LOAD_PATH.unshift("#{File.dirname(__FILE__)}/lib")
|
||||||
|
require_relative '../lib/landline'
|
||||||
|
|
||||||
|
class Propogator
|
||||||
|
def initialize
|
||||||
|
@queue = []
|
||||||
|
end
|
||||||
|
|
||||||
|
# Append a client to the queue
|
||||||
|
def append(client)
|
||||||
|
@queue.append(client)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Push data to all clients, releasing all of them at once
|
||||||
|
def push(data)
|
||||||
|
@queue.each do |client|
|
||||||
|
client << data
|
||||||
|
client.flush
|
||||||
|
client.close
|
||||||
|
end
|
||||||
|
@queue = []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @!parse include ::Landline::DSL::PathConstructors
|
||||||
|
# @!parse include ::Landline::DSL::PathMethods
|
||||||
|
|
||||||
|
longpoll_queue = Propogator.new
|
||||||
|
|
||||||
|
app = Landline::Server.new do
|
||||||
|
post "/push_event" do
|
||||||
|
longpoll_queue.push(request.body)
|
||||||
|
next request.body
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/await_event" do
|
||||||
|
next "No long polling for you :(" unless request.hijack?
|
||||||
|
|
||||||
|
partial_hijack do |stream|
|
||||||
|
longpoll_queue.append(stream)
|
||||||
|
end
|
||||||
|
|
||||||
|
next ''
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/ping" do
|
||||||
|
"pong"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
run app
|
|
@ -0,0 +1,43 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
$LOAD_PATH.unshift("#{File.dirname(__FILE__)}/lib")
|
||||||
|
require_relative '../lib/landline'
|
||||||
|
|
||||||
|
# @!parse include ::Landline::DSL::PathConstructors
|
||||||
|
# @!parse include ::Landline::DSL::PathMethods
|
||||||
|
|
||||||
|
app = Landline::Server.new do
|
||||||
|
postprocess do |request, response|
|
||||||
|
puts response.class
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/hijack" do
|
||||||
|
# @!parse include ::Landline::DSL::ProbeMethods
|
||||||
|
if request.hijack?
|
||||||
|
partial_hijack do |stream|
|
||||||
|
sec = (rand * 20).floor
|
||||||
|
sleep(sec)
|
||||||
|
stream << <<~DOC
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<HTML>
|
||||||
|
<HEAD>
|
||||||
|
<TITLE>You have been delayed, bozo!</TITLE>
|
||||||
|
</HEAD>
|
||||||
|
<BODY>
|
||||||
|
<H1>Get delayed, dumbass!</H1>
|
||||||
|
<P>Imagine getting delayed for like, what, #{sec} seconds? LMAO</P>
|
||||||
|
</BODY>
|
||||||
|
</HTML>
|
||||||
|
DOC
|
||||||
|
stream.flush
|
||||||
|
stream.close
|
||||||
|
end
|
||||||
|
next ''
|
||||||
|
else
|
||||||
|
header "content-type", "text/plain"
|
||||||
|
next 'No partial hijacking for you :('
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
run app
|
|
@ -0,0 +1,73 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'landline'
|
||||||
|
|
||||||
|
# Example rack middleware
|
||||||
|
class TimerMiddleware
|
||||||
|
def initialize(app)
|
||||||
|
@app = app
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(*data)
|
||||||
|
puts("Request accepted")
|
||||||
|
before = Time.now
|
||||||
|
output = @app.call(*data)
|
||||||
|
puts("Time elapsed: #{(Time.now - before) * 1000}ms")
|
||||||
|
output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Example Landline application as rack middleware
|
||||||
|
class HelloServer < Landline::App
|
||||||
|
setup do
|
||||||
|
get "/test2" do
|
||||||
|
"Hello world from #{self}!"
|
||||||
|
end
|
||||||
|
handle do |status, backtrace: nil|
|
||||||
|
page = ([Landline::Util::HTTP_STATUS[status]] +
|
||||||
|
(backtrace || [""])).join("\n")
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"content-length": page.bytesize,
|
||||||
|
"content-type": "text/plain",
|
||||||
|
"x-cascade": true
|
||||||
|
},
|
||||||
|
page
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Example Landline app as rack application
|
||||||
|
class CrossCallServer < Landline::App
|
||||||
|
setup do
|
||||||
|
get "/inner_test" do
|
||||||
|
"Hello world, through crosscall!"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Example Landline app as rack application
|
||||||
|
class Server < Landline::App
|
||||||
|
use TimerMiddleware
|
||||||
|
use HelloServer
|
||||||
|
|
||||||
|
setup do
|
||||||
|
crosscall_server = CrossCallServer.new
|
||||||
|
|
||||||
|
get "/test" do
|
||||||
|
"Hello from #{self}!"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Cross-callable application included as a subpath
|
||||||
|
link "/outer", crosscall_server
|
||||||
|
|
||||||
|
# Cross calling an application in a probe context
|
||||||
|
get "/crosscall" do
|
||||||
|
request.path = "/inner_test"
|
||||||
|
call(crosscall_server)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
run Server.new
|
|
@ -0,0 +1,67 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'landline/extensions/session'
|
||||||
|
require 'landline'
|
||||||
|
|
||||||
|
Landline::Session.hmac_secret = "Your secure signing secret here"
|
||||||
|
|
||||||
|
app = Landline::Server.new do
|
||||||
|
get "/make_cookie" do
|
||||||
|
session["random_number"] = Random.random_number(100)
|
||||||
|
text = <<~HTML
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>
|
||||||
|
Session test #{request.to_s}
|
||||||
|
</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Your random number is #RAND#!</h1>
|
||||||
|
<hr>
|
||||||
|
<p>Go check it at <a href="/check_cookie">this link!</a></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
HTML
|
||||||
|
sleep(20)
|
||||||
|
text.gsub("#RAND#", request.to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/check_cookie" do
|
||||||
|
if session["random_number"]
|
||||||
|
<<~HTML
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>
|
||||||
|
Session test
|
||||||
|
</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Your random number is...</h1>
|
||||||
|
<hr>
|
||||||
|
<p>#{session['random_number']}! Enjoy your random magic number!</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
HTML
|
||||||
|
else
|
||||||
|
<<~HTML
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>
|
||||||
|
Session test
|
||||||
|
</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Uh oh!</h1>
|
||||||
|
<hr>
|
||||||
|
<p>Go get your magic number at <a href="/make_cookie">this link!</a></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
HTML
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
run app
|
|
@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
||||||
It is usable for many menial tasks, and as long as it continues to be fun, it will keep growing.
|
It is usable for many menial tasks, and as long as it continues to be fun, it will keep growing.
|
||||||
DESC
|
DESC
|
||||||
spec.authors = ["Yessiest"]
|
spec.authors = ["Yessiest"]
|
||||||
spec.license = "AGPL-3.0"
|
spec.license = "AGPL-3.0-or-later"
|
||||||
spec.email = "yessiest@text.512mb.org"
|
spec.email = "yessiest@text.512mb.org"
|
||||||
spec.homepage = "https://adastra7.net/git/Yessiest/landline"
|
spec.homepage = "https://adastra7.net/git/Yessiest/landline"
|
||||||
spec.files = Dir["lib/**/*"]
|
spec.files = Dir["lib/**/*"]
|
||||||
|
|
|
@ -7,6 +7,7 @@ require_relative 'landline/probe'
|
||||||
require_relative 'landline/request'
|
require_relative 'landline/request'
|
||||||
require_relative 'landline/response'
|
require_relative 'landline/response'
|
||||||
require_relative 'landline/template'
|
require_relative 'landline/template'
|
||||||
|
require_relative 'landline/app'
|
||||||
|
|
||||||
# Landline is a hideously simple ruby web framework
|
# Landline is a hideously simple ruby web framework
|
||||||
module Landline
|
module Landline
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Landline
|
||||||
|
# Rack application interface
|
||||||
|
class App
|
||||||
|
# TODO: fix this mess somehow (probably impossible)
|
||||||
|
|
||||||
|
# @!parse include Landline::DSL::PathMethods
|
||||||
|
# @!parse include Landline::DSL::PathConstructors
|
||||||
|
# @!parse include Landline::DSL::ProbeConstructors
|
||||||
|
# @!parse include Landline::DSL::ProbeMethods
|
||||||
|
# @!parse include Landline::DSL::CommonMethods
|
||||||
|
class << self
|
||||||
|
# Duplicate used middleware for the subclassed app
|
||||||
|
def inherited(subclass)
|
||||||
|
super(subclass)
|
||||||
|
subclass.middleware = @middleware.dup
|
||||||
|
end
|
||||||
|
|
||||||
|
# Include a middleware in application
|
||||||
|
# @param middleware [Class]
|
||||||
|
def use(middleware)
|
||||||
|
@middleware ||= []
|
||||||
|
@middleware.append(middleware)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Setup block
|
||||||
|
# @param block [#call]
|
||||||
|
def setup(&block)
|
||||||
|
@setup_block = block
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_accessor :middleware, :setup_block
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize(*args, **opts)
|
||||||
|
@app = ::Landline::Server.new(*args, **opts, &self.class.setup_block)
|
||||||
|
self.class.middleware&.reverse_each do |cls|
|
||||||
|
@app = cls.new(@app)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Rack ingress point.
|
||||||
|
# @param env [Hash]
|
||||||
|
# @return [Array(Integer,Hash,Array)]
|
||||||
|
def call(env)
|
||||||
|
@app.call(env)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -5,6 +5,7 @@ module Landline
|
||||||
module DSL
|
module DSL
|
||||||
# Path (and subclasses) DSL constructors
|
# Path (and subclasses) DSL constructors
|
||||||
module PathConstructors
|
module PathConstructors
|
||||||
|
# (in Landline::Path context)
|
||||||
# Append a Node child object to the list of children
|
# Append a Node child object to the list of children
|
||||||
def register(obj)
|
def register(obj)
|
||||||
unless obj.is_a? Landline::Node
|
unless obj.is_a? Landline::Node
|
||||||
|
@ -14,11 +15,13 @@ module Landline
|
||||||
@origin.children.append(obj)
|
@origin.children.append(obj)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Path context)
|
||||||
# Create a new {Landline::Path} object
|
# Create a new {Landline::Path} object
|
||||||
def path(path, **args, &setup)
|
def path(path, **args, &setup)
|
||||||
register(Landline::Path.new(path, parent: @origin, **args, &setup))
|
register(Landline::Path.new(path, parent: @origin, **args, &setup))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Path context)
|
||||||
# Create a new {Landline::Handlers::Probe} object
|
# Create a new {Landline::Handlers::Probe} object
|
||||||
def probe(path, **args, &_setup)
|
def probe(path, **args, &_setup)
|
||||||
register(Landline::Handlers::Probe.new(path,
|
register(Landline::Handlers::Probe.new(path,
|
||||||
|
@ -26,6 +29,7 @@ module Landline
|
||||||
**args))
|
**args))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Path context)
|
||||||
# Create a new {Landline::Handlers::GETHandler} object
|
# Create a new {Landline::Handlers::GETHandler} object
|
||||||
def get(path, **args, &setup)
|
def get(path, **args, &setup)
|
||||||
register(Landline::Handlers::GET.new(path,
|
register(Landline::Handlers::GET.new(path,
|
||||||
|
@ -34,6 +38,7 @@ module Landline
|
||||||
&setup))
|
&setup))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Path context)
|
||||||
# create a new {Landline::Handlers::POSTHandler} object
|
# create a new {Landline::Handlers::POSTHandler} object
|
||||||
def post(path, **args, &setup)
|
def post(path, **args, &setup)
|
||||||
register(Landline::Handlers::POST.new(path,
|
register(Landline::Handlers::POST.new(path,
|
||||||
|
@ -42,6 +47,7 @@ module Landline
|
||||||
&setup))
|
&setup))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Path context)
|
||||||
# Create a new {Landline::Handlers::PUTHandler} object
|
# Create a new {Landline::Handlers::PUTHandler} object
|
||||||
def put(path, **args, &setup)
|
def put(path, **args, &setup)
|
||||||
register(Landline::Handlers::PUT.new(path,
|
register(Landline::Handlers::PUT.new(path,
|
||||||
|
@ -50,6 +56,7 @@ module Landline
|
||||||
&setup))
|
&setup))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Path context)
|
||||||
# Create a new {Landline::Handlers::HEADHandler} object
|
# Create a new {Landline::Handlers::HEADHandler} object
|
||||||
def head(path, **args, &setup)
|
def head(path, **args, &setup)
|
||||||
register(Landline::Handlers::HEAD.new(path,
|
register(Landline::Handlers::HEAD.new(path,
|
||||||
|
@ -58,6 +65,7 @@ module Landline
|
||||||
&setup))
|
&setup))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Path context)
|
||||||
# Create a new {Landline::Handlers::DELETEHandler} object
|
# Create a new {Landline::Handlers::DELETEHandler} object
|
||||||
def delete(path, **args, &setup)
|
def delete(path, **args, &setup)
|
||||||
register(Landline::Handlers::DELETE.new(path,
|
register(Landline::Handlers::DELETE.new(path,
|
||||||
|
@ -66,6 +74,7 @@ module Landline
|
||||||
&setup))
|
&setup))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Path context)
|
||||||
# Create a new {Landline::Handlers::CONNECTHandler} object
|
# Create a new {Landline::Handlers::CONNECTHandler} object
|
||||||
def connect(path, **args, &setup)
|
def connect(path, **args, &setup)
|
||||||
register(Landline::Handlers::CONNECT.new(path,
|
register(Landline::Handlers::CONNECT.new(path,
|
||||||
|
@ -74,6 +83,7 @@ module Landline
|
||||||
&setup))
|
&setup))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Path context)
|
||||||
# Create a new {Landline::Handlers::TRACEHandler} object
|
# Create a new {Landline::Handlers::TRACEHandler} object
|
||||||
def trace(path, **args, &setup)
|
def trace(path, **args, &setup)
|
||||||
register(Landline::Handlers::TRACE.new(path,
|
register(Landline::Handlers::TRACE.new(path,
|
||||||
|
@ -82,6 +92,7 @@ module Landline
|
||||||
&setup))
|
&setup))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Path context)
|
||||||
# Create a new {Landline::Handlers::PATCHHandler} object
|
# Create a new {Landline::Handlers::PATCHHandler} object
|
||||||
def patch(path, **args, &setup)
|
def patch(path, **args, &setup)
|
||||||
register(Landline::Handlers::PATCH.new(path,
|
register(Landline::Handlers::PATCH.new(path,
|
||||||
|
@ -90,6 +101,7 @@ module Landline
|
||||||
&setup))
|
&setup))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Path context)
|
||||||
# Create a new {Landline::Handlers::OPTIONSHandler} object
|
# Create a new {Landline::Handlers::OPTIONSHandler} object
|
||||||
def options(path, **args, &setup)
|
def options(path, **args, &setup)
|
||||||
register(Landline::Handlers::OPTIONS.new(path,
|
register(Landline::Handlers::OPTIONS.new(path,
|
||||||
|
@ -98,10 +110,19 @@ module Landline
|
||||||
&setup))
|
&setup))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Path context)
|
||||||
# Create a new {Landline::Handlers::GETHandler} that serves static files
|
# Create a new {Landline::Handlers::GETHandler} that serves static files
|
||||||
def serve(path)
|
def serve(path)
|
||||||
register(Landline::Handlers::Serve.new(path, parent: @origin))
|
register(Landline::Handlers::Serve.new(path, parent: @origin))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Path context)
|
||||||
|
# Create a new application crosscall link (acts like #call in probe context and strips its path from request)
|
||||||
|
def link(path, application)
|
||||||
|
register(Landline::Handlers::Link.new(path,
|
||||||
|
application,
|
||||||
|
parent: @origin))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,6 +4,7 @@ module Landline
|
||||||
module DSL
|
module DSL
|
||||||
# Probe (and subclasses) DSL construct
|
# Probe (and subclasses) DSL construct
|
||||||
module ProbeConstructors
|
module ProbeConstructors
|
||||||
|
# (in Landline::Probe context)
|
||||||
# Create a new erb template
|
# Create a new erb template
|
||||||
# @see Landline::Template#new
|
# @see Landline::Template#new
|
||||||
def erb(input, vars = {})
|
def erb(input, vars = {})
|
||||||
|
@ -13,6 +14,7 @@ module Landline
|
||||||
filename: caller_locations[0].path)
|
filename: caller_locations[0].path)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Probe context)
|
||||||
# Create a new erb template using Erubi engine
|
# Create a new erb template using Erubi engine
|
||||||
# @see Landline::Template#new
|
# @see Landline::Template#new
|
||||||
# @param freeze [Boolean] whether to use frozen string literal
|
# @param freeze [Boolean] whether to use frozen string literal
|
||||||
|
|
|
@ -4,6 +4,7 @@ module Landline
|
||||||
module DSL
|
module DSL
|
||||||
# Methods shared by probes, preprocessors and filters.
|
# Methods shared by probes, preprocessors and filters.
|
||||||
module CommonMethods
|
module CommonMethods
|
||||||
|
# (in Landline::Probe context)
|
||||||
# Stop execution and generate a boilerplate response with the given code
|
# Stop execution and generate a boilerplate response with the given code
|
||||||
# @param errorcode [Integer]
|
# @param errorcode [Integer]
|
||||||
# @param backtrace [Array(String), nil]
|
# @param backtrace [Array(String), nil]
|
||||||
|
@ -18,6 +19,7 @@ module Landline
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Probe context)
|
||||||
# Bounce request to the next handler
|
# Bounce request to the next handler
|
||||||
# @raise [UncaughtThrowError] throws :break to get out of the callback
|
# @raise [UncaughtThrowError] throws :break to get out of the callback
|
||||||
def bounce
|
def bounce
|
||||||
|
|
|
@ -5,11 +5,19 @@ module Landline
|
||||||
module DSL
|
module DSL
|
||||||
# Common path methods
|
# Common path methods
|
||||||
module PathMethods
|
module PathMethods
|
||||||
|
# (in Landline::Path context)
|
||||||
# Bounce request if no handler found instead of issuing 404
|
# Bounce request if no handler found instead of issuing 404
|
||||||
def bounce
|
def bounce
|
||||||
@origin.bounce = true
|
@origin.bounce = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Path context)
|
||||||
|
# Unset bounce
|
||||||
|
def nobounce
|
||||||
|
@origin.bounce = false
|
||||||
|
end
|
||||||
|
|
||||||
|
# (in Landline::Path context)
|
||||||
# Create a status code handler on path.
|
# Create a status code handler on path.
|
||||||
# Recursively applies to all paths unless overridden.
|
# Recursively applies to all paths unless overridden.
|
||||||
# @param code [Integer, nil] Specify a status code to handle
|
# @param code [Integer, nil] Specify a status code to handle
|
||||||
|
@ -18,6 +26,7 @@ module Landline
|
||||||
@origin.properties["handle.#{code || 'default'}"] = block
|
@origin.properties["handle.#{code || 'default'}"] = block
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Path context)
|
||||||
# Insert a pass-through pipeline into request processing
|
# Insert a pass-through pipeline into request processing
|
||||||
# (i.e. for error handling purposes).
|
# (i.e. for error handling purposes).
|
||||||
# Passed block should yield request (and return yielded data back).
|
# Passed block should yield request (and return yielded data back).
|
||||||
|
@ -26,6 +35,7 @@ module Landline
|
||||||
@origin.pipeline = block
|
@origin.pipeline = block
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Path context)
|
||||||
# Set path index
|
# Set path index
|
||||||
# @param index [Array,String]
|
# @param index [Array,String]
|
||||||
def index(index)
|
def index(index)
|
||||||
|
@ -39,18 +49,21 @@ module Landline
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Path context)
|
||||||
# Set root path (appends matched part of the path).
|
# Set root path (appends matched part of the path).
|
||||||
# @param path [String]
|
# @param path [String]
|
||||||
def root(path)
|
def root(path)
|
||||||
@origin.root = File.expand_path(path)
|
@origin.root = File.expand_path(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Path context)
|
||||||
# Set root path (without appending matched part).
|
# Set root path (without appending matched part).
|
||||||
# @param path [String]
|
# @param path [String]
|
||||||
def remap(path)
|
def remap(path)
|
||||||
@origin.remap = File.expand_path(path)
|
@origin.remap = File.expand_path(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Path context)
|
||||||
# Add a preprocessor to the path.
|
# Add a preprocessor to the path.
|
||||||
# Does not modify path execution.
|
# Does not modify path execution.
|
||||||
# @param block [#call]
|
# @param block [#call]
|
||||||
|
@ -60,6 +73,7 @@ module Landline
|
||||||
block
|
block
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Path context)
|
||||||
# Add a postprocessor to the path.
|
# Add a postprocessor to the path.
|
||||||
# @param block [#call]
|
# @param block [#call]
|
||||||
# @yieldparam request [Landline::Request]
|
# @yieldparam request [Landline::Request]
|
||||||
|
@ -72,6 +86,7 @@ module Landline
|
||||||
alias before preprocess
|
alias before preprocess
|
||||||
alias after postprocess
|
alias after postprocess
|
||||||
|
|
||||||
|
# (in Landline::Path context)
|
||||||
# Add a filter to the path.
|
# Add a filter to the path.
|
||||||
# Blocks path access if a filter returns false.
|
# Blocks path access if a filter returns false.
|
||||||
# @param block [#call]
|
# @param block [#call]
|
||||||
|
@ -81,7 +96,9 @@ module Landline
|
||||||
block
|
block
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Path context)
|
||||||
# Include an application as a child of path.
|
# Include an application as a child of path.
|
||||||
|
# @deprecated this method is being deprecated due to strong dependency on the framework
|
||||||
# @param filename [String]
|
# @param filename [String]
|
||||||
def plugin(filename)
|
def plugin(filename)
|
||||||
define_singleton_method(:run) do |object|
|
define_singleton_method(:run) do |object|
|
||||||
|
|
|
@ -10,12 +10,14 @@ module Landline
|
||||||
module DSL
|
module DSL
|
||||||
# Common methods for Probe objects
|
# Common methods for Probe objects
|
||||||
module ProbeMethods
|
module ProbeMethods
|
||||||
|
# (in Landline::Probe context)
|
||||||
# Get the current request
|
# Get the current request
|
||||||
# @return [Landline::Request]
|
# @return [Landline::Request]
|
||||||
def request
|
def request
|
||||||
@origin.request
|
@origin.request
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Probe context)
|
||||||
# Set response status (generate response if one doesn't exist yet)
|
# Set response status (generate response if one doesn't exist yet)
|
||||||
# @param status [Integer] http status code
|
# @param status [Integer] http status code
|
||||||
def status(status)
|
def status(status)
|
||||||
|
@ -23,8 +25,59 @@ module Landline
|
||||||
@origin.response.status = status
|
@origin.response.status = status
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Probe context)
|
||||||
|
# Add a finalizer callable to the response
|
||||||
|
# @param callable [#call]
|
||||||
|
def defer(&callable)
|
||||||
|
rack = @origin.request.rack
|
||||||
|
if rack.respond_to?(:response_finished)
|
||||||
|
rack.response_finished.append(callable)
|
||||||
|
end
|
||||||
|
# puma for some reason isn't compatible with the 3.0.0 spec on this
|
||||||
|
rack.after_reply.append(callable) if rack.respond_to?(:after_reply)
|
||||||
|
end
|
||||||
|
|
||||||
|
# (in Landline::Probe context)
|
||||||
|
# Do serverside request redirection
|
||||||
|
# @note this essentially reprocesses the whole request - be mindful of processing time!
|
||||||
|
# @param path [String]
|
||||||
|
def jump(path)
|
||||||
|
@origin.request.path = path
|
||||||
|
throw(:break, [307, { "x-internal-jump": true }, []])
|
||||||
|
end
|
||||||
|
|
||||||
|
# (in Landline::Probe context)
|
||||||
|
# Do clientside request redirection via 302 code
|
||||||
|
# @param path [String]
|
||||||
|
def redirect(path)
|
||||||
|
throw(:break, [302, { "location": path }, []])
|
||||||
|
end
|
||||||
|
|
||||||
|
# (in Landline::Probe context)
|
||||||
|
# Do clientside request redirection via 307 code
|
||||||
|
# @param path [String]
|
||||||
|
def redirect_with_method(path)
|
||||||
|
throw(:break, [307, { "location": path }, []])
|
||||||
|
end
|
||||||
|
|
||||||
alias code status
|
alias code status
|
||||||
|
|
||||||
|
# (in Landline::Probe context)
|
||||||
|
# Set a partial hijack callback
|
||||||
|
# @param block [#call] Callable block
|
||||||
|
def partial_hijack(&block)
|
||||||
|
@origin.response ||= Landline::Response.new
|
||||||
|
@origin.response.add_header("rack.hijack", block)
|
||||||
|
end
|
||||||
|
|
||||||
|
# (in Landline::Probe context)
|
||||||
|
# Fully hijack IO
|
||||||
|
# @return [IO]
|
||||||
|
def hijack
|
||||||
|
@origin.request.hijack.call
|
||||||
|
end
|
||||||
|
|
||||||
|
# (in Landline::Probe context)
|
||||||
# Set response header (generate response if one doesn't exist yet)
|
# Set response header (generate response if one doesn't exist yet)
|
||||||
# @param key [String] header name
|
# @param key [String] header name
|
||||||
# @param value [String] header value
|
# @param value [String] header value
|
||||||
|
@ -40,10 +93,11 @@ module Landline
|
||||||
end
|
end
|
||||||
|
|
||||||
@origin.response = (@origin.response or Landline::Response.new)
|
@origin.response = (@origin.response or Landline::Response.new)
|
||||||
key = key.downcase
|
key = key.downcase.to_s
|
||||||
@origin.response.add_header(key, value)
|
@origin.response.add_header(key, value)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Probe context)
|
||||||
# Delete a header value from the headers hash
|
# Delete a header value from the headers hash
|
||||||
# If no value is provided, deletes all key entries
|
# If no value is provided, deletes all key entries
|
||||||
# @param key [String] header name
|
# @param key [String] header name
|
||||||
|
@ -61,9 +115,10 @@ module Landline
|
||||||
raise ArgumentError, "value key has invalid characters"
|
raise ArgumentError, "value key has invalid characters"
|
||||||
end
|
end
|
||||||
|
|
||||||
@origin.response.delete_header(key, value)
|
@origin.response.delete_header(key.to_s, value)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Probe context)
|
||||||
# Set response cookie
|
# Set response cookie
|
||||||
# @see Landline::Cookie.new
|
# @see Landline::Cookie.new
|
||||||
def cookie(*params, **options)
|
def cookie(*params, **options)
|
||||||
|
@ -73,6 +128,7 @@ module Landline
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Probe context)
|
||||||
# Delete a cookie
|
# Delete a cookie
|
||||||
# If no value is provided, deletes all cookies with the same key
|
# If no value is provided, deletes all cookies with the same key
|
||||||
# @param key [String] cookie key
|
# @param key [String] cookie key
|
||||||
|
@ -83,6 +139,7 @@ module Landline
|
||||||
@origin.response.delete_cookie(key, value)
|
@origin.response.delete_cookie(key, value)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Probe context)
|
||||||
# Checks if current request has multipart/form-data associated with it
|
# Checks if current request has multipart/form-data associated with it
|
||||||
# @return [Boolean]
|
# @return [Boolean]
|
||||||
def form?
|
def form?
|
||||||
|
@ -90,6 +147,7 @@ module Landline
|
||||||
!!(value && opts && opts['boundary'])
|
!!(value && opts && opts['boundary'])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Probe context)
|
||||||
# Returns formdata
|
# Returns formdata
|
||||||
# @note reads request.input - may nullify request.body.
|
# @note reads request.input - may nullify request.body.
|
||||||
# @return [Hash{String=>(String,Landline::Util::FormPart)}]
|
# @return [Hash{String=>(String,Landline::Util::FormPart)}]
|
||||||
|
@ -102,12 +160,14 @@ module Landline
|
||||||
).to_h
|
).to_h
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Probe context)
|
||||||
# Checks if current request has urlencoded query string
|
# Checks if current request has urlencoded query string
|
||||||
# @return [Boolean]
|
# @return [Boolean]
|
||||||
def query?
|
def query?
|
||||||
!!_verify_content_type("application/x-www-form-urlencode")
|
!!_verify_content_type("application/x-www-form-urlencode")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Probe context)
|
||||||
# Returns parsed query hash
|
# Returns parsed query hash
|
||||||
# @note reads request.body - may nullify .input, .body data is memoized
|
# @note reads request.body - may nullify .input, .body data is memoized
|
||||||
# @return [Hash{String => Object}] query data
|
# @return [Hash{String => Object}] query data
|
||||||
|
@ -115,6 +175,7 @@ module Landline
|
||||||
Landline::Util::Query.new(request.body).parse
|
Landline::Util::Query.new(request.body).parse
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Probe context)
|
||||||
# Returns shallow parsed query hash
|
# Returns shallow parsed query hash
|
||||||
# @note reads request.body - may nullify .input, .body data is memoized
|
# @note reads request.body - may nullify .input, .body data is memoized
|
||||||
# @return [Hash{String => Object}] query data
|
# @return [Hash{String => Object}] query data
|
||||||
|
@ -122,12 +183,14 @@ module Landline
|
||||||
Landline::Util::Query.new(request.body).parse_shallow
|
Landline::Util::Query.new(request.body).parse_shallow
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Probe context)
|
||||||
# Check if body is a JSON object
|
# Check if body is a JSON object
|
||||||
# @return [Boolean]
|
# @return [Boolean]
|
||||||
def json?
|
def json?
|
||||||
!!_verify_content_type('application/json')
|
!!_verify_content_type('application/json')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Probe context)
|
||||||
# Return parse JSON object
|
# Return parse JSON object
|
||||||
# @note reads request.input - may nullify request.body.
|
# @note reads request.input - may nullify request.body.
|
||||||
# @return [Object]
|
# @return [Object]
|
||||||
|
@ -135,24 +198,35 @@ module Landline
|
||||||
JSON.parse(request.input)
|
JSON.parse(request.input)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Probe context)
|
||||||
# Open a file relative to current filepath
|
# Open a file relative to current filepath
|
||||||
# @see File.open
|
# @see File.open
|
||||||
def file(path, mode = "r", *all, &block)
|
def file(path, mode = "r", *all, &block)
|
||||||
File.open("#{request.filepath}/#{path}", mode, *all, &block)
|
File.open("#{request.filepath}/#{path}", mode, *all, &block)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Probe context)
|
||||||
# Escape HTML entities
|
# Escape HTML entities
|
||||||
# @see Landline::Util.escape_html
|
# @see Landline::Util.escape_html
|
||||||
def escape_html(text)
|
def escape_html(text)
|
||||||
Landline::Util.escape_html(text)
|
Landline::Util.escape_html(text)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Probe context)
|
||||||
# Unescape HTML entities
|
# Unescape HTML entities
|
||||||
# @see Landline::Util.escape_html
|
# @see Landline::Util.escape_html
|
||||||
def unescape_html(text)
|
def unescape_html(text)
|
||||||
Landline::Util.unescape_html(text)
|
Landline::Util.unescape_html(text)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# (in Landline::Path context)
|
||||||
|
# Pass the requested environment to a different application
|
||||||
|
# @param application [#call] Rack application
|
||||||
|
# @return [Array(Integer, Hash{String => Object}, Object)] response
|
||||||
|
def call(application)
|
||||||
|
application.call(@origin.request.env)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def _verify_content_type(type)
|
def _verify_content_type(type)
|
||||||
|
|
|
@ -7,6 +7,7 @@ module Landline
|
||||||
module DSL
|
module DSL
|
||||||
# Common methods for template contexts
|
# Common methods for template contexts
|
||||||
module TemplateMethods
|
module TemplateMethods
|
||||||
|
# (in Landline::Template context)
|
||||||
# Import a template part
|
# Import a template part
|
||||||
# @param filepath [String, File] path to the file (or the file itself)
|
# @param filepath [String, File] path to the file (or the file itself)
|
||||||
# @return [String] compiled template
|
# @return [String] compiled template
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'securerandom'
|
||||||
|
|
||||||
|
module Landline
|
||||||
|
# Module for controlling session signing secrets
|
||||||
|
module Session
|
||||||
|
# Set hmac secret
|
||||||
|
# @param secret [String]
|
||||||
|
def self.hmac_secret=(secret)
|
||||||
|
@hmac_secret = secret
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get hmac secret
|
||||||
|
def self.hmac_secret
|
||||||
|
unless @hmac_secret or ENV['HMAC_SECRET']
|
||||||
|
warn <<~MSG
|
||||||
|
warn: hmac secret not supplied, using randomized one
|
||||||
|
warn: provide hmac secret with $HMAC_SECRET or Landline::Session.hmac_secret
|
||||||
|
MSG
|
||||||
|
end
|
||||||
|
@hmac_secret ||= ENV.fetch('HMAC_SECRET', SecureRandom.base64(80))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Class for representing session storage
|
||||||
|
class Session
|
||||||
|
def initialize(cookie, cookies_callback)
|
||||||
|
@data = if cookie
|
||||||
|
Landline::Util::JWT.from_string(
|
||||||
|
cookie,
|
||||||
|
Landline::Session.hmac_secret
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Landline::Util::JWT.new({})
|
||||||
|
end
|
||||||
|
@valid = !@data.nil?
|
||||||
|
@cookies_callback = cookies_callback
|
||||||
|
end
|
||||||
|
|
||||||
|
# Retrieve data from session storage
|
||||||
|
# @param key [String, Symbol] serializable key
|
||||||
|
def [](key)
|
||||||
|
raise StandardError, "session not valid" unless @valid
|
||||||
|
|
||||||
|
unless key.is_a? String or key.is_a? Symbol
|
||||||
|
raise StandardError, "key not serializable"
|
||||||
|
end
|
||||||
|
|
||||||
|
@data.data[key]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Set data to session storage
|
||||||
|
# @param key [String, Symbol] serializable key
|
||||||
|
# @param value [Object] serializable data
|
||||||
|
def []=(key, value)
|
||||||
|
raise StandardError, "session not valid" unless @valid
|
||||||
|
|
||||||
|
unless key.is_a? String or key.is_a? Symbol
|
||||||
|
raise StandardError, "key not serializable"
|
||||||
|
end
|
||||||
|
|
||||||
|
@data.data[key] = value
|
||||||
|
@cookies_callback.call(@data.make(Landline::Session.hmac_secret))
|
||||||
|
end
|
||||||
|
|
||||||
|
attr_reader :valid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module Landline
|
||||||
|
module DSL
|
||||||
|
module ProbeMethods
|
||||||
|
# TODO: If probe execution contexts are somehow shared between threads
|
||||||
|
# this could result in a session leakage through race condition.
|
||||||
|
|
||||||
|
# (in Landline::Probe context)
|
||||||
|
# Return session storage hash
|
||||||
|
# @return [Landline::Session::Session]
|
||||||
|
def session
|
||||||
|
return @session if @session
|
||||||
|
|
||||||
|
@session = Landline::Session::Session.new(
|
||||||
|
request.cookies.dig('session', 0)&.value,
|
||||||
|
proc do |value|
|
||||||
|
delete_cookie("session", value)
|
||||||
|
cookie("session", value)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
request.postprocessors.append(proc do
|
||||||
|
@session = nil
|
||||||
|
end)
|
||||||
|
@session
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -50,6 +50,7 @@ module Landline
|
||||||
# Contexts setup
|
# Contexts setup
|
||||||
context = self.class::Context.new(self)
|
context = self.class::Context.new(self)
|
||||||
context.instance_exec(&setup)
|
context.instance_exec(&setup)
|
||||||
|
# TODO: This isn't fine
|
||||||
@proccontext = self.class::ProcContext.new(self)
|
@proccontext = self.class::ProcContext.new(self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -132,18 +133,25 @@ module Landline
|
||||||
enqueue_postprocessors(request)
|
enqueue_postprocessors(request)
|
||||||
@children.each do |x|
|
@children.each do |x|
|
||||||
value = x.go(request)
|
value = x.go(request)
|
||||||
return value if value
|
return exit_stack(request, value) if value
|
||||||
end
|
end
|
||||||
value = index(request)
|
value = index(request)
|
||||||
return value if value
|
return exit_stack(request, value) if value
|
||||||
|
|
||||||
@bounce ? false : _die(404)
|
@bounce ? exit_stack(request) : _die(404)
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
_die(500, backtrace: [e.to_s] + e.backtrace)
|
_die(500, backtrace: [e.to_s] + e.backtrace)
|
||||||
ensure
|
ensure
|
||||||
@request = nil
|
@request = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Run enqueued postprocessors on navigation failure
|
||||||
|
# @param request [Landline::Request]
|
||||||
|
def exit_stack(request, response = nil)
|
||||||
|
request.run_postprocessors(response)
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
# Try to perform indexing on the path if possible
|
# Try to perform indexing on the path if possible
|
||||||
# @param request [Landline::Request]
|
# @param request [Landline::Request]
|
||||||
# @return [Boolean] true if indexing succeeded
|
# @return [Boolean] true if indexing succeeded
|
||||||
|
|
|
@ -21,6 +21,7 @@ module Landline
|
||||||
autoload :TRACE, "landline/probe/http_method"
|
autoload :TRACE, "landline/probe/http_method"
|
||||||
autoload :PATCH, "landline/probe/http_method"
|
autoload :PATCH, "landline/probe/http_method"
|
||||||
autoload :Serve, "landline/probe/serve_handler"
|
autoload :Serve, "landline/probe/serve_handler"
|
||||||
|
autoload :Link, "landline/probe/crosscall_handler"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Context that provides execution context for Probes.
|
# Context that provides execution context for Probes.
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative "../probe"
|
||||||
|
|
||||||
|
module Landline
|
||||||
|
module Handlers
|
||||||
|
# Probe that sends files from a location
|
||||||
|
class Link < Landline::Probe
|
||||||
|
# @param path [Object]
|
||||||
|
# @param parent [Landline::Node]
|
||||||
|
def initialize(path, application, parent:)
|
||||||
|
@application = application
|
||||||
|
super(path, parent: parent, filepath: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Method callback on successful request navigation.
|
||||||
|
# Sends request over to another rack app, stripping the part of the path that was not navigated
|
||||||
|
# @param request [Landline::Request]
|
||||||
|
# @return [Array(Integer, Host{String => Object}, Object)]
|
||||||
|
def process(request)
|
||||||
|
throw :finish, @application.call(request.env)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -21,10 +21,10 @@ module Landline
|
||||||
@param = {}
|
@param = {}
|
||||||
@splat = []
|
@splat = []
|
||||||
# Traversal route. Public and writable.
|
# Traversal route. Public and writable.
|
||||||
@path = URI.decode_www_form_component(env["PATH_INFO"].dup)
|
@path = URI.decode_www_form_component(env["PATH_INFO"])
|
||||||
# File serving path. Public and writable.
|
# File serving path. Public and writable.
|
||||||
@filepath = "/"
|
@filepath = "/"
|
||||||
# Encapsulates all rack variables. Should not be public.
|
# Encapsulates all rack variables. Is no longer private, but usually should not be used directly
|
||||||
@rack = init_rack_vars(env)
|
@rack = init_rack_vars(env)
|
||||||
# Internal navigation states. Private.
|
# Internal navigation states. Private.
|
||||||
@states = []
|
@states = []
|
||||||
|
@ -35,9 +35,10 @@ module Landline
|
||||||
# Run postprocessors
|
# Run postprocessors
|
||||||
# @param response [Landline::Response]
|
# @param response [Landline::Response]
|
||||||
def run_postprocessors(response)
|
def run_postprocessors(response)
|
||||||
@postprocessors.each do |postproc|
|
@postprocessors.reverse_each do |postproc|
|
||||||
postproc.call(self, response)
|
postproc.call(self, response)
|
||||||
end
|
end
|
||||||
|
@postprocessors = []
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns request body (if POST data exists)
|
# Returns request body (if POST data exists)
|
||||||
|
@ -64,10 +65,33 @@ module Landline
|
||||||
@path, @param, @splat, @filepath = @states.pop
|
@path, @param, @splat, @filepath = @states.pop
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Checks if response stream can be partially hijacked
|
||||||
|
def hijack?
|
||||||
|
@_original_env['rack.hijack?']
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns full hijack callback
|
||||||
|
def hijack
|
||||||
|
@_original_env['rack.hijack']
|
||||||
|
end
|
||||||
|
|
||||||
|
# Reconstructs rack env after modification
|
||||||
|
def env
|
||||||
|
path = @path
|
||||||
|
@_original_env.merge(reconstruct_headers)
|
||||||
|
.merge({
|
||||||
|
'PATH_INFO' => path,
|
||||||
|
'REQUEST_PATH' => path,
|
||||||
|
'QUERY_STRING' => query.query,
|
||||||
|
'REQUEST_URI' => "#{path}?#{query.query}"
|
||||||
|
})
|
||||||
|
.merge(reconstruct_cookie)
|
||||||
|
end
|
||||||
|
|
||||||
attr_reader :request_method, :script_name, :path_info, :server_name,
|
attr_reader :request_method, :script_name, :path_info, :server_name,
|
||||||
:server_port, :server_protocol, :headers, :param, :splat,
|
:server_port, :server_protocol, :headers, :param, :splat,
|
||||||
:postprocessors, :query, :cookies
|
:postprocessors, :cookies, :rack
|
||||||
attr_accessor :path, :filepath
|
attr_accessor :path, :filepath, :query
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
@ -119,7 +143,7 @@ module Landline
|
||||||
.freeze
|
.freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
# Iniitalize headers hash
|
# Initialize headers hash
|
||||||
# @param env [Hash]
|
# @param env [Hash]
|
||||||
# @return Hash
|
# @return Hash
|
||||||
def init_headers(env)
|
def init_headers(env)
|
||||||
|
@ -133,5 +157,30 @@ module Landline
|
||||||
x.downcase.gsub("_", "-") if x.is_a? String
|
x.downcase.gsub("_", "-") if x.is_a? String
|
||||||
end.freeze
|
end.freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Reconstruct headers
|
||||||
|
def reconstruct_headers
|
||||||
|
@headers.filter_map do |k, v|
|
||||||
|
next unless v
|
||||||
|
|
||||||
|
if !['content-type', 'content-length',
|
||||||
|
'remote-addr'].include?(k) && (k.is_a? String)
|
||||||
|
k = "http_#{k}"
|
||||||
|
end
|
||||||
|
k = k.upcase.gsub("-", "_")
|
||||||
|
[k, v]
|
||||||
|
end.to_h
|
||||||
|
end
|
||||||
|
|
||||||
|
# Reconstruct cookie string
|
||||||
|
def reconstruct_cookie
|
||||||
|
return {} if @cookies.empty?
|
||||||
|
|
||||||
|
{
|
||||||
|
"HTTP_COOKIE" => @cookies.map do |_, v|
|
||||||
|
v.finalize_short
|
||||||
|
end.join(";")
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -96,7 +96,7 @@ module Landline
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_accessor :status, :headers, :body
|
attr_accessor :status, :headers, :body, :cookies
|
||||||
|
|
||||||
# Ensure response correctness
|
# Ensure response correctness
|
||||||
# @param obj [String, Array, Landline::Response]
|
# @param obj [String, Array, Landline::Response]
|
||||||
|
|
|
@ -11,39 +11,64 @@ module Landline
|
||||||
# A specialized path that can be used directly as a Rack application.
|
# A specialized path that can be used directly as a Rack application.
|
||||||
class Server < Landline::Path
|
class Server < Landline::Path
|
||||||
Context = ServerContext
|
Context = ServerContext
|
||||||
|
|
||||||
# @param parent [Landline::Node, nil] Parent object to inherit properties to
|
# @param parent [Landline::Node, nil] Parent object to inherit properties to
|
||||||
# @param setup [#call] Setup block
|
# @param setup [#call] Setup block
|
||||||
def initialize(parent: nil, **args, &setup)
|
def initialize(passthrough = nil, parent: nil, **opts, &setup)
|
||||||
super("", parent: nil, **args, &setup)
|
super("", parent: nil, **opts, &setup)
|
||||||
return if parent
|
return if parent
|
||||||
|
|
||||||
|
@passthrough = passthrough
|
||||||
|
setup_properties(parent: nil, **opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Rack ingress point.
|
||||||
|
# @param env [Hash]
|
||||||
|
# @return [Array(Integer,Hash,Array)]
|
||||||
|
def call(env)
|
||||||
|
request = Landline::Request.new(env)
|
||||||
|
|
||||||
|
response = handle_jumps(request)
|
||||||
|
request.run_postprocessors(response)
|
||||||
|
resp = response.finalize
|
||||||
|
if resp[1][:"x-cascade"] and resp[0] == 404 and @passthrough
|
||||||
|
@passthrough.call(request.env)
|
||||||
|
else
|
||||||
|
resp
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Catch internal jumps
|
||||||
|
def handle_jumps(request)
|
||||||
|
response = Response.convert(catch(:finish) do
|
||||||
|
go(request)
|
||||||
|
end)
|
||||||
|
while response and
|
||||||
|
response.status == 307 and
|
||||||
|
response.headers.include? :"x-internal-jump"
|
||||||
|
response = Response.convert(catch(:finish) do
|
||||||
|
go(request)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
response
|
||||||
|
end
|
||||||
|
|
||||||
|
# Inititalization block for property setup
|
||||||
|
def setup_properties(*_args, **_opts)
|
||||||
{
|
{
|
||||||
"index" => [],
|
"index" => [],
|
||||||
"handle.default" => proc do |code, backtrace: nil|
|
"handle.default" => proc do |code, backtrace: nil|
|
||||||
page = Landline::Util.default_error_page(code, backtrace)
|
page = Landline::Util.default_error_page(code, backtrace)
|
||||||
headers = {
|
headers = {
|
||||||
"content-length": page.bytesize,
|
"content-length": page.bytesize,
|
||||||
"content-type": "text/html"
|
"content-type": "text/html",
|
||||||
|
"x-cascade": true
|
||||||
}
|
}
|
||||||
[headers, page]
|
[headers, page]
|
||||||
end,
|
end,
|
||||||
"path" => "/"
|
"path" => "/"
|
||||||
}.each { |k, v| @properties[k] = v unless @properties[k] }
|
}.each { |k, v| @properties[k] = v unless @properties[k] }
|
||||||
end
|
end
|
||||||
|
|
||||||
# Rack ingress point.
|
|
||||||
# This should not be called under any circumstances twice in the same application,
|
|
||||||
# although server nesting for the purpose of creating virtual hosts is allowed.
|
|
||||||
# @param env [Hash]
|
|
||||||
# @return [Array(Integer,Hash,Array)]
|
|
||||||
def call(env)
|
|
||||||
request = Landline::Request.new(env)
|
|
||||||
response = catch(:finish) do
|
|
||||||
go(request)
|
|
||||||
end
|
|
||||||
request.run_postprocessors(response)
|
|
||||||
Response.convert(response).finalize
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,10 +14,32 @@ module Landline
|
||||||
module Util
|
module Util
|
||||||
# JSON Web Token construction class
|
# JSON Web Token construction class
|
||||||
class JWT
|
class JWT
|
||||||
|
ALGO = {
|
||||||
|
"HS256" => proc do |data, secret|
|
||||||
|
Base64.urlsafe_encode64(
|
||||||
|
OpenSSL::HMAC.digest("SHA256", secret, data)
|
||||||
|
).gsub('=', '')
|
||||||
|
end,
|
||||||
|
"HS384" => proc do |data, secret|
|
||||||
|
Base64.urlsafe_encode64(
|
||||||
|
OpenSSL::HMAC.digest("SHA384", secret, data)
|
||||||
|
).gsub('=', '')
|
||||||
|
end,
|
||||||
|
"HS512" => proc do |data, secret|
|
||||||
|
Base64.urlsafe_encode64(
|
||||||
|
OpenSSL::HMAC.digest("SHA512", secret, data)
|
||||||
|
).gsub('=', '')
|
||||||
|
end
|
||||||
|
}.freeze
|
||||||
|
|
||||||
# Create a new JWT token wrapper
|
# Create a new JWT token wrapper
|
||||||
# @param data [Hash, Array] JSON-formattable data
|
# @param data [Hash, Array] JSON-formattable data
|
||||||
# @param halgo [String] Name of the hash algorithm to use
|
# @param halgo [String] Name of the hash algorithm to use
|
||||||
def initialize(data, halgo = "SHA256")
|
def initialize(data, halgo = "HS256")
|
||||||
|
unless ALGO.include? halgo
|
||||||
|
raise StandardError, "hash algorithm #{halgo} not supported"
|
||||||
|
end
|
||||||
|
|
||||||
@halgo = halgo
|
@halgo = halgo
|
||||||
@data = data
|
@data = data
|
||||||
end
|
end
|
||||||
|
@ -26,14 +48,13 @@ module Landline
|
||||||
# @param key [String]
|
# @param key [String]
|
||||||
# @return [String]
|
# @return [String]
|
||||||
def make(key)
|
def make(key)
|
||||||
|
jsonheader = {
|
||||||
|
"alg": @halgo,
|
||||||
|
"typ": "JWT"
|
||||||
|
}.to_json
|
||||||
jsondata = @data.to_json
|
jsondata = @data.to_json
|
||||||
[
|
data = "#{base64(jsonheader)}.#{base64(jsondata)}"
|
||||||
{
|
"#{data}.#{ALGO[@halgo].call(data, key)}"
|
||||||
"hash" => @halgo
|
|
||||||
}.to_json,
|
|
||||||
jsondata,
|
|
||||||
OpenSSL::HMAC.digest(@halgo, key, jsondata)
|
|
||||||
].map(&Base64.method(:strict_encode64)).map(&:strip).join "&"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Construct an object from string
|
# Construct an object from string
|
||||||
|
@ -41,14 +62,21 @@ module Landline
|
||||||
# @param key [String]
|
# @param key [String]
|
||||||
# @return [JWT, nil] returns nil if verification couldn't complete
|
# @return [JWT, nil] returns nil if verification couldn't complete
|
||||||
def self.from_string(input, key)
|
def self.from_string(input, key)
|
||||||
halgoj, dataj, sig = input.split("&").map(&Base64.method(:strict_decode64))
|
halgoj, dataj, sig = input.split(".")
|
||||||
halgo = JSON.parse(halgoj)["hash"]
|
halgo = JSON.parse(Base64.urlsafe_decode64(halgoj))["alg"]
|
||||||
return nil if OpenSSL::HMAC.digest(halgo, key, dataj) != sig
|
return nil unless ALGO.include? halgo
|
||||||
|
return nil if ALGO[halgo].call("#{halgoj}.#{dataj}", key) != sig
|
||||||
|
|
||||||
new(JSON.parse(dataj), halgo)
|
new(JSON.parse(Base64.urlsafe_decode64(dataj)), halgo)
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_accessor :data
|
attr_accessor :data
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def base64(data)
|
||||||
|
Base64.urlsafe_encode64(data).gsub("=", "")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1267,6 +1267,7 @@ module Landline
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
# Get MIME type by file extension
|
# Get MIME type by file extension
|
||||||
|
# @note This function does no checks on the file - simply renaming the file to a different extension will yield an invalid result. Do not use this to check uploaded files - preferably, use libmagic or proper mime type tools for Ruby.
|
||||||
# @param file [String] filename
|
# @param file [String] filename
|
||||||
# @return [String] MIME type, defaults to "application/octet-stream"
|
# @return [String] MIME type, defaults to "application/octet-stream"
|
||||||
def self.get_mime_type(file)
|
def self.get_mime_type(file)
|
||||||
|
|
|
@ -8,6 +8,8 @@ module Landline
|
||||||
# Query string parser
|
# Query string parser
|
||||||
class Query
|
class Query
|
||||||
include Landline::Util::ParserSorting
|
include Landline::Util::ParserSorting
|
||||||
|
attr_reader :query
|
||||||
|
|
||||||
# @param query [String]
|
# @param query [String]
|
||||||
def initialize(query)
|
def initialize(query)
|
||||||
@query = query
|
@query = query
|
||||||
|
|
Loading…
Reference in New Issue