# `Storehouse.Check`
[🔗](https://github.com/hattoriai/storehouse/blob/v0.1.0/lib/storehouse/check.ex#L1)

The type of a file, from its bytes.

The browser's content type is a claim. `describe/3` 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 from their headers: a JPEG's turned as its EXIF
    orientation says, as browsers draw it. The claim does not matter: a
    PNG sent as `text/html` is a PNG.
  * PDF, from `%PDF-`.
  * MP4 video (`video/mp4`), from its `ftyp` box with an MP4 brand
    (`isom`, `mp41`, `mp42`, `avc1`, `M4V `, …), and WebM video
    (`video/webm`), from its EBML header with the `webm` document 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, else `application/zip`.
  * The older Office files (the OLE2 signature): `application/msword`,
    `application/vnd.ms-excel` or `application/vnd.ms-powerpoint` when 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 is `application/octet-stream`: `text/html`,
`application/xhtml+xml`, `image/svg+xml`, JavaScript, and any type that
is not in the list. Serve such a file as a download; its key ends in
`.bin` (`extension/1`).

`inline?/1` says which types a browser shows in the page (the images,
PDF and the videos): serve every other with `Content-Disposition:
attachment`, as `Storehouse.Local.Plug` does.

# `description`

```elixir
@type description() :: %{
  content_type: String.t(),
  bytes: non_neg_integer(),
  width: pos_integer() | nil,
  height: pos_integer() | nil
}
```

What `describe/3` finds.

# `claimed`

```elixir
@spec claimed(String.t() | nil) :: String.t()
```

The type a browser claims for a file, as the checked type it would be
if its bytes prove it: a type of the allow-list, else
`application/octet-stream`. A file uploaded straight to a store is
stored as this type, and refused when its bytes say otherwise.

## Examples

    iex> Storehouse.Check.claimed("image/PNG")
    "image/png"

    iex> Storehouse.Check.claimed("text/html")
    "application/octet-stream"

# `content_type`

```elixir
@spec content_type(String.t()) :: String.t()
```

Returns the checked content type for a file extension (as `extension/1`
gives it, and `.jpeg`), or `"application/octet-stream"`.

## Examples

    iex> Storehouse.Check.content_type(".PDF")
    "application/pdf"

    iex> Storehouse.Check.content_type(".svg")
    "application/octet-stream"

# `describe`

```elixir
@spec describe(Path.t(), String.t() | nil, keyword()) ::
  {: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. `claim` is the browser's type
(`"image/png; charset=binary"`, or `nil`).

## Options

  * `:max_bytes` - a larger file gives `{:error, :too_large}`.
  * `:max_pixels` - an image of more pixels (width × height) gives
    `{:error, :too_large}`: one a browser would struggle to draw.

# `describe_head`

```elixir
@spec describe_head(binary(), non_neg_integer(), String.t() | nil, keyword()) ::
  {:ok, description()} | {:error, term()}
```

As `describe/3`, of the start of a file (`head`, its first
256 KB or all of it, as `head_size/0` says) and its size in
`bytes`: a file that is in a store and not here, uploaded straight to
it.

# `extension`

```elixir
@spec extension(String.t()) :: String.t()
```

Returns the file extension of a checked content type. A type that is
not in the allow-list gives `".bin"`.

## Examples

    iex> Storehouse.Check.extension("image/jpeg")
    ".jpg"

    iex> Storehouse.Check.extension("text/html")
    ".bin"

# `head_size`

```elixir
@spec head_size() :: pos_integer()
```

How much of the start of a file `describe_head/4` reads, in bytes.

# `images`

```elixir
@spec images() :: [String.t()]
```

The content types of the images: PNG, JPEG, GIF and WebP.

# `inline?`

```elixir
@spec inline?(String.t()) :: boolean()
```

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> Storehouse.Check.inline?("video/webm")
    true

    iex> Storehouse.Check.inline?("text/plain")
    false

# `types`

```elixir
@spec types() :: [String.t()]
```

Every type `describe/3` gives, `application/octet-stream` included.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
