Server-side checks of an uploaded file, before it becomes an attachment node.
The browser's content type is a claim. describe/2 keeps only a type
that the bytes of the file prove, from a short allow-list of passive
types:
- PNG, JPEG, GIF and WebP images, from their signatures, with their
width and height when the header has them (a JPEG's turned as its
EXIF orientation says, as browsers draw it). The browser's claim does
not matter: a PNG sent as
text/htmlis a PNG. - PDF, from
%PDF-. - MP4 video (
video/mp4), from itsftypbox with an MP4 brand (isom,mp41,mp42,avc1,M4V, …), and WebM video (video/webm), from its EBML header with thewebmdocument type. A QuickTime (.mov), Matroska (.mkv) or audio-only (M4A) file is not in the list. - ZIP files (
PK\x03\x04): the Office Open XML and OpenDocument types when the browser claims one of them, elseapplication/zip. - The older Office files (the OLE2 signature):
application/msword,application/vnd.ms-excelorapplication/vnd.ms-powerpointwhen the browser claims one of them. text/plain, only when the browser claims it and the start of the file is valid UTF-8 with no NUL byte.
Every other file becomes application/octet-stream: text/html,
application/xhtml+xml, image/svg+xml, JavaScript, and any type that
is not in the list. Such a file renders as a download link, and its
storage key ends in .bin.
extension/1 gives the file extension of each checked type, and
inline?/1 tells which types a browser can show in the page (the
images, PDF and the videos). Serve every other file with
Content-Disposition: attachment.
store/2 makes an attachment of a file on the server, as an upload
does: for seeds, imports, or files an app makes itself.
The checks and the store are Storehouse's
(Storehouse.Check, Storehouse.store/2): these functions give them as
attachments.
Summary
Types
What describe/2 finds.
Functions
Returns a file name for display: control and format characters (bidi
controls included) removed, at most
200 characters, and "file" when nothing is left.
Returns the checked content type for a file extension (as extension/1
gives it), or "application/octet-stream".
Reads the file at path and returns its checked content type, its size
and, for an image, its width and height.
Returns the file extension of a checked content type. A type that is
not in the allow-list gives ".bin".
Returns true for a checked type that a browser can show in the page:
the images, PDF and the videos. Serve every other type with
Content-Disposition: attachment.
Builds an attachment node for a stored file.
Stores the file at path and returns its attachment node, as an upload
of the file would make it.
Types
@type description() :: %{ content_type: String.t(), bytes: non_neg_integer(), width: pos_integer() | nil, height: pos_integer() | nil }
What describe/2 finds.
Functions
Returns a file name for display: control and format characters (bidi
controls included) removed, at most
200 characters, and "file" when nothing is left.
Examples
iex> Kotoba.Attachments.clean_name("cat\u0000.png")
"cat.png"
iex> Kotoba.Attachments.clean_name("a\u202Egnp.exe")
"agnp.exe"
Returns the checked content type for a file extension (as extension/1
gives it), or "application/octet-stream".
Examples
iex> Kotoba.Attachments.content_type(".pdf")
"application/pdf"
iex> Kotoba.Attachments.content_type(".svg")
"application/octet-stream"
@spec describe(Path.t(), String.t() | nil) :: {:ok, description()} | {:error, term()}
Reads the file at path and returns its checked content type, its size
and, for an image, its width and height.
Returns the file extension of a checked content type. A type that is
not in the allow-list gives ".bin".
Examples
iex> Kotoba.Attachments.extension("image/jpeg")
".jpg"
iex> Kotoba.Attachments.extension("text/html")
".bin"
Returns true for a checked type that a browser can show in the page:
the images, PDF and the videos. Serve every other type with
Content-Disposition: attachment.
Examples
iex> Kotoba.Attachments.inline?("video/webm")
true
iex> Kotoba.Attachments.inline?("application/pdf")
true
iex> Kotoba.Attachments.inline?("text/plain")
false
@spec node(description(), keyword()) :: Kotoba.Nodes.Attachment.t()
Builds an attachment node for a stored file.
@spec store(Path.t(), keyword()) :: {:ok, Kotoba.Nodes.Attachment.t()} | {:error, term()}
Stores the file at path and returns its attachment node, as an upload
of the file would make it.
The file is checked with describe/2, stored with the storage adapter
under a new key, and its node checked. When the adapter stores the file
but its result does not make a valid attachment (a URL that is not a
safe link, say), the stored file is deleted. The file at path is left
where it is.
Put the node in a document with Kotoba.Content.from_markdown/2 and
its :attachments option:
{:ok, shot} = Kotoba.Attachments.store("priv/seeds/timeline.png")
Kotoba.Content.from_markdown(
"The timeline:",
attachments: %{"timeline" => shot}
)Options
:name- the file name. The default is the base name ofpath.:content_type- the claimed content type, as a browser sends it. The default is the type of the file's extension. Only the bytes of the file make an image an image (seedescribe/2).:storage- the store (aStorehouse.Adapter). The default isKotoba.Storage.adapter/0.:key- a function of the file name and the checked content type that returns a storage key. The default is&Storehouse.key/2.:preview- a function of the stored attachment andpaththat returns the URL of a preview image, ornil: as the:previewoption ofKotoba.Live.consume_uploads/4.