better path validation

This commit is contained in:
Moon Man 2024-12-02 07:37:54 -05:00
parent 2e04ebf470
commit 8ccd8e6586
2 changed files with 62 additions and 7 deletions

View File

@ -31,26 +31,42 @@ defmodule BallsPDS.Ecto.Schema.Object do
:path, :path,
:public :public
]) ])
|> validate_format(:path, ~r/^[^<>:"\\|\?\*\0]+$/, message: "Invalid characters") |> validate_path(:path)
|> validate_format(:path, ~r/^\/$|^\/.*[^\/]$/,
message: "Must have leading slash but no trailing slash"
)
|> validate_format(:path, ~r/(?<!\/)\/{1}(?!\/)/, message: "Multiple sequential slashes")
|> validate_format( |> validate_format(
:content_type, :content_type,
~r/^(application|audio|font|image|message|model|multipart|text|video)\/[\w\-\+\.]+(?:;\s*charset=[\w\-]+)?$/i, ~r/^(application|audio|font|image|message|model|multipart|text|video)\/[\w\-\+\.]+(?:;\s*charset=[\w\-]+)?$/i,
allow_nil: true allow_nil: true
) )
|> validate_format(:activitypub_type, ~r/[A-Z][a-zA-Z0-9]*/, allow_nil: true) |> validate_format(:activitypub_type, ~r/[A-Z][a-zA-Z0-9]*/, allow_nil: true)
|> validate_format(:storage_key, ~r/^[0-9a-f]{64}$/, |> validate_format(:storage_key, ~r/^[0-9a-f]+$/,
allow_nil: true, allow_nil: true,
message: "Invalid 256-bit hexadecimal string" message: "Invalid hexadecimal string"
) )
|> validate_inclusion(:public, [true, false]) |> validate_inclusion(:public, [true, false])
|> validate_number(:total_items, greater_than: -1, allow_nil: true) |> validate_number(:total_items, greater_than: -1, allow_nil: true)
|> unique_constraint(:path) |> unique_constraint(:path)
end end
defp validate_path(changeset, field) do
validate_change(changeset, field, fn _, path ->
case BallsPDS.Util.Util.is_valid_path?(path) do
:ok -> []
{:error, :invalid_char} ->
[{field, "Invalid characters"}]
{:error, :trailing_or_no_starting_slash} ->
[{field, "Trailing slash, or no starting slash"}]
{:error, :sequential_slashes} ->
[{field, "Sequential slashes"}]
{:error, :relative_path} ->
[{field, "Relative path"}]
end
end)
end
def get_by_path(path) when is_binary(path) do def get_by_path(path) when is_binary(path) do
query = query =
from(o in __MODULE__, from(o in __MODULE__,

View File

@ -0,0 +1,39 @@
defmodule BallsPDS.Util.Util do
@valid_path_chars_regex ~r/^[^<>:"\\|\?\*\0\s]+$/
@starting_trailing_slashes_regex ~r/^\/$|^\/.*[^\/]$/
@sequential_slashes_regex ~r/(?<!\/)\/{1}(?!\/)/
@relative_path_regex ~r{^(?!.*(/\./|/\.\./)).*$}
@doc """
A valid path inside the PDS.
"""
def is_valid_path?(path) when is_binary(path) do
with {:invalid_char, false} <- {:invalid_char, Regex.match?(@valid_path_chars_regex, path)},
{:trailing_or_no_starting_slash, false} <-
{:trailing_or_no_starting_slash, Regex.match?(@starting_trailing_slashes_regex, path)},
{:sequential_slashes, false} <-
{:sequential_slashes, Regex.match?(@sequential_slashes_regex, path)},
{:relative_path, false} <- {:relative_path, Regex.match?(@relative_path_regex, path)} do
:ok
else
{error, _} -> {:error, error}
end
end
@doc """
Every AP ID in an AP object should validate against this function.
"""
def is_valid_personal_ap_id(id) when is_binary(id) do
owner = Application.get_env(:balls_pds, :owner_ap_id)
service = Application.get_env(:balls_pds, :did_service)
prefix = "#{owner}?service=#{URI.encode(service)}&relativeRef="
with {:prefix, ^prefix <> relative_ref} <- {:prefix, id},
{:decode, relative_ref} <- {:decode, URI.decode(relative_ref)},
{:valid, :ok} <- {:valid, is_valid_path?(relative_ref)} do
true
else
_ -> false
end
end
end