factor gemini file retreival logic out of gemini modlule
this also fixes a critical bug causing false 'bad request' responses
This commit is contained in:
parent
1411eef0a4
commit
45bcbec0d8
|
@ -0,0 +1,38 @@
|
|||
defmodule Egalaxyd.FileRetrieval do
|
||||
@moduledoc "Logic for getting file contents from disk"
|
||||
|
||||
def retrieve_file_contents(path) do
|
||||
case normalized_path(path) do
|
||||
{:error, error} -> {:error, error}
|
||||
{:ok, canonical_path} ->
|
||||
case File.read(canonical_path) do
|
||||
{:ok, file_contents} ->
|
||||
{:ok, file_contents, MIME.from_path(canonical_path)}
|
||||
_ -> {:error, :unknown_error}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@doc """
|
||||
Given a filesystem path, converts to canonical response path form by rewriting directories.
|
||||
If the file cannot be found or stat'd, returns {:error, error}; otherwise returns {:ok, canonical_path}
|
||||
"""
|
||||
defp normalized_path(path) do
|
||||
# Make sure the path is an absolute path to a file in the current working directory
|
||||
# TODO: Adjust file base path via config option
|
||||
prefixed_path = if String.starts_with?(path, File.cwd!), do: path, else: (File.cwd! <> path)
|
||||
|
||||
case File.stat(prefixed_path) do
|
||||
{:error, error} -> {:error, error}
|
||||
{:ok, %File.Stat{type: :directory}} ->
|
||||
# This is a directory so first try to rewrite with a trailing slash, then to index.gmi
|
||||
# TODO: Directory listening on fallback?
|
||||
suffix = (if String.last(prefixed_path) != "/", do: "/", else: "") <> "index.gmi"
|
||||
normalized_path(prefixed_path <> suffix)
|
||||
{:ok, _} ->
|
||||
# This is an existing file
|
||||
{:ok, prefixed_path}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -41,7 +41,7 @@ defmodule Geminex.RequestParser do
|
|||
trimmed = String.trim(uri)
|
||||
|
||||
if String.length(trimmed) > 1024 do
|
||||
response "59 bad request\r\n"
|
||||
response "59 bad request: Request too long\r\n"
|
||||
end
|
||||
|
||||
case String.trim(uri) |> URI.new() do
|
||||
|
@ -56,9 +56,11 @@ defmodule Geminex.RequestParser do
|
|||
response("53 protocol refused\r\n") # this is a gemini server!
|
||||
request_references_forbidden_secrets?(req.path) ->
|
||||
# Settings disallow requesting secrets and the path seems to reference them
|
||||
Logger.notice("Client requested forbidden secret")
|
||||
response("59 bad request\r\n")
|
||||
request_attempts_to_escape?(req.path) ->
|
||||
# Settings disallow directory backtracking and the request path seems to attempt this
|
||||
Logger.notice("Client requested directory backtrack")
|
||||
response("59 bad request\r\n")
|
||||
true ->
|
||||
# todo: mime typing
|
||||
|
@ -101,26 +103,17 @@ defmodule Geminex.RequestParser do
|
|||
|
||||
@doc "Check request to ensure directory traversal is not attempted, if the setting is enabled"
|
||||
defp request_attempts_to_escape?(path) do
|
||||
(not ConfigSentinel.getopt(:allow_updir)) && String.match?(path, ~r/../)
|
||||
(not ConfigSentinel.getopt(:allow_updir)) && String.match?(path, ~r/\.\./)
|
||||
end
|
||||
|
||||
defp retrieve_file_contents(path) do
|
||||
# first, check if the file exists and/or is a directory
|
||||
case File.stat(File.cwd! <> path) do
|
||||
case Egalaxyd.FileRetrieval.retrieve_file_contents(path) do
|
||||
{:error, :enoent} -> {:error, "51 not found\r\n"}
|
||||
{:error, :enametoolong} -> {:error, "59 bad request\r\n"}
|
||||
{:error, _} -> {:error, "50 internal server error\r\n"}
|
||||
{:ok, %File.Stat{type: :directory}} ->
|
||||
# retry with index.gmi
|
||||
# TODO: handle non-unix file paths
|
||||
# TODO: Directory listing on fallback?
|
||||
retrieve_file_contents(path <> (if String.last(path) != "/", do: "/", else: "") <> "index.gmi")
|
||||
# TODO: check file permissions
|
||||
{:ok, _} ->
|
||||
case File.read(File.cwd! <> path) do
|
||||
{:ok, content} -> {:ok, content, MIME.from_path(File.cwd! <> path)}
|
||||
_ -> {:error, "50 internal server error\r\n"}
|
||||
end
|
||||
{:error, :enametoolong} -> {:error, "59 bad request: Request too long\r\n"}
|
||||
{:error, other_error} ->
|
||||
Logger.error other_error
|
||||
{:error, "50 internal server error\r\n"}
|
||||
{:ok, file_contents, mime_type} -> {:ok, file_contents, mime_type}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue