feat: datasheet upload and auto-retrieve
- store datasheet PDFs on the server - download PDF automatically when given a link
This commit is contained in:
@@ -1,29 +1,79 @@
|
||||
defmodule ComponentsElixirWeb.FileController do
|
||||
use ComponentsElixirWeb, :controller
|
||||
|
||||
def show(conn, %{"filename" => filename}) do
|
||||
# Security: only allow alphanumeric, dashes, underscores, and dots
|
||||
if String.match?(filename, ~r/^[a-zA-Z0-9_\-\.]+$/) do
|
||||
uploads_dir = Application.get_env(:components_elixir, :uploads_dir)
|
||||
file_path = Path.join([uploads_dir, "images", filename])
|
||||
def show(conn, %{"filename" => encoded_filename}) do
|
||||
case decode_and_validate_filename(encoded_filename) do
|
||||
{:ok, filename} ->
|
||||
uploads_dir = Application.get_env(:components_elixir, :uploads_dir)
|
||||
file_path = Path.join([uploads_dir, "images", filename])
|
||||
|
||||
if File.exists?(file_path) do
|
||||
# Get the file's MIME type
|
||||
mime_type = get_mime_type(filename)
|
||||
if File.exists?(file_path) do
|
||||
# Get the file's MIME type
|
||||
mime_type = get_mime_type(filename)
|
||||
|
||||
conn
|
||||
|> put_resp_content_type(mime_type)
|
||||
|> put_resp_header("cache-control", "public, max-age=86400") # Cache for 1 day
|
||||
|> send_file(200, file_path)
|
||||
else
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> text("File not found")
|
||||
end
|
||||
|
||||
{:error, reason} ->
|
||||
conn
|
||||
|> put_resp_content_type(mime_type)
|
||||
|> put_resp_header("cache-control", "public, max-age=86400") # Cache for 1 day
|
||||
|> send_file(200, file_path)
|
||||
|> put_status(:bad_request)
|
||||
|> text(reason)
|
||||
end
|
||||
end
|
||||
|
||||
def show_datasheet(conn, %{"filename" => encoded_filename}) do
|
||||
case decode_and_validate_filename(encoded_filename) do
|
||||
{:ok, filename} ->
|
||||
uploads_dir = Application.get_env(:components_elixir, :uploads_dir)
|
||||
file_path = Path.join([uploads_dir, "datasheets", filename])
|
||||
|
||||
if File.exists?(file_path) do
|
||||
# Get the file's MIME type
|
||||
mime_type = get_mime_type(filename)
|
||||
|
||||
conn
|
||||
|> put_resp_content_type(mime_type)
|
||||
|> put_resp_header("cache-control", "public, max-age=86400") # Cache for 1 day
|
||||
|> put_resp_header("content-disposition", "inline; filename=\"#{filename}\"")
|
||||
|> send_file(200, file_path)
|
||||
else
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> text("File not found")
|
||||
end
|
||||
|
||||
{:error, reason} ->
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> text(reason)
|
||||
end
|
||||
end
|
||||
|
||||
defp decode_and_validate_filename(encoded_filename) do
|
||||
try do
|
||||
# URL decode the filename
|
||||
decoded_filename = URI.decode(encoded_filename)
|
||||
|
||||
# Security validation: prevent directory traversal and only allow safe characters
|
||||
# Allow letters, numbers, spaces, dots, dashes, underscores, parentheses, and basic punctuation
|
||||
if String.match?(decoded_filename, ~r/^[a-zA-Z0-9\s_\-\.\(\)\[\]]+$/) and
|
||||
not String.contains?(decoded_filename, "..") and
|
||||
not String.starts_with?(decoded_filename, "/") and
|
||||
not String.contains?(decoded_filename, "\\") do
|
||||
{:ok, decoded_filename}
|
||||
else
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|> text("File not found")
|
||||
{:error, "Invalid filename: contains unsafe characters"}
|
||||
end
|
||||
else
|
||||
conn
|
||||
|> put_status(:bad_request)
|
||||
|> text("Invalid filename")
|
||||
rescue
|
||||
_ ->
|
||||
{:error, "Invalid filename: cannot decode"}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -34,6 +84,7 @@ defmodule ComponentsElixirWeb.FileController do
|
||||
".png" -> "image/png"
|
||||
".gif" -> "image/gif"
|
||||
".webp" -> "image/webp"
|
||||
".pdf" -> "application/pdf"
|
||||
_ -> "application/octet-stream"
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user