From 0b855b86dc69d9f43ecb9652ae52cdd3cca41271 Mon Sep 17 00:00:00 2001 From: neylix Date: Mon, 23 May 2022 11:19:39 +0200 Subject: [PATCH 1/9] Add Pathex dependency --- mix.exs | 3 ++- mix.lock | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index b4d78b09a..2078910a2 100644 --- a/mix.exs +++ b/mix.exs @@ -101,7 +101,8 @@ defmodule Archethic.MixProject do {:flow, "~> 1.0"}, {:broadway, "~> 1.0"}, {:knigge, "~> 1.4"}, - {:ex_json_schema, "~> 0.9.1", override: true} + {:ex_json_schema, "~> 0.9.1", override: true}, + {:pathex, "~> 2.0"} ] end diff --git a/mix.lock b/mix.lock index 6389714f6..7bd5304ca 100644 --- a/mix.lock +++ b/mix.lock @@ -48,6 +48,7 @@ "nimble_options": {:hex, :nimble_options, "0.3.7", "1e52dd7673d36138b1a5dede183b5d86dff175dc46d104a8e98e396b85b04670", [:mix], [], "hexpm", "2086907e6665c6b6579be54ef5001928df5231f355f71ed258f80a55e9f63633"}, "nimble_parsec": {:hex, :nimble_parsec, "1.2.0", "b44d75e2a6542dcb6acf5d71c32c74ca88960421b6874777f79153bbbbd7dccc", [:mix], [], "hexpm", "52b2871a7515a5ac49b00f214e4165a40724cf99798d8e4a65e4fd64ebd002c1"}, "observer_cli": {:hex, :observer_cli, "1.7.1", "c9ca1f623a3ef0158283a3c37cd7b7235bfe85927ad6e26396dd247e2057f5a1", [:mix, :rebar3], [{:recon, "~>2.5.1", [hex: :recon, repo: "hexpm", optional: false]}], "hexpm", "4ccafaaa2ce01b85ddd14591f4d5f6731b4e13b610a70fb841f0701178478280"}, + "pathex": {:hex, :pathex, "2.0.0", "b129359b22bf2005d5ed6bf41beb7aed64ca83d8510a0624a596a3fa23d34a1c", [:mix], [], "hexpm", "ffe038484ad8313fd91bec3ed51d0f821a4ba3eb6f2a1b14d9ab2685c5c3c876"}, "phoenix": {:hex, :phoenix, "1.5.13", "d4e0805ec0973bed80d67302631130fb47d75b1a0b7335a0b23c4432b6ce55ee", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1a7c4f1900e6e60bb60ae6680e48418e3f7c360d58bcb9f812487b6d0d281a0f"}, "phoenix_html": {:hex, :phoenix_html, "2.14.3", "51f720d0d543e4e157ff06b65de38e13303d5778a7919bcc696599e5934271b8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "efd697a7fff35a13eeeb6b43db884705cba353a1a41d127d118fda5f90c8e80f"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.4.0", "87990e68b60213d7487e65814046f9a2bed4a67886c943270125913499b3e5c3", [:mix], [{:ecto_psql_extras, "~> 0.4.1 or ~> 0.5", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.14.1 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.15.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.4.0 or ~> 0.5.0 or ~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "8d52149e58188e9e4497cc0d8900ab94d9b66f96998ec38c47c7a4f8f4f50e57"}, From 39ca4446fae8b7f9a0055d2b6c9f43edcc9214e3 Mon Sep 17 00:00:00 2001 From: neylix Date: Mon, 23 May 2022 16:00:58 +0200 Subject: [PATCH 2/9] Add new web_hosting API --- .../controllers/api/web_hosting_controller.ex | 145 ++++++++++++++++++ lib/archethic_web/router.ex | 2 + 2 files changed, 147 insertions(+) create mode 100644 lib/archethic_web/controllers/api/web_hosting_controller.ex diff --git a/lib/archethic_web/controllers/api/web_hosting_controller.ex b/lib/archethic_web/controllers/api/web_hosting_controller.ex new file mode 100644 index 000000000..0d39b4336 --- /dev/null +++ b/lib/archethic_web/controllers/api/web_hosting_controller.ex @@ -0,0 +1,145 @@ +defmodule ArchethicWeb.API.WebHostingController do + @moduledoc false + + use ArchethicWeb, :controller + + alias Archethic + + alias Archethic.TransactionChain.Transaction + alias Archethic.TransactionChain.TransactionData + + alias Archethic.Crypto + + use Pathex + + require Logger + + @spec web_hosting(Plug.Conn.t(), map) :: Plug.Conn.t() + def web_hosting(conn, %{"address" => address, "url_path" => url_path}) do + with {:ok, address} <- Base.decode16(address, case: :mixed), + true <- Crypto.valid_address?(address), + {:ok, %Transaction{address: last_address, data: %TransactionData{content: content}}} <- + Archethic.get_last_transaction(address), + {:ok, json_content} <- Jason.decode(content), + {:ok, file, mime_type} <- get_file(json_content, url_path), + {cached?, etag} <- get_cache(conn, last_address, url_path), + {:ok, file_content} <- get_file_content(file, cached?) do + conn = + conn + |> put_resp_content_type(mime_type, "utf-8") + |> put_resp_header("content-encoding", "gzip") + |> put_resp_header("cache-control", "public") + |> put_resp_header("etag", etag) + + if cached? do + send_resp(conn, 304, "") + else + send_resp(conn, 200, :zlib.gzip(file_content)) + end + else + # Base.decode16 || Crypto.valid_address + er when er in [:error, false] -> + send_resp(conn, 400, "Invalid address") + + # Jason.decode + {:error, %Jason.DecodeError{}} -> + send_resp(conn, 400, "Invalid transaction content") + + # Archethic.get_last_transaction + {:error, _} -> + send_resp(conn, 400, "Invalid address") + + {:file_not_found, url} -> + send_resp(conn, 404, "File #{url} does not exist") + + :encodage_error -> + send_resp(conn, 400, "Invalid file encodage") + + :file_error -> + send_resp(conn, 400, "Cannot find file content") + + _reason -> + send_resp(conn, 404, "Not Found") + end + end + + # API without path returns default index.html file + @spec get_file(json_content :: map(), url_path :: list()) :: + {:ok, map(), binary()} | {:file_not_found, binary()} + defp get_file(json_content, url_path) do + {json_path, url} = + case Enum.count(url_path) do + 0 -> + json_path = path("index.html") + url = "index.html" + {json_path, url} + + 1 -> + file_name = Enum.at(url_path, 0) + json_path = path(file_name) + {json_path, file_name} + + _ -> + json_path = Enum.reduce(url_path, fn value, acc -> path(acc) ~> path(value) end) + url = Path.join(url_path) + {json_path, url} + end + + case Pathex.view(json_content, json_path) do + {:ok, file} -> + {:ok, file, MIME.from_path(url)} + + :error -> + {:file_not_found, url} + end + end + + @spec get_cache(conn :: Plug.Conn.t(), last_address :: binary(), url_path :: list()) :: + {boolean(), binary()} + defp get_cache(conn, last_address, url_path) do + etag = + case Enum.empty?(url_path) do + true -> + Base.encode16(last_address, case: :lower) + + false -> + Base.encode16(last_address, case: :lower) <> Path.join(url_path) + end + + cached? = + case List.first(get_req_header(conn, "if-none-match")) do + got_etag when got_etag == etag -> + true + + _ -> + false + end + + {cached?, etag} + end + + @spec get_file_content(file :: map(), cached? :: boolean()) :: + {:ok, binary()} | :encodage_error | :file_error + defp get_file_content(_file, _cached? = true), do: {:ok, nil} + + defp get_file_content(%{"encodage" => encodage, "content" => content}, _cached = false) do + try do + file_content = + case encodage do + "base64" -> + Base.url_decode64!(content, padding: false) + + _ -> + content + end + + {:ok, file_content} + rescue + _ -> + :encodage_error + end + end + + defp get_file_content(%{"content" => content}, _cached = false), do: {:ok, content} + defp get_file_content(_, _), do: :file_error +end diff --git a/lib/archethic_web/router.ex b/lib/archethic_web/router.ex index 86f248915..94f5e736c 100644 --- a/lib/archethic_web/router.ex +++ b/lib/archethic_web/router.ex @@ -71,6 +71,8 @@ defmodule ArchethicWeb.Router do :last_transaction_content ) + get("/web_hosting/:address/*url_path", ArchethicWeb.API.WebHostingController, :web_hosting) + post("/origin_key", ArchethicWeb.API.OriginKeyController, :origin_key) post("/transaction", ArchethicWeb.API.TransactionController, :new) From 10acb08324b29a0e5c7b02df033d3669f232c4ba Mon Sep 17 00:00:00 2001 From: neylix Date: Mon, 23 May 2022 16:03:56 +0200 Subject: [PATCH 3/9] Add test file --- .../api/web_hosting_controller_test.exs | 252 ++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 test/archethic_web/controllers/api/web_hosting_controller_test.exs diff --git a/test/archethic_web/controllers/api/web_hosting_controller_test.exs b/test/archethic_web/controllers/api/web_hosting_controller_test.exs new file mode 100644 index 000000000..1b6a25d38 --- /dev/null +++ b/test/archethic_web/controllers/api/web_hosting_controller_test.exs @@ -0,0 +1,252 @@ +defmodule ArchethicWeb.API.WebHostingControllerTest do + use ArchethicCase + use ArchethicWeb.ConnCase + + alias Archethic.P2P + alias Archethic.P2P.Node + + alias Archethic.Crypto + + alias Archethic.P2P.Message.GetLastTransaction + + alias Archethic.TransactionChain.Transaction + alias Archethic.TransactionChain.TransactionData + + import Mox + + setup do + P2P.add_and_connect_node(%Node{ + ip: {127, 0, 0, 1}, + port: 3000, + first_public_key: Crypto.last_node_public_key(), + last_public_key: Crypto.last_node_public_key(), + network_patch: "AAA", + geo_patch: "AAA", + available?: true, + authorized?: true, + authorization_date: DateTime.utc_now() + }) + + :ok + end + + describe "web_hosting/2" do + test "should return Invalid address", %{conn: conn} do + MockClient + |> stub(:send_message, fn _, %GetLastTransaction{}, _ -> + {:error, :transaction_not_exists} + end) + + conn1 = get(conn, "/api/web_hosting/AZERTY") + conn2 = get(conn, "/api/web_hosting/0123456789") + + conn3 = + get( + conn, + "/api/web_hosting/0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba" + ) + + assert "Invalid address" = response(conn1, 400) + assert "Invalid address" = response(conn2, 400) + assert "Invalid address" = response(conn3, 400) + end + + test "should return Invalid transaction content", %{conn: conn} do + MockClient + |> stub(:send_message, fn _, %GetLastTransaction{}, _ -> + {:ok, + %Transaction{ + address: "0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba", + data: %TransactionData{content: "invalid"} + }} + end) + + conn = + get( + conn, + "/api/web_hosting/0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba" + ) + + assert "Invalid transaction content" = response(conn, 400) + end + end + + describe "get_file/2" do + setup do + content = """ + { + "index.html":{ + "encodage":"base64", + "content":"PGgxPkFyY2hldGhpYzwvaDE-" + }, + "folder":{ + "hello_world.html":{ + "encodage":"base64", + "content":"PGgxPkhlbGxvIHdvcmxkICE8L2gxPg" + } + } + } + """ + + MockClient + |> stub(:send_message, fn _, %GetLastTransaction{}, _ -> + {:ok, + %Transaction{ + address: "0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba", + data: %TransactionData{content: content} + }} + end) + + :ok + end + + test "should return file does not exist", %{conn: conn} do + conn = + get( + conn, + "/api/web_hosting/0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba/file.html" + ) + + assert "File file.html does not exist" = response(conn, 404) + end + + test "should return default index.html file", %{conn: conn} do + conn = + get( + conn, + "/api/web_hosting/0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba" + ) + + assert "

Archethic

" = response(conn, 200) |> :zlib.gunzip() + end + + test "should return selected file", %{conn: conn} do + conn = + get( + conn, + "/api/web_hosting/0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba/folder/hello_world.html" + ) + + assert "

Hello world !

" = response(conn, 200) |> :zlib.gunzip() + end + end + + describe "get_file_content/2" do + setup do + content = """ + { + "error.html":{ + "encodage":"base64", + "content":"4rdHFh%2BHYoS8oLdVvbUzEVqB8Lvm7kSPnuwF0AAABYQ%3D" + }, + "base64.js":{ + "encodage":"base64", + "content":"PGgxPkhlbGxvIHdvcmxkICE8L2gxPg" + }, + "unsupported.xml":{ + "encodage":"unsupported", + "content":"PGgxPkhlbGxvIHdvcmxkICE8L2gxPg" + }, + "raw.html":{ + "content":"PGgxPkhlbGxvIHdvcmxkICE8L2gxPg" + }, + "no_content.html":{ + "unsupported":"unsupported" + }, + "image.png":{ + "content":"image" + } + } + """ + + MockClient + |> stub(:send_message, fn _, %GetLastTransaction{}, _ -> + {:ok, + %Transaction{ + address: "0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba", + data: %TransactionData{content: content} + }} + end) + + :ok + end + + test "should return Invalid file encodage", %{conn: conn} do + conn = + get( + conn, + "/api/web_hosting/0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba/error.html" + ) + + assert "Invalid file encodage" = response(conn, 400) + end + + test "should return Cannot find file content", %{conn: conn} do + conn = + get( + conn, + "/api/web_hosting/0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba/no_content.html" + ) + + assert "Cannot find file content" = response(conn, 400) + end + + test "should return decoded file content", %{conn: conn} do + conn = + get( + conn, + "/api/web_hosting/0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba/base64.js" + ) + + assert "

Hello world !

" = response(conn, 200) |> :zlib.gunzip() + end + + test "should return raw file content", %{conn: conn} do + conn1 = + get( + conn, + "/api/web_hosting/0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba/raw.html" + ) + + conn2 = + get( + conn, + "/api/web_hosting/0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba/unsupported.xml" + ) + + assert "PGgxPkhlbGxvIHdvcmxkICE8L2gxPg" = response(conn1, 200) |> :zlib.gunzip() + assert "PGgxPkhlbGxvIHdvcmxkICE8L2gxPg" = response(conn2, 200) |> :zlib.gunzip() + end + + test "should return good file content-type", %{conn: conn} do + conn1 = + get( + conn, + "/api/web_hosting/0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba/raw.html" + ) + + conn2 = + get( + conn, + "/api/web_hosting/0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba/unsupported.xml" + ) + + conn3 = + get( + conn, + "/api/web_hosting/0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba/base64.js" + ) + + conn4 = + get( + conn, + "/api/web_hosting/0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba/image.png" + ) + + assert ["text/html; charset=utf-8"] = get_resp_header(conn1, "content-type") + assert ["text/xml; charset=utf-8"] = get_resp_header(conn2, "content-type") + assert ["text/javascript; charset=utf-8"] = get_resp_header(conn3, "content-type") + assert ["image/png; charset=utf-8"] = get_resp_header(conn4, "content-type") + end + end +end From 656ae4ce6e6ac2e6b657bf910daccc15ac580ea5 Mon Sep 17 00:00:00 2001 From: neylix Date: Mon, 23 May 2022 16:33:19 +0200 Subject: [PATCH 4/9] Add get_cache in test file --- .../api/web_hosting_controller_test.exs | 57 ++++++++++++++++++- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/test/archethic_web/controllers/api/web_hosting_controller_test.exs b/test/archethic_web/controllers/api/web_hosting_controller_test.exs index 1b6a25d38..a07087cd8 100644 --- a/test/archethic_web/controllers/api/web_hosting_controller_test.exs +++ b/test/archethic_web/controllers/api/web_hosting_controller_test.exs @@ -56,7 +56,9 @@ defmodule ArchethicWeb.API.WebHostingControllerTest do |> stub(:send_message, fn _, %GetLastTransaction{}, _ -> {:ok, %Transaction{ - address: "0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba", + address: + <<0, 0, 34, 84, 150, 163, 128, 213, 0, 92, 182, 131, 116, 233, 184, 180, 93, 126, 15, + 80, 90, 66, 248, 205, 97, 203, 212, 60, 54, 132, 197, 203, 172, 186>>, data: %TransactionData{content: "invalid"} }} end) @@ -92,7 +94,9 @@ defmodule ArchethicWeb.API.WebHostingControllerTest do |> stub(:send_message, fn _, %GetLastTransaction{}, _ -> {:ok, %Transaction{ - address: "0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba", + address: + <<0, 0, 34, 84, 150, 163, 128, 213, 0, 92, 182, 131, 116, 233, 184, 180, 93, 126, 15, + 80, 90, 66, 248, 205, 97, 203, 212, 60, 54, 132, 197, 203, 172, 186>>, data: %TransactionData{content: content} }} end) @@ -163,7 +167,9 @@ defmodule ArchethicWeb.API.WebHostingControllerTest do |> stub(:send_message, fn _, %GetLastTransaction{}, _ -> {:ok, %Transaction{ - address: "0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba", + address: + <<0, 0, 34, 84, 150, 163, 128, 213, 0, 92, 182, 131, 116, 233, 184, 180, 93, 126, 15, + 80, 90, 66, 248, 205, 97, 203, 212, 60, 54, 132, 197, 203, 172, 186>>, data: %TransactionData{content: content} }} end) @@ -249,4 +255,49 @@ defmodule ArchethicWeb.API.WebHostingControllerTest do assert ["image/png; charset=utf-8"] = get_resp_header(conn4, "content-type") end end + + describe "get_cache/3" do + test "should return 304 status if file is cached in browser", %{conn: conn} do + content = """ + { + "folder":{ + "hello_world.html":{ + "encodage":"base64", + "content":"PGgxPkhlbGxvIHdvcmxkICE8L2gxPg" + } + } + } + """ + + MockClient + |> stub(:send_message, fn _, %GetLastTransaction{}, _ -> + {:ok, + %Transaction{ + address: + <<0, 0, 34, 84, 150, 163, 128, 213, 0, 92, 182, 131, 116, 233, 184, 180, 93, 126, 15, + 80, 90, 66, 248, 205, 97, 203, 212, 60, 54, 132, 197, 203, 172, 186>>, + data: %TransactionData{content: content} + }} + end) + + conn1 = + get( + conn, + "/api/web_hosting/0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba/folder/hello_world.html" + ) + + etag = get_resp_header(conn1, "etag") |> Enum.at(0) + + assert "0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacbafolder/hello_world.html" = + etag + + conn2 = + get( + conn |> put_req_header("if-none-match", etag), + "/api/web_hosting/0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba/folder/hello_world.html" + ) + + assert "" = response(conn2, 304) + end + end end From 275116261d6e89dbb905a0c872763a6b37a72d7c Mon Sep 17 00:00:00 2001 From: neylix Date: Wed, 25 May 2022 15:47:34 +0200 Subject: [PATCH 5/9] Fix json path for more than 2 depth path --- .../controllers/api/web_hosting_controller.ex | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/archethic_web/controllers/api/web_hosting_controller.ex b/lib/archethic_web/controllers/api/web_hosting_controller.ex index 0d39b4336..3dcd90354 100644 --- a/lib/archethic_web/controllers/api/web_hosting_controller.ex +++ b/lib/archethic_web/controllers/api/web_hosting_controller.ex @@ -71,8 +71,7 @@ defmodule ArchethicWeb.API.WebHostingController do case Enum.count(url_path) do 0 -> json_path = path("index.html") - url = "index.html" - {json_path, url} + {json_path, "index.html"} 1 -> file_name = Enum.at(url_path, 0) @@ -80,7 +79,7 @@ defmodule ArchethicWeb.API.WebHostingController do {json_path, file_name} _ -> - json_path = Enum.reduce(url_path, fn value, acc -> path(acc) ~> path(value) end) + json_path = get_json_path(url_path) url = Path.join(url_path) {json_path, url} end @@ -94,6 +93,16 @@ defmodule ArchethicWeb.API.WebHostingController do end end + defp get_json_path(url_path) do + Enum.reduce(url_path, nil, fn value, acc -> + if acc == nil do + path(value) + else + acc ~> path(value) + end + end) + end + @spec get_cache(conn :: Plug.Conn.t(), last_address :: binary(), url_path :: list()) :: {boolean(), binary()} defp get_cache(conn, last_address, url_path) do From e4aca76fca2802b2a6c1b8d24796b3c1e7dd57a4 Mon Sep 17 00:00:00 2001 From: neylix Date: Wed, 25 May 2022 16:39:18 +0200 Subject: [PATCH 6/9] Update encodage to force base64 in JSON All file are encoded in base64 inside JSON content The encodage value is the encodage done before base64 (corresponding to brower content-encoding) --- .../controllers/api/web_hosting_controller.ex | 31 ++++++++++--------- .../api/web_hosting_controller_test.exs | 14 ++++----- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/lib/archethic_web/controllers/api/web_hosting_controller.ex b/lib/archethic_web/controllers/api/web_hosting_controller.ex index 3dcd90354..9de7de10c 100644 --- a/lib/archethic_web/controllers/api/web_hosting_controller.ex +++ b/lib/archethic_web/controllers/api/web_hosting_controller.ex @@ -23,7 +23,7 @@ defmodule ArchethicWeb.API.WebHostingController do {:ok, json_content} <- Jason.decode(content), {:ok, file, mime_type} <- get_file(json_content, url_path), {cached?, etag} <- get_cache(conn, last_address, url_path), - {:ok, file_content} <- get_file_content(file, cached?) do + {:ok, file_content, encodage} <- get_file_content(file, cached?) do conn = conn |> put_resp_content_type(mime_type, "utf-8") @@ -34,7 +34,13 @@ defmodule ArchethicWeb.API.WebHostingController do if cached? do send_resp(conn, 304, "") else - send_resp(conn, 200, :zlib.gzip(file_content)) + case encodage do + "gzip" -> + send_resp(conn, 200, file_content) + + _ -> + send_resp(conn, 200, :zlib.gzip(file_content)) + end end else # Base.decode16 || Crypto.valid_address @@ -127,28 +133,23 @@ defmodule ArchethicWeb.API.WebHostingController do {cached?, etag} end + # All file are encoded in base64 in JSON content @spec get_file_content(file :: map(), cached? :: boolean()) :: - {:ok, binary()} | :encodage_error | :file_error - defp get_file_content(_file, _cached? = true), do: {:ok, nil} + {:ok, binary(), binary() | nil} | :encodage_error | :file_error + defp get_file_content(_file, _cached? = true), do: {:ok, nil, nil} defp get_file_content(%{"encodage" => encodage, "content" => content}, _cached = false) do try do - file_content = - case encodage do - "base64" -> - Base.url_decode64!(content, padding: false) - - _ -> - content - end - - {:ok, file_content} + file_content = Base.url_decode64!(content, padding: false) + {:ok, file_content, encodage} rescue _ -> :encodage_error end end - defp get_file_content(%{"content" => content}, _cached = false), do: {:ok, content} + defp get_file_content(%{"content" => content}, _cached = false), + do: {:ok, Base.url_decode64!(content, padding: false), nil} + defp get_file_content(_, _), do: :file_error end diff --git a/test/archethic_web/controllers/api/web_hosting_controller_test.exs b/test/archethic_web/controllers/api/web_hosting_controller_test.exs index a07087cd8..0db725271 100644 --- a/test/archethic_web/controllers/api/web_hosting_controller_test.exs +++ b/test/archethic_web/controllers/api/web_hosting_controller_test.exs @@ -140,10 +140,10 @@ defmodule ArchethicWeb.API.WebHostingControllerTest do content = """ { "error.html":{ - "encodage":"base64", + "encodage":"gzip", "content":"4rdHFh%2BHYoS8oLdVvbUzEVqB8Lvm7kSPnuwF0AAABYQ%3D" }, - "base64.js":{ + "gzip.js":{ "encodage":"base64", "content":"PGgxPkhlbGxvIHdvcmxkICE8L2gxPg" }, @@ -158,7 +158,7 @@ defmodule ArchethicWeb.API.WebHostingControllerTest do "unsupported":"unsupported" }, "image.png":{ - "content":"image" + "content":"PGgxPkhlbGxvIHdvcmxkICE8L2gxPg" } } """ @@ -201,7 +201,7 @@ defmodule ArchethicWeb.API.WebHostingControllerTest do conn = get( conn, - "/api/web_hosting/0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba/base64.js" + "/api/web_hosting/0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba/gzip.js" ) assert "

Hello world !

" = response(conn, 200) |> :zlib.gunzip() @@ -220,8 +220,8 @@ defmodule ArchethicWeb.API.WebHostingControllerTest do "/api/web_hosting/0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba/unsupported.xml" ) - assert "PGgxPkhlbGxvIHdvcmxkICE8L2gxPg" = response(conn1, 200) |> :zlib.gunzip() - assert "PGgxPkhlbGxvIHdvcmxkICE8L2gxPg" = response(conn2, 200) |> :zlib.gunzip() + assert "

Hello world !

" = response(conn1, 200) |> :zlib.gunzip() + assert "

Hello world !

" = response(conn2, 200) |> :zlib.gunzip() end test "should return good file content-type", %{conn: conn} do @@ -240,7 +240,7 @@ defmodule ArchethicWeb.API.WebHostingControllerTest do conn3 = get( conn, - "/api/web_hosting/0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba/base64.js" + "/api/web_hosting/0000225496a380d5005cb68374e9b8b45d7e0f505a42f8cd61cbd43c3684c5cbacba/gzip.js" ) conn4 = From 9e1e87f1dfd18c31a173291061f6638630f54c08 Mon Sep 17 00:00:00 2001 From: neylix Date: Wed, 25 May 2022 17:35:10 +0200 Subject: [PATCH 7/9] Fix encodage error --- lib/archethic_web/controllers/api/web_hosting_controller.ex | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/archethic_web/controllers/api/web_hosting_controller.ex b/lib/archethic_web/controllers/api/web_hosting_controller.ex index 9de7de10c..3d249ae3c 100644 --- a/lib/archethic_web/controllers/api/web_hosting_controller.ex +++ b/lib/archethic_web/controllers/api/web_hosting_controller.ex @@ -138,9 +138,10 @@ defmodule ArchethicWeb.API.WebHostingController do {:ok, binary(), binary() | nil} | :encodage_error | :file_error defp get_file_content(_file, _cached? = true), do: {:ok, nil, nil} - defp get_file_content(%{"encodage" => encodage, "content" => content}, _cached = false) do + defp get_file_content(file = %{"content" => content}, _cached = false) do try do file_content = Base.url_decode64!(content, padding: false) + encodage = Map.get(file, "encodage") {:ok, file_content, encodage} rescue _ -> @@ -148,8 +149,5 @@ defmodule ArchethicWeb.API.WebHostingController do end end - defp get_file_content(%{"content" => content}, _cached = false), - do: {:ok, Base.url_decode64!(content, padding: false), nil} - defp get_file_content(_, _), do: :file_error end From 68ed1d9d4f16b77a531d0a9c9301b0dff305754e Mon Sep 17 00:00:00 2001 From: neylix Date: Mon, 30 May 2022 12:24:34 +0200 Subject: [PATCH 8/9] Without path return content if it's a single file --- .../controllers/api/web_hosting_controller.ex | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/lib/archethic_web/controllers/api/web_hosting_controller.ex b/lib/archethic_web/controllers/api/web_hosting_controller.ex index 3d249ae3c..257b52a87 100644 --- a/lib/archethic_web/controllers/api/web_hosting_controller.ex +++ b/lib/archethic_web/controllers/api/web_hosting_controller.ex @@ -70,14 +70,16 @@ defmodule ArchethicWeb.API.WebHostingController do end # API without path returns default index.html file + # or the only file if there is only one @spec get_file(json_content :: map(), url_path :: list()) :: {:ok, map(), binary()} | {:file_not_found, binary()} defp get_file(json_content, url_path) do {json_path, url} = case Enum.count(url_path) do 0 -> - json_path = path("index.html") - {json_path, "index.html"} + file_name = get_single_file_name(json_content) + json_path = path(file_name) + {json_path, file_name} 1 -> file_name = Enum.at(url_path, 0) @@ -109,6 +111,25 @@ defmodule ArchethicWeb.API.WebHostingController do end) end + defp get_single_file_name(json_content) do + keys = Map.keys(json_content) + + case Enum.count(keys) do + 1 -> + # Control if it is a file or a folder + file_name = Enum.at(keys, 0) + + if Map.get(json_content, file_name) |> Map.has_key?("content") do + file_name + else + "index.html" + end + + _ -> + "index.html" + end + end + @spec get_cache(conn :: Plug.Conn.t(), last_address :: binary(), url_path :: list()) :: {boolean(), binary()} defp get_cache(conn, last_address, url_path) do From e8d11818efe1a36ba5e46fa16c3a1cb8a7338c83 Mon Sep 17 00:00:00 2001 From: neylix Date: Mon, 30 May 2022 17:28:16 +0200 Subject: [PATCH 9/9] Format content in explorer --- lib/archethic_web/views/explorer_view.ex | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/archethic_web/views/explorer_view.ex b/lib/archethic_web/views/explorer_view.ex index 62b972dee..3e62ce9c7 100644 --- a/lib/archethic_web/views/explorer_view.ex +++ b/lib/archethic_web/views/explorer_view.ex @@ -210,6 +210,10 @@ defmodule ArchethicWeb.ExplorerView do Jason.Formatter.pretty_print_to_iodata(content) end + def format_transaction_content(:hosting, content) do + Jason.Formatter.pretty_print_to_iodata(content) + end + def format_transaction_content(_, content), do: content defp format_origin_shared_secrets_content(family, keys) do