CodeSampleX

Пример

nimble_csv 1.3.0: Reassemble arbitrary CSV chunks and configure formula-prefix escaping in NimbleCSV 1.3.0

Проверенный пример — hex nimble_csv 1.3.0: Reassemble arbitrary CSV chunks and configure formula-prefix escaping in NimbleCSV 1.3.0. Контракт выполнен на…

sha256:d3b935a442e69a84c6664c75e2a07c5c6354c86243d8f2f795aa07d17eb5619c

Эта сеть предлагает одно: образец, который собирается. Она запустила его в песочнице и сохранила подписанную квитанцию. Она ничего не оценивает и ничего не гарантирует — собирается ли тот же код у вас, она не измеряла. Сколько различных ключей подписи подали пройденную квитанцию контракта. Один — только автор; больше одного — значит, кто-то ещё тоже собрал. Ключ создаётся сам и не имеет зарегистрированной личности, поэтому считаются ключи, а не люди. MIT-0

Свидетельства выполнения

Заявленное окружение и подписанные запуски разделены, чтобы вы точно видели, что этот образец запускал и где.

Основа свидетельства
Подписанный контракт пройден
Квитанции проверки
2
Ключи подписи, собравшие его
2
Заявленная среда elixir 1 linux x64 elixir 1 elixir mix

Среды запусков проверки

Окружение Контракт Этапы Запуск
elixir 1 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:PASS · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · hex@1
2026-08-17
elixir 1 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:PASS · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · hex@1
2026-08-18

Кейс

HOW
Цель
Reassemble arbitrary CSV chunks and configure formula-prefix escaping in NimbleCSV 1.3.0
Пакеты
Символы
  • NimbleCSV.define
  • NimbleCSV.RFC4180.to_line_stream
  • NimbleCSV.RFC4180.parse_stream
  • NimbleCSV.RFC4180.dump_to_iodata
  • NimbleCSV.Spreadsheet.to_line_stream
Окружение
elixir 1
Создан
2026-08-17T04:44:18Z

Контракт

  1. assert parsing the arbitrary chunks directly does not yield the complete expected table while RFC4180.to_line_stream reconstructs five complete lines and enables that parse
  2. assert Spreadsheet.to_line_stream reconstructs the tested UTF-16LE chunks into three tab-separated rows
  3. assert RFC4180.dump_to_iodata leaves the tested formula prefixes unchanged
  4. assert custom modules defined with escape_formula apply the configured quote or tab prefixes while preserving non-formula cells

Файлы

  • NOTES.md
  • csx.json
  • lib/nimble_csv_stream/parsers.ex
  • mix.exs
  • mix.lock
  • test/contract_test.exs
  • test/test_helper.exs

Скачать артефакт с исходным кодом (tar.gz)

Исходный код

NOTES.md
# NimbleCSV 1.3.0 chunking and formula prefixes

The contract measures arbitrary UTF-8 chunks with `RFC4180.to_line_stream/1`,
UTF-16LE chunks with `Spreadsheet.to_line_stream/1`, the default dumper's
verbatim formula prefixes, and two custom `escape_formula` mappings. It is
fully in memory and makes no assertion about earlier releases.
csx.json
{"buildCommand":["mix","deps.compile","--no-deps-check"],"case":{"believed":"NimbleCSV.parse_stream reassembles arbitrary byte chunks into complete lines, and the default dumper prefixes spreadsheet formula triggers.","caseId":"case:sha256:fc135ed00444df51c153cf9a5bee5e33086800b1241676f9adb384702dc773e2","contract":["assert parsing the arbitrary chunks directly does not yield the complete expected table while RFC4180.to_line_stream reconstructs five complete lines and enables that parse","assert Spreadsheet.to_line_stream reconstructs the tested UTF-16LE chunks into three tab-separated rows","assert RFC4180.dump_to_iodata leaves the tested formula prefixes unchanged","assert custom modules defined with escape_formula apply the configured quote or tab prefixes while preserving non-formula cells"],"goal":"Reassemble arbitrary CSV chunks and configure formula-prefix escaping in NimbleCSV 1.3.0","kind":"HOW","packages":["pkg:hex/nimble_csv@1.3.0"],"schemaVersion":1,"symbols":["NimbleCSV.define","NimbleCSV.RFC4180.to_line_stream","NimbleCSV.RFC4180.parse_stream","NimbleCSV.RFC4180.dump_to_iodata","NimbleCSV.Spreadsheet.to_line_stream"]},"contractCommand":["mix","test","--no-deps-check"],"environment":{"arch":"x64","ecosystem":"hex","executionContext":"elixir","language":"elixir","os":"linux","packageManager":"mix","runtime":"elixir","runtimeVersion":"1","schemaVersion":1},"license":"MIT-0","packages":["pkg:hex/nimble_csv@1.3.0"],"schemaVersion":1,"symbols":["NimbleCSV.define","NimbleCSV.RFC4180.to_line_stream","NimbleCSV.RFC4180.parse_stream","NimbleCSV.RFC4180.dump_to_iodata","NimbleCSV.Spreadsheet.to_line_stream"],"verifierAdapter":"hex@1"}
lib/nimble_csv_stream/parsers.ex
defmodule NimbleCsvStream.Parsers do
  @moduledoc """
  Defines CSV parsers and dumpers demonstrating stream line chunking and formula escaping.
  """

  # Custom dumper configured with formula escaping according to OWASP CSV injection guidelines.
  # Values starting with =, +, -, @, \t, or \r are prefixed with a single quote.
  NimbleCSV.define(NimbleCsvStream.FormulaSafeCSV,
    separator: ",",
    escape: "\"",
    line_separator: "\r\n",
    escape_formula: %{["@", "+", "-", "=", "\t", "\r"] => "'"}
  )

  # Custom dumper with custom tab-prefix formula escaping for spreadsheet calculation neutralisation.
  NimbleCSV.define(NimbleCsvStream.TabEscapedCSV,
    separator: ",",
    escape: "\"",
    line_separator: "\n",
    escape_formula: %{~w(@ +) => "'", ~w(- =) => "\t"}
  )
end
mix.exs
defmodule NimbleCsvStream.MixProject do
  use Mix.Project

  def project do
    [
      app: :nimble_csv_stream,
      version: "0.1.0",
      elixir: "~> 1.15",
      deps: deps()
    ]
  end

  def application, do: [extra_applications: []]

  defp deps do
    [{:nimble_csv, "1.3.0"}]
  end
end
mix.lock
%{
  "nimble_csv": {:hex, :nimble_csv, "1.3.0", "b7f998dc62b222bce9596e46f028c7a5af04cb5dde6df2ea197c583227c54971", [:mix], [], "hexpm", "41ccdc18f7c8f8bb06e84164fc51635321e80d5a3b450761c4997d620925d619"},
}
test/contract_test.exs
defmodule NimbleCsvStreamTest do
  use ExUnit.Case

  alias NimbleCSV.RFC4180, as: CSV
  alias NimbleCSV.Spreadsheet
  alias NimbleCsvStream.FormulaSafeCSV
  alias NimbleCsvStream.TabEscapedCSV

  test "parse_stream/1 requires to_line_stream/1 for arbitrary byte chunks, which reassembles lines lazily" do
    raw_chunks = [
      "header_a,header_b\n",
      "alice,25\nbo",
      "b,30\ncarol",
      ",35\ndavid,40\n"
    ]

    naive_parsed = raw_chunks |> CSV.parse_stream(skip_headers: false) |> Enum.to_list()

    refute naive_parsed == [
             ["header_a", "header_b"],
             ["alice", "25"],
             ["bob", "30"],
             ["carol", "35"],
             ["david", "40"]
           ]

    line_stream = CSV.to_line_stream(raw_chunks)

    assert Enum.to_list(line_stream) == [
             "header_a,header_b\n",
             "alice,25\n",
             "bob,30\n",
             "carol,35\n",
             "david,40\n"
           ]

    assert raw_chunks
           |> CSV.to_line_stream()
           |> CSV.parse_stream(skip_headers: false)
           |> Enum.to_list() == [
             ["header_a", "header_b"],
             ["alice", "25"],
             ["bob", "30"],
             ["carol", "35"],
             ["david", "40"]
           ]
  end

  test "to_line_stream/1 works with multibyte UTF-16LE encoded spreadsheet streams" do
    to_utf16le = fn string ->
      :unicode.characters_to_binary(string, :utf8, {:utf16, :little})
    end

    raw_chunks =
      [
        "col1\tcol2\n",
        "val1\tva",
        "l2\nval3\tval4\n"
      ]
      |> Stream.map(to_utf16le)

    parsed =
      raw_chunks
      |> Spreadsheet.to_line_stream()
      |> Spreadsheet.parse_stream(skip_headers: false)
      |> Enum.to_list()

    assert parsed == [
             ["col1", "col2"],
             ["val1", "val2"],
             ["val3", "val4"]
           ]
  end

  test "dump_to_iodata does not escape formulas by default, leaving injection prefixes verbatim" do
    rows = [
      ["name", "command"],
      ["user1", "=SUM(1,2)"],
      ["user2", "@cmd:run"],
      ["user3", "+12345"],
      ["user4", "-99999"]
    ]

    dumped = rows |> CSV.dump_to_iodata() |> IO.iodata_to_binary()

    assert dumped ==
             "name,command\r\n" <>
               "user1,\"=SUM(1,2)\"\r\n" <>
               "user2,@cmd:run\r\n" <>
               "user3,+12345\r\n" <>
               "user4,-99999\r\n"
  end

  test "custom parser with :escape_formula prepends prefix to formula cells and preserves non-formulas" do
    rows = [
      ["name", "formula_col", "clean_col"],
      ["user1", "=SUM(A1:B1)", "plain_text"],
      ["user2", "@calc", "100"],
      ["user3", "+44123456", "safe_val"],
      ["user4", "-500", "another_safe"]
    ]

    dumped_tab = rows |> TabEscapedCSV.dump_to_iodata() |> IO.iodata_to_binary()

    assert dumped_tab ==
             "name,formula_col,clean_col\n" <>
               "user1,\t=SUM(A1:B1),plain_text\n" <>
               "user2,'@calc,100\n" <>
               "user3,'+44123456,safe_val\n" <>
               "user4,\t-500,another_safe\n"

    dumped_owasp = rows |> FormulaSafeCSV.dump_to_iodata() |> IO.iodata_to_binary()

    assert dumped_owasp ==
             "name,formula_col,clean_col\r\n" <>
               "user1,\'=SUM(A1:B1),plain_text\r\n" <>
               "user2,\'@calc,100\r\n" <>
               "user3,\'+44123456,safe_val\r\n" <>
               "user4,\'-500,another_safe\r\n"
  end
end
test/test_helper.exs
ExUnit.start()

Исходный сидер

csx-seed