From fd8bf97459218716578631b7304a3c4ca4e6eeee Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sun, 11 Apr 2021 10:44:53 -0400 Subject: [PATCH] Send decrypted upload response as chunked --- lib/wiki/content/upload.ex | 34 +++++++++++++++++++++ lib/wiki_web/controllers/page_controller.ex | 19 +++++++++--- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/lib/wiki/content/upload.ex b/lib/wiki/content/upload.ex index 33f9244..cfdb61b 100644 --- a/lib/wiki/content/upload.ex +++ b/lib/wiki/content/upload.ex @@ -98,6 +98,40 @@ defmodule Wiki.Content.Upload do :crypto.crypto_one_time(:aes_256_ctr, key, iv, encrypted_data, false) end + @spec decrypt_stream(t(), binary()) :: Enumerable.t() + def decrypt_stream(%__MODULE__{relative_path: filename, encryption_iv: iv}, key) do + start_fun = fn -> + state = :crypto.crypto_init(:aes_256_ctr, key, iv, false) + path = Path.join(upload_dir(), filename) + {:ok, input} = File.open(path, [:read, :binary]) + {state, input, false} + end + + next_fun = fn {state, input_dev, halt} -> + if halt do + {:halt, {state, input_dev}} + else + # why 4k? + {halt, data} = + case IO.binread(input_dev, 4096) do + :eof -> + {true, :crypto.crypto_final(state)} + + data -> + {false, :crypto.crypto_update(state, data)} + end + + {[data], {state, input_dev, halt}} + end + end + + after_fun = fn {state, input_dev} -> + File.close(input_dev) + end + + Stream.resource(start_fun, next_fun, after_fun) + end + @spec delete_file(upload :: t()) :: :ok def delete_file(%__MODULE__{relative_path: filename}) do File.rm(Path.join(upload_dir(), filename)) diff --git a/lib/wiki_web/controllers/page_controller.ex b/lib/wiki_web/controllers/page_controller.ex index d94d2f1..af54289 100644 --- a/lib/wiki_web/controllers/page_controller.ex +++ b/lib/wiki_web/controllers/page_controller.ex @@ -141,11 +141,22 @@ defmodule WikiWeb.PageController do key = get_session(conn, :content_encryption_key) key = Base.decode16!(key, case: :lower) - data = Upload.decrypt_content(upload, key) + conn = + conn + |> put_resp_header("content-type", upload.content_type) + |> send_chunked(200) - conn - |> put_resp_header("content-type", upload.content_type) - |> send_resp(200, data) + upload + |> Upload.decrypt_stream(key) + |> Enum.reduce_while(conn, fn decrypted_chunk, conn -> + case chunk(conn, decrypted_chunk) do + {:ok, conn} -> + {:cont, conn} + + {:error, :closed} -> + {:halt, {:halt, conn}} + end + end) end def delete_upload(conn, _params) do