lib/matchers/archive.ex

defmodule Infer.Archive do
  @moduledoc """
  Archive type matchers based on the [magic number](https://en.wikipedia.org/wiki/Magic_number_(programming))
  """

  defdelegate epub?(binary), to: Infer.Book

  @doc """
  Takes the binary file contents as arguments. Returns `true` if it's a zip archive.

  See: https://en.wikipedia.org/wiki/List_of_file_signatures

  ## Examples

      iex> binary = File.read!("test/archives/sample.zip")
      iex> Infer.Archive.zip?(binary)
      true

  """
  @spec zip?(binary()) :: boolean()
  def zip?(<<0x50, 0x4B, 0x3, 0x4, _rest::binary>>), do: true
  def zip?(<<0x50, 0x4B, 0x5, 0x6, _rest::binary>>), do: true
  def zip?(<<0x50, 0x4B, 0x7, 0x8, _rest::binary>>), do: true
  def zip?(_binary), do: false

  @doc """
  Takes the binary file contents as arguments. Returns `true` if it's a tar archive.
  """
  @spec tar?(binary()) :: boolean()
  def tar?(<<_data::binary-size(257), 0x75, 0x73, 0x74, 0x61, 0x72, _rest::binary>>), do: true
  def tar?(_binary), do: false

  @doc """
  Takes the binary file contents as arguments. Returns `true` if it's a rar archive.
  """
  @spec rar?(binary()) :: boolean()
  def rar?(<<0x52, 0x61, 0x72, 0x21, 0x1A, 0x7, 0x0, _rest::binary>>), do: true
  def rar?(<<0x52, 0x61, 0x72, 0x21, 0x1A, 0x7, 0x1, _rest::binary>>), do: true
  def rar?(_binary), do: false

  @doc """
  Takes the binary file contents as arguments. Returns `true` if it's a gzip archive.
  """
  @spec gz?(binary()) :: boolean()
  def gz?(<<0x1F, 0x8B, 0x8, _rest::binary>>), do: true
  def gz?(_binary), do: false

  @doc """
  Takes the binary file contents as arguments. Returns `true` if it's a bzip archive.
  """
  @spec bz2?(binary()) :: boolean()
  def bz2?(<<0x42, 0x5A, 0x68, _rest::binary>>), do: true
  def bz2?(_binary), do: false

  @doc """
  Takes the binary file contents as arguments. Returns `true` if it's a 7z archive.
  """
  @spec sevenz?(binary()) :: boolean()
  def sevenz?(<<0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C, _rest::binary>>), do: true
  def sevenz?(_binary), do: false

  @doc """
  Takes the binary file contents as arguments. Returns `true` if it's a pdf.

  ## Examples

      iex> binary = File.read!("test/archives/sample.pdf")
      iex> Infer.Archive.pdf?(binary)
      true

  """
  @spec pdf?(binary()) :: boolean()
  def pdf?(<<0x25, 0x50, 0x44, 0x46, _rest::binary>>), do: true
  def pdf?(_binary), do: false

  @doc """
  Takes the binary file contents as arguments. Returns `true` if it's a swf.
  """
  @spec swf?(binary()) :: boolean()
  def swf?(<<0x43, 0x57, 0x53, _rest::binary>>), do: true
  def swf?(<<0x46, 0x57, 0x53, _rest::binary>>), do: true
  def swf?(_binary), do: false

  @doc """
  Takes the binary file contents as arguments. Returns `true` if it's a rtf.
  """
  @spec rtf?(binary()) :: boolean()
  def rtf?(<<0x7B, 0x5C, 0x72, 0x74, 0x66, _rest::binary>>), do: true
  def rtf?(_binary), do: false

  @doc """
  Takes the binary file contents as arguments. Returns `true` if it's a Nintendo NES ROM.
  """
  @spec nes?(binary()) :: boolean()
  def nes?(<<0x4E, 0x45, 0x53, 0x1A, _rest::binary>>), do: true
  def nes?(_binary), do: false

  @doc """
  Takes the binary file contents as arguments. Returns `true` if it's a Google Chrome Extension.
  """
  @spec crx?(binary()) :: boolean()
  def crx?(<<0x43, 0x72, 0x32, 0x34, _rest::binary>>), do: true
  def crx?(_binary), do: false

  @doc """
  Takes the binary file contents as arguments. Returns `true` if it's a CAB.
  """
  @spec cab?(binary()) :: boolean()
  def cab?(<<0x4D, 0x53, 0x43, 0x46, _rest::binary>>), do: true
  def cab?(<<0x49, 0x53, 0x63, 0x28, _rest::binary>>), do: true
  def cab?(_binary), do: false

  @doc """
  Takes the binary file contents as arguments. Returns `true` if it's a eot octet stream.
  """
  @spec eot?(binary()) :: boolean()
  def eot?(<<_header::binary-size(8), 0x01, 0x00, 0x00, _data::binary-size(24), 0x4C, 0x50, _rest::binary>>), do: true
  def eot?(<<_header::binary-size(8), 0x02, 0x00, 0x02, _data::binary-size(24), 0x4C, 0x50, _rest::binary>>), do: true
  def eot?(_binary), do: false

  @doc """
  Takes the binary file contents as arguments. Returns `true` if it's a postscript.
  """
  @spec ps?(binary()) :: boolean()
  def ps?(<<0x25, 0x21, _rest::binary>>), do: true
  def ps?(_binary), do: false

  @doc """
  Takes the binary file contents as arguments. Returns `true` if it's a xz archive.
  """
  @spec xz?(binary()) :: boolean()
  def xz?(<<0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00, _rest::binary>>), do: true
  def xz?(_binary), do: false

  @doc """
  Takes the binary file contents as arguments. Returns `true` if it's a sqlite3 database.


  ## Examples

      iex> binary = File.read!("test/archives/sample.db")
      iex> Infer.Archive.sqlite?(binary)
      true

  """
  @spec sqlite?(binary()) :: boolean()
  def sqlite?(<<0x53, 0x51, 0x4C, 0x69, _rest::binary>>), do: true
  def sqlite?(_binary), do: false

  @doc """
  Takes the binary file contents as arguments. Returns `true` if it's a deb archive.
  """
  @spec deb?(binary()) :: boolean()
  def deb?(
        <<0x21, 0x3C, 0x61, 0x72, 0x63, 0x68, 0x3E, 0x0A, 0x64, 0x65, 0x62, 0x69, 0x61, 0x6E, 0x2D, 0x62, 0x69, 0x6E, 0x61, 0x72, 0x79,
          _rest::binary>>
      ),
      do: true

  def deb?(_binary), do: false

  @doc """
  Takes the binary file contents as arguments. Returns `true` if it's a ar archive.
  """
  @spec ar?(binary()) :: boolean()
  def ar?(<<0x21, 0x3C, 0x61, 0x72, 0x63, 0x68, 0x3E, _rest::binary>>), do: true
  def ar?(_binary), do: false

  @doc """
  Takes the binary file contents as arguments. Returns `true` if it's a z archive.
  """
  @spec z?(binary()) :: boolean()
  def z?(<<0x1F, 0xA0, _rest::binary>>), do: true
  def z?(<<0x1F, 0x9D, _rest::binary>>), do: true
  def z?(_binary), do: false

  @doc """
  Takes the binary file contents as arguments. Returns `true` if it's a lzip archive.
  """
  @spec lz?(binary()) :: boolean()
  def lz?(<<0x4C, 0x5A, 0x49, 0x50, _rest::binary>>), do: true
  def lz?(_binary), do: false

  @doc """
  Takes the binary file contents as arguments. Returns `true` if it's a RPM.
  """
  @spec rpm?(binary()) :: boolean()
  def rpm?(<<0xED, 0xAB, 0xEE, 0xDB, _rest::binary>> = binary) when byte_size(binary) < 96, do: true
  def rpm?(_binary), do: false

  @doc """
  Takes the binary file contents as arguments. Returns `true` if it's a dcm archive.
  """
  @spec dcm?(binary()) :: boolean()
  def dcm?(<<_data::binary-size(128), 0x44, 0x49, 0x43, 0x4D, _rest::binary>>), do: true
  def dcm?(_binary), do: false

  @doc """
  Takes the binary file contents as arguments. Returns `true` if it's a Zstd archive.

    ## Examples

      iex> binary = File.read!("test/archives/sample.tar.zst")
      iex> Infer.Archive.zst?(binary)
      true

  """
  @spec zst?(binary()) :: boolean()
  def zst?(<<0x28, 0xB5, 0x2F, 0xFD, _rest::binary>>), do: true
  def zst?(_binary), do: false

  @doc """
  Takes the binary file contents as arguments. Returns `true` if it's a MSI windows installer archive.
  """
  @spec msi?(binary()) :: boolean()
  def msi?(<<0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1, _rest::binary>>), do: true
  def msi?(_binary), do: false
end