aboutsummaryrefslogtreecommitdiff
path: root/src/guff/handlers/guff-stuff.cr
diff options
context:
space:
mode:
Diffstat (limited to 'src/guff/handlers/guff-stuff.cr')
-rw-r--r--src/guff/handlers/guff-stuff.cr77
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