@ -1,13 +1,53 @@
require 'mime-types'
require 'webrick'
require 'uri'
require 'pp'
module Hyde
# Branding and version
VERSION = " 0.5 (alpha) "
attr_reader :VERSION
VLINE = " <ADDRESS> \n Hyde/#{ Hyde :: VERSION } on WEBrick/ #{ WEBrick :: VERSION } (Ruby/ #{ RUBY_VERSION } / #{ RUBY_RELEASE_DATE } ) \n </ADDRESS> "
VLINE = " Hyde/ #{ Hyde :: VERSION } on WEBrick/ #{ WEBrick :: VERSION } (Ruby/ #{ RUBY_VERSION } / #{ RUBY_RELEASE_DATE } ) \n "
attr_reader :VLINE
def error_template ( errortext , backtrace )
<< HTMLEOF
< ! DOCTYPE HTML >
< html >
< head >
< title > #{WEBrick::HTMLUtils.escape(errortext)}</title>
< style > . header { background - color : #CC7878; padding: 0.5rem; border-bottom-width: 0.2rem; border-bottom-style: solid; border-bottom-color: #202222; overflow: auto;} .title { font-weight: bolder; font-size: 36px; margin: 0 0; text-shadow: 1px 1px 1px #202222, 2px 2px 2px #404444; float: left } body { margin: 0;
} . text { font - size 1 rem ; } . small { color : #7D7D7D; font-size: 12px;} .code { font-family: monospace; font-size: 0.7rem; } </style>
< / head>
< body >
< div class = " header " >
< p class = " title " > HYDE < / p>
< p style = " float: right " > < a href = " https://adastra7.net/git/yessiest/hyde " > Source code < / a>< / p >
< / div>
< div style = " padding: 0.5rem " >
< p class = " text " > #{WEBrick::HTMLUtils.escape(errortext)}</p>
< pre > < code class = " text code " >
#{WEBrick::HTMLUtils.escape(backtrace) or "\n\n\n"}
< / code>< / pre >
< hr / >
< p class = " small " > #{WEBrick::HTMLUtils.escape(VLINE)}</p>
< / div>
< / body>
< / html>
HTMLEOF
end
module_function :error_template
WEBrick :: HTTPResponse . class_exec do
attr_accessor :recent_backtrace
public
def set_backtrace ( backtrace )
@recent_backtrace = backtrace
end
def create_error_page
@body = Hyde . error_template ( @reason_phrase , @recent_backtrace )
end
end
class Server < WEBrick :: HTTPServer
def initialize ( config = { } , & setup )
@ -15,32 +55,29 @@ module Hyde
@hyde_pathspec = Hyde :: Pathspec . new " / " , & setup
self . mount_proc '/' do | req , res |
context = Hyde :: Context . new ( req . path , req , res )
while context and ( not context . exit_loop ) do
context . exit_loop = true
context = catch :controlled_exit do
@hyde_pathspec . _match ( context )
context
begin
while context and ( not context . exit_loop ) do
context . exit_loop = true
context = catch :controlled_exit do
@hyde_pathspec . _match ( context )
context
end
while postprocessor = context . queue_postprocess . shift do
postprocessor . call ( context )
end
end
while finalizer = context . queue_finalize . shift do
finalizer . call ( context )
end
rescue Exception = > e
puts e . message
puts e . backtrace
res . set_backtrace " #{ e . message } ( #{ e . class } ) \n #{ e . backtrace . join " \n " } "
res . status = 500
end
end
end
end
module ErrorPages
# 404 text
def error404 ( request , filepath )
request . response . status = 404
if request . handles . include? 404
request . response . body = request . handles [ 404 ] . call (
filepath
)
else
request . response . body = " <!DOCTYPE HTML PUBLIC \" -//W3C//DTD HTML 4.0//EN \" > \n <HTML> \n <HEAD><TITLE>File not found</TITLE></HEAD> \n <BODY> \n <H1>File not found</H1> \n #{ filepath } \n <HR> \n #{ Hyde :: VLINE } \n </BODY> \n </HTML> "
end
request . response [ " Content-Type " ] = 'text/html'
end
module_function :error404
end
# Interchangeable glob/regex/string pattern matching
module PatternMatching
@ -110,6 +147,19 @@ module Hyde
new_context . exit_loop = false
throw :controlled_exit , new_context
end
def die ( code , message = nil , backtrace = " " )
@current_context . response . status = code
@current_context . response . set_backtrace ( backtrace )
if not message then
message = WEBrick :: HTTPStatus :: StatusMessage [ code ]
end
if @current_context . codehandlers [ code ] then
@current_context . codehandlers [ code ] . call ( @current_context , message , backtrace )
else
@current_context . response . body = Hyde . error_template ( message , backtrace )
end
throw :controlled_exit , @current_context
end
end
# Request wrapper class
@ -119,24 +169,42 @@ module Hyde
@filepath = " "
@request = request
@response = response
@handles = { }
@indexlist = [ ]
@vars = { }
@codehandlers = { }
@queue_postprocess = [ ]
@queue_finalize = [ ]
@exit_loop = false
end
def self . rewrite ( pctx , newpath )
newctx = self . new ( newpath , pctx . request , pctx . response )
newctx = Context . new ( newpath , pctx . request , pctx . response )
newctx . vars = pctx . vars
newctx . queue_finalize = pctx . queue_finalize . clone
newctx . queue_postprocess = pctx . queue_postprocess . clone
return newctx
end
def enqueue_finalizer ( dup : false , & block )
if block_given? then
if dup or not @queue_finalize . include? block then
@queue_finalize . append ( block )
end
end
end
def enqueue_postprocessor ( & block )
if block_given? then
@queue_postprocess . append ( block )
end
end
attr_reader :request
attr_reader :response
attr_accessor :filepath
attr_accessor :queue_finalize
attr_accessor :queue_postprocess
attr_accessor :path
attr_accessor :handles
attr_accessor :indexlist
attr_accessor :filepath
attr_accessor :exit_loop
attr_accessor :vars
attr_accessor :codehandlers
end
# Context object with safe path encapsulation
@ -146,15 +214,18 @@ module Hyde
@filepath = request . filepath
@request = request . request
@response = request . response
@handles = request . handles
@indexlist = request . indexlist
@exit_loop = request . exit_loop
@vars = request . vars
@codehandlers = request . codehandlers
@queue_postprocess = request . queue_postprocess
@queue_finalize = request . queue_finalize
end
undef :path =
undef :filepath =
undef :handles =
undef :indexlist =
undef :queue_postprocess =
undef :queue_finalize =
end
# Handler classes
@ -185,6 +256,7 @@ module Hyde
def _match ( request )
return super if @block
if _match? request . path , request then
@current_context = request
match_path = _normalize_input ( request . path ) . match ( @path ) [ 0 ]
filepath = request . filepath + match_path
begin
@ -194,7 +266,7 @@ module Hyde
request . response . body = data
request . response [ " Content-Type " ] = mimetype
rescue Errno :: ENOENT
Hyde :: ErrorPages :: error404 request , request . request . path
die ( 404 )
end
end
end
@ -282,10 +354,10 @@ module Hyde
class Pathspec
include Hyde :: PatternMatching
include Hyde :: Handlers
include Hyde :: PublicContextControlMethods
def initialize ( path , root_path : nil , safe_regexp : true , & block )
_prep_path path , safe_regexp : safe_regexp
@chain = [ ]
@handles = { }
@root_override = root_path
@remap = false
self . instance_exec & block
@ -308,15 +380,23 @@ module Hyde
end
def index ( list )
@indexlist = list if list . kind_of? Array
end
def handle ( code , & block )
@handles [ code ] = block
@indexlist = [ list ] if list . kind_of? String
end
def preprocess ( & block )
@preprocessor = block
end
def _match ( request )
def postprocess ( & block )
@postprocessor = block
end
def finalize ( dup : false , & block )
@finalizer = block
@finalizer_dup = dup
end
def _match ( request )
@current_context = request
self . instance_exec request , & @preprocessor if @preprocessor
request . enqueue_postprocessor & @postprocessor if @preprocessor
request . enqueue_finalizer dup : @finalizer_dup , & @finalizer if @finalizer
if _match? request . path , request then
match_path = _normalize_input ( request . path ) . match ( @path )
next_path = match_path [ 0 ]
@ -329,8 +409,7 @@ module Hyde
else
request . filepath = request . filepath + next_path + " / "
end
# parameter overrides overlaying
@handles . each_pair { | k , v | request . handles [ k ] = v }
# redefine indexing parameters if they are defined for a pathspec
request . indexlist = @indexlist if @indexlist
# do directory indexing
if cut_path . match / ^ \/ ?$ / then
@ -346,10 +425,11 @@ module Hyde
next_pathspec = @chain . find { | x | x . _match? cut_path , request }
next_pathspec . _match request if next_pathspec
unless next_pathspec then
# throw 404 if nowhere to go
Hyde :: ErrorPages :: error404 request , request . request . path
# die and throw up if nowhere to go
die ( 404 )
end
end
@current_context = nil
end
end
end