トラック
/
Elixir
Elixir
/
演習
/
ファイルスニッファー
ファイルスニッファー

ファイルスニッファー

学習演習

はじめに

バイナリ

Elixirには、バイナリデータを扱うためのエレガントな構文があります。それは、ビット列を扱うために用意された<<>>という特殊形式で、すでに見てきたとおりです。

バイナリ型は、ビット列型を特殊化した型です。ビット列はどのような長さ(任意のビット数)にすることもできますが、バイナリはビット数を8で割り切れる場合のものです。つまり、バイナリを扱うときは、バイト(8ビット)という単位で考えることがよくあります。1バイトで0から255までの整数を表せます。バイトの値は、16進数の0x00 - 0xFFで扱うのが一般的です。

バイナリリテラルは、ビット列の特殊形式<<>>を使って定義します。バイナリリテラルを定義するときには、整数リテラルと文字列リテラルを使えます。255より大きい整数はオーバーフローし、整数の最後の8ビットだけが使われます。デフォルトでは、値に::binary修飾子が適用されます。バイナリは<>/2演算子で連結できます。

<<255>> == <<0xFF>>
# Overflowing bits are truncated
<<256>> == <<0>>
<<2, 4, 6, 8, 10, 12, 14, 16>> == <<0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x10>>

ヌルバイト は<<0>>の別名です。

バイナリデータのパターンマッチング

パターンマッチングはバイナリにも拡張されていて、リストの場合と同じように、バイナリデータの一部分にパターンマッチできます。

# Ignore the first 8 bytes, match and bind the remaining to `body`
<<_::binary-size(8), body::binary>>

他の種類のパターンマッチングと同様に、これを関数の定義で使うと、複数の関数節から選ぶときのマッチングに利用できます。

説明

ユーザーがファイルをサーバーにアップロードして、ほかのユーザーと共有できるプロジェクトに取り組んでいます。アップロードされたファイルがそのメディアタイプと一致するかを検証する関数を書くことになりました。調べてみると、ファイルの最初の数バイトは一般にそのファイルの種類に固有で、一種のシグネチャのようになっていることがわかりました。

参考として、次の表を見てください。

ファイルの種類 一般的な拡張子 メディアタイプ バイナリの「シグネチャ」
ELF "exe" "application/octet-stream" 0x7F, 0x45, 0x4C, 0x46
BMP "bmp" "image/bmp" 0x42, 0x4D
PNG "png" "image/png" 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A
JPG "jpg" "image/jpg" 0xFF, 0xD8, 0xFF
GIF "gif" "image/gif" 0x47, 0x49, 0x46

1. 拡張子から期待されるメディアタイプを返す

type_from_extension/1関数を実装してください。ファイルの拡張子(文字列)を受け取り、メディアタイプ(文字列)を返します。拡張子が期待されるものと一致しない場合はnilを返します。

FileSniffer.type_from_extension("exe")
# => "application/octet-stream"

FileSniffer.type_from_extension("txt")
# => nil

2. バイナリファイルから期待されるメディアタイプを返す

type_from_binary/1関数を実装してください。ファイル(バイナリ)を受け取り、メディアタイプ(文字列)を返します。ファイルのシグネチャが期待されるものと一致しない場合はnilを返します。

file = File.read!("application.exe")
FileSniffer.type_from_binary(file)
# => "application/octet-stream"

file = File.read!("example.txt")
FileSniffer.type_from_binary(file)
# => nil

ファイルをバイナリとして読み込む必要はありません。それはすでに行われており、テストから引数として渡されると想定してください。

3. 拡張子とバイナリファイルから、ファイルが期待される種類と一致することを検証する

verify/2関数を実装してください。ファイル(バイナリ)と拡張子(文字列)を受け取り、:okまたは:errorのタプルを返します。

file = File.read!("application.exe")

FileSniffer.verify(file, "exe")
# => {:ok, "application/octet-stream"}

FileSniffer.verify(file, "png")
# => {:error, "Warning, file format and file extension do not match."}
GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
Elixir Exercism

ファイルスニッファーを始める準備はできましたか?

Exercismに登録すれば、58個のコンセプト168個の演習、そして本物の人間によるメンタリングとともに、Elixirを学んでマスターできます。すべて無料です。