zf

zenflows testing
git clone https://s.sonu.ch/~srfsh/zf.git
Log | Files | Refs | Submodules | README | LICENSE

file.ex (2790B)


      1 defmodule Zenflows.Web.File do
      2 @moduledoc """
      3 Plug router that deals with file uploads and downloads (serve).
      4 """
      5 
      6 use Plug.Router
      7 
      8 alias Ecto.Multi
      9 alias Plug.{Conn, Conn.Utils}
     10 alias Zenflows.DB.Repo
     11 alias Zenflows.{File, Restroom}
     12 
     13 plug :match
     14 plug :dispatch
     15 
     16 post "/" do
     17 	with :ok <- check_content_type(conn),
     18 			{:ok, conn, hash} <- fetch_hash(conn) do
     19 		Multi.new()
     20 		|> Multi.put(:hash, hash)
     21 		|> Multi.run(:one, &File.Domain.one/2)
     22 		|> Multi.run(:check, fn _, %{one: %{size: size, hash: hash}} ->
     23 			{conn, bin} = read_multipart_body(conn, size)
     24 			with ^size <- byte_size(bin),
     25 					{:ok, hash_left} <- Base.url_decode64(hash, padding: false),
     26 					hash_right = :crypto.hash(:sha512, bin),
     27 					true <- Restroom.byte_equal?(hash_left, hash_right) do
     28 				{:ok, {conn, bin}}
     29 			else _ ->
     30 				{:error, conn}
     31 			end
     32 		end)
     33 		|> Multi.update(:update, fn %{one: file, check: {_, bin}} ->
     34 			Ecto.Changeset.change(file, bin: Base.encode64(bin))
     35 		end)
     36 		|> Repo.transaction()
     37 		|> case do
     38 			{:ok, %{check: {conn, _}}} -> send_resp(conn, 201, "Created")
     39 			{:error, :one, _, _} -> send_resp(conn, 404, "Not Found")
     40 			{:error, :check, conn, _} -> send_resp(conn, 422, "Unprocessable Content")
     41 			{:error, :update, _, %{check: {conn, _}}} -> send_resp(conn, 501, "Internal Server Error")
     42 		end
     43 	end
     44 end
     45 
     46 get "/:hash" do
     47 	hash = Map.fetch!(conn.path_params, "hash")
     48 	case File.Domain.one(hash: hash) do
     49 		{:ok, file} ->
     50 			conn
     51 			|> put_resp_content_type(file.mime_type)
     52 			|> send_resp(200, file.bin)
     53 		_ -> send_resp(conn, 404, "Not Found")
     54 	end
     55 end
     56 
     57 match _ do
     58 	send_resp(conn, 404, "Not Found")
     59 end
     60 
     61 @spec check_content_type(Conn.t()) :: :ok | Conn.t() | no_return()
     62 defp check_content_type(conn) do
     63 	with [hdr] <- get_req_header(conn, "content-type"),
     64 			{:ok, "multipart", "form-data", %{}} <- Utils.content_type(hdr) do
     65 		:ok
     66 	else _ ->
     67 		send_resp(conn, 415, "Unsupported Media Type")
     68 	end
     69 end
     70 
     71 @spec fetch_hash(Conn.t()) :: {:ok, Conn.t(), String.t()} | Conn.t() | no_return()
     72 defp fetch_hash(conn) do
     73 	with {:ok, hdrs, conn} <- read_part_headers(conn),
     74 		[val] <- for({"content-disposition", val} <- hdrs, do: val),
     75 			%{"name" => hash} <- Utils.params(val) do
     76 		{:ok, conn, hash}
     77 	else _ ->
     78 		send_resp(conn, 422, "Unprocessable Content")
     79 	end
     80 end
     81 
     82 @spec read_multipart_body(Conn.t(), non_neg_integer()) :: {Conn.t(), binary()}
     83 defp read_multipart_body(conn, size) do
     84 	read_multipart_body(conn, size, "")
     85 end
     86 
     87 @spec read_multipart_body(Conn.t(), non_neg_integer(), binary())
     88 	:: {Conn.t(), binary()}
     89 defp read_multipart_body(conn, size, acc) do
     90 	case read_part_body(conn, length: size, read_length: size) do
     91 		{:ok, body, conn} -> {conn, body}
     92 		{:more, read, conn} -> read_multipart_body(conn, size, <<acc::binary, read::binary>>)
     93 		{:done, conn} -> {conn, acc}
     94 	end
     95 end
     96 end