     {:error, {"header parse error: #{error}", fname, 0}}
   end
 
-  @spec extract_header(IO.device(), [binary], boolean) :: {:ok, [binary]} | {:error, binary}
-
-  defp extract_header(device, lines, open?)
-
-  defp extract_header(device, lines, false) do
-    case IO.read(device, :line) do
-      "---\n" ->
-        extract_header(device, lines, true)
-
-      line when is_binary(line) ->
-        extract_header(device, lines, false)
-
-      :eof ->
+  @spec extract_header(binary, [binary], boolean) :: extract_ok | extract_err
+  defp extract_header(data, acc, open?)
+
+  defp extract_header(data, acc, false) do
+    case String.split(data, ~r/\r?\n/, parts: 2) do
+      ["---", rest] ->
+        extract_header(rest, acc, true)
+
+      [line, rest] when is_binary(line) ->
+        extract_header(rest, acc, false)
+
+      [_] ->
         {:error, "header not found"}
     end
   end
 
-  defp extract_header(device, lines, true) do
-    case IO.read(device, :line) do
-      "---\n" ->
-        {:ok, lines}
-
-      line when is_binary(line) ->
-        extract_header(device, [line | lines], true)
-
-      :eof ->
+  defp extract_header(data, acc, true) do
+    case String.split(data, ~r/\r?\n/, parts: 2) do
+      ["---", rest] ->
+        {:ok, acc, rest}
+
+      [line, rest] when is_binary(line) ->
+        extract_header(rest, [line | acc], true)
+
+      [_] ->
         {:error, "encountered unexpected end of file"}
     end
   end

  @type options :: [{atom, value_type}]
  @type value_type :: :string | :integer | :datetime | {:list, value_type}
  @type value :: binary | integer | [binary] | [integer]

  @doc """
  Reads lines from an I/O device `device` and extracts the header area into a

    value must have the same type, either `:string`, `:integer`, or `:datetime`.
    You cannot make a list of lists.
  """
  @spec parse_header(Serum.File.t(), options, [atom]) :: parse_result

  def parse_header(file, options, required \\ []) do
    case extract_header(file.in_data, [], false) do
      {:ok, header_lines, rest_data} ->
        key_strings = options |> Keyword.keys() |> Enum.map(&Atom.to_string/1)

        kv_list =
          header_lines
          |> Enum.map(&split_kv/1)
          |> Enum.filter(fn {k, _} -> k in key_strings end)

        with [] <- find_missing(kv_list, required),
             {:ok, new_kv} <- transform_values(kv_list, options, []) do
          {:ok, Map.new(new_kv), rest_data}
        else
          error -> handle_error(error, file.src)
        end

      error ->
        handle_error(error, file.src)
    end
  end

