diff options
Diffstat (limited to 'src/guff/handlers/guff-stuff.cr')
-rw-r--r-- | src/guff/handlers/guff-stuff.cr | 77 |
1 files changed, 77 insertions, 0 deletions
diff --git a/src/guff/handlers/guff-stuff.cr b/src/guff/handlers/guff-stuff.cr new file mode 100644 index 0000000..cc3ac6c --- /dev/null +++ b/src/guff/handlers/guff-stuff.cr @@ -0,0 +1,77 @@ +require "uri" +require "../handler" +require "../mime-type" +require "../views/html/page" + +class Guff::Handlers::GuffStuffHandler < Guff::Handler + def initialize(models) + super(models) + @digests = {} of String => String + end + + def call(context : HTTP::Server::Context) + path = context.request.path.not_nil! + + if matching_request?(context.request.method, path) + reply(context) + else + call_next(context) + end + end + + private def reply(context : HTTP::Server::Context) + # get expanded path to file + if path = expand_path(context.request.path.not_nil!) + # get file digest + file_digest = digest(path) + + # check for cache header + if context.request.headers["if-none-match"]? == file_digest + # cached, send 304 not modified + context.response.status_code = 304 + else + # not cached, set code and send headers + context.response.status_code = 200 + context.response.content_type = Guff::MimeType.mime_type(path) + context.response.content_length = File.size(path) + context.response.headers["etag"] = file_digest + + if context.request.method == "GET" + # send body + File.open(path) do |fh| + IO.copy(fh, context.response) + end + end + end + else + # file not found + context.response.status_code = 404 + end + end + + VALID_METHODS = %w{GET HEAD} + PATH_RE = %r{^/guff-stuff/} + + private def matching_request?(method, path) + VALID_METHODS.includes?(method) && PATH_RE.match(path) + end + + private def expand_path(req_path : String) : String? + # unescape path, check for nil byte + path = URI.unescape(req_path) + return nil if path.includes?('\0') + + # build absolute path + r = File.join( + @models.config["stuff"], + File.expand_path(path.gsub(PATH_RE, ""), "/") + ) + + # return path if file exists, or nil otherwise + File.file?(r) ? r : nil + end + + private def digest(path : String) : String + @digests[path] ||= OpenSSL::Digest.new("SHA1").file(path).hexdigest + end +end |