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. 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.
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 storage 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&Kotoba.Storage.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.