|
5 | 5 |
|
6 | 6 | defmodule Scenic.Cache.Term do
|
7 | 7 | @moduledoc """
|
8 |
| - Simple functions to load a file, following the hashing rules. |
9 |
| - """ |
| 8 | + Helpers for loading file based Erlang terms directly into the cache. |
| 9 | +
|
| 10 | + Sometimes you want to pre-compile a big erlang term, such as a dictionary/map and |
| 11 | + distribute it to multiple applications. In this case you build your term, then use |
| 12 | + [`:erlang.term_to_binary/2`](http://erlang.org/doc/man/erlang.html#term_to_binary-2) |
| 13 | + to change it into binary data, which you write out to a file. Later you read the file |
| 14 | + and load the term. |
| 15 | +
|
| 16 | + This will be used in Scenic to store pre-compiled font metric data, such as character |
| 17 | + widths, kerning, etc. It is much better to compute that once and store it than to |
| 18 | + run the C code every time your program is run. |
| 19 | +
|
| 20 | + The `Scenic.Cache.Term` is very similar to [`Scenic.Cache.File`](Scenic.Cache.File.html) |
| 21 | + module, except that after the file has been loaded into memory, it also calls |
| 22 | + [`:erlang.binary_to_term/2`](http://erlang.org/doc/man/erlang.html#binary_to_term-2). |
| 23 | +
|
| 24 | + ## Where to store your static file assets |
| 25 | +
|
| 26 | + You can store your assets anywhere in your app's `priv/` directory. This directory is |
| 27 | + special in the sense that the Elixir build system knows to copy its contents into the |
| 28 | + correct final build location. How you organize your assets inside of `priv/` is up to you. |
| 29 | +
|
| 30 | + my_app/ |
| 31 | + priv/ |
| 32 | + static/ |
| 33 | + terms/ |
| 34 | + asset.jpg |
| 35 | +
|
| 36 | +
|
| 37 | + At compile time you need to build the actual path of your asset by combining |
| 38 | + the build directory with the partial path inside of `priv/` |
| 39 | +
|
| 40 | + Example |
| 41 | +
|
| 42 | + path = :code.priv_dir(:my_app) |
| 43 | + |> Path.join("/static/terms/asset.jpg") |
| 44 | +
|
| 45 | + You can do this at either compile time or runtime. |
| 46 | +
|
| 47 | + ## Security |
| 48 | +
|
| 49 | + A lesson learned the hard way is that static assets (fonts, images, etc) that your app |
| 50 | + loads out of storage can easily become attack vectors. |
| 51 | +
|
| 52 | + These formats are complicated! There is no guarantee (on any system) that a malformed |
| 53 | + asset will not cause an error in the C code that interprets it. Again - these are complicated |
| 54 | + and the renderers need to be fast... |
| 55 | +
|
| 56 | + The solution is to compute a SHA hash of these files during build-time of your |
| 57 | + and to store the result in your applications code itself. Then during run time, you |
| 58 | + compare then pre-computed hash against the run-time of the asset being loaded. |
| 59 | +
|
| 60 | + These scheme is much stronger when the application code itself is also signed and |
| 61 | + verified, but that is an exercise for the packaging tools. |
| 62 | +
|
| 63 | + When assets are loaded this way, the `@asset_hash` term is also used as the key in |
| 64 | + the cache. This has the additional benefit of allowing you to pre-compute |
| 65 | + the graph itself, using the correct keys for the correct assets. |
| 66 | +
|
| 67 | + Note that the hash is of the binary data in the file. |
| 68 | +
|
| 69 | + ## Full example |
10 | 70 |
|
| 71 | + defmodule MyApp.MyScene do |
| 72 | + use Scenic.Scene |
| 73 | +
|
| 74 | + # build the path to the static asset file (compile time) |
| 75 | + @asset_path :code.priv_dir(:my_app) |> Path.join("/static/terms/asset.jpg") |
| 76 | +
|
| 77 | + # pre-compute the hash (compile time) |
| 78 | + @asset_hash Scenic.Cache.Hash.file!( @asset_path, :sha ) |
| 79 | +
|
| 80 | + def init( _, _ ) { |
| 81 | + # load the asset into the cache (run time) |
| 82 | + Scenic.Cache.File.load(@asset_path, @asset_hash) |
| 83 | + ... |
| 84 | + end |
| 85 | +
|
| 86 | + end |
| 87 | + """ |
11 | 88 | alias Scenic.Cache
|
12 | 89 | alias Scenic.Cache.Hash
|
13 | 90 |
|
14 |
| - # import IEx |
15 |
| - |
16 | 91 | # --------------------------------------------------------
|
| 92 | + @doc """ |
| 93 | + Load a file-based term directly into the cache. |
| 94 | +
|
| 95 | + Parameters: |
| 96 | + * `path` - the path to the term file |
| 97 | + * `hash` - the pre-computed hash of the file |
| 98 | + * `opts` - a list of options. See below. |
| 99 | +
|
| 100 | + Options: |
| 101 | + * `hash` - format of the hash. Valid formats include `:sha, :sha224, :sha256, :sha384, :sha512, :ripemd160`. If the hash option is not set, it will use `:sha` by default. |
| 102 | + * `scope` - Explicitly set the scope of the term in the cache. |
| 103 | + * `safe` - prevents the creation of new atoms. [See erlang docs](http://erlang.org/doc/man/erlang.html#binary_to_term-2). |
| 104 | +
|
| 105 | + On success, returns |
| 106 | + `{:ok, cache_key}` |
| 107 | +
|
| 108 | + The key in the cache will be the hash of the file. |
| 109 | + """ |
| 110 | + |
17 | 111 | def load(path, hash, opts \\ [])
|
18 | 112 |
|
19 | 113 | # insecure loading. Loads file blindly even it is altered
|
@@ -56,6 +150,25 @@ defmodule Scenic.Cache.Term do
|
56 | 150 | end
|
57 | 151 |
|
58 | 152 | # --------------------------------------------------------
|
| 153 | + @doc """ |
| 154 | + Read a file-based term into memory. |
| 155 | +
|
| 156 | + The reason you would use this instead of File.read is to verify the data against |
| 157 | + a pre-computed hash. |
| 158 | +
|
| 159 | + Parameters: |
| 160 | + * `path` - the path to the term file |
| 161 | + * `hash` - the pre-computed hash of the file |
| 162 | + * `opts` - a list of options. See below. |
| 163 | +
|
| 164 | + Options: |
| 165 | + * `hash` - format of the hash. Valid formats include `:sha, :sha224, :sha256, :sha384, :sha512, :ripemd160`. If the hash option is not set, it will use `:sha` by default. |
| 166 | + * `safe` - prevents the creation of new atoms. [See erlang docs](http://erlang.org/doc/man/erlang.html#binary_to_term-2). |
| 167 | +
|
| 168 | + On success, returns |
| 169 | + `{:ok, term}` |
| 170 | + """ |
| 171 | + |
59 | 172 | def read(path, hash, opts \\ [])
|
60 | 173 |
|
61 | 174 | # insecure read
|
|
0 commit comments