changed
CHANGELOG.md
|
@@ -1,5 1,14 @@
|
1
1
|
# Changelog
|
2
2
|
|
3
|
## 3.1.0 (2023-09-24)
|
4
|
- Strict mode: Exception messages of _thrown_ exceptions are now redacted by default to avoid data unintentionally leaking into logs.
|
5
|
This behaviour change is not considered to be breaking backwards compatibility since source data presented in exeption messages is
|
6
|
not considered part of the `CSV` public API.
|
7
|
- Option to (un)redact exception messages [contributed in [#122](https://github.com/beatrichartz/csv/pull/124) by [@taylor-redden-papa](https://github.com/taylor-redden-papa)
|
8
|
|
9
|
## 3.0.5 (2022-12-03)
|
10
|
- Exclude dialyzer files from library package [contributed in [#121](https://github.com/beatrichartz/csv/pull/121) by [@milmazz](https://github.com/milmazz)
|
11
|
|
3
12
|
## 3.0.4 (2022-11-19)
|
4
13
|
- Add missing `escape_max_lines` to decode options typespec [closes #120](https://github.com/beatrichartz/csv/issues/120)
|
changed
README.md
|
@@ -6,13 6,13 @@
|
6
6
|
|
7
7
|
Add
|
8
8
|
```elixir
|
9
|
- {:csv, "~> 3.0"}
|
9
|
{:csv, "~> 3.1"}
|
10
10
|
```
|
11
11
|
to your deps in `mix.exs` like so:
|
12
12
|
|
13
13
|
```elixir
|
14
14
|
defp deps do
|
15
|
- [{:csv, "~> 3.0"}]
|
15
|
[{:csv, "~> 3.1"}]
|
16
16
|
end
|
17
17
|
```
|
18
18
|
|
|
@@ -142,6 142,12 @@ occur, aborting the operation:
|
142
142
|
File.stream!("data.csv") |> CSV.decode!
|
143
143
|
````
|
144
144
|
|
145
|
Redact data in exceptions that `decode!` throws to avoid potentially sensitive data showing up in logs:
|
146
|
```elixir
|
147
|
File.stream!("data.csv") |> CSV.decode!(redact_exception: true)
|
148
|
```
|
149
|
|
150
|
|
145
151
|
#### Options
|
146
152
|
|
147
153
|
For all available options [check the docs on `decode`](https://hexdocs.pm/csv/CSV.html#decode/2)
|
changed
hex_metadata.config
|
@@ -13,4 13,4 @@
|
13
13
|
{<<"links">>,[{<<"GitHub">>,<<"https://github.com/beatrichartz/csv">>}]}.
|
14
14
|
{<<"name">>,<<"csv">>}.
|
15
15
|
{<<"requirements">>,[]}.
|
16
|
- {<<"version">>,<<"3.0.5">>}.
|
16
|
{<<"version">>,<<"3.1.0">>}.
|
changed
lib/csv.ex
|
@@ -17,26 17,26 @@ defmodule CSV do
|
17
17
|
|
18
18
|
These are the options:
|
19
19
|
|
20
|
- * `:separator` – The separator token to use, defaults to `?,`.
|
20
|
* `:separator` – The separator token to use, defaults to `?,`.
|
21
21
|
Must be a codepoint (syntax: ? (your separator)).
|
22
|
- * `:escape_character` – The escape character token to use, defaults to `?"`.
|
22
|
* `:escape_character` – The escape character token to use, defaults to `?"`.
|
23
23
|
Must be a codepoint (syntax: ? (your escape character)).
|
24
|
- * `:escape_max_lines` – The number of lines an escape sequence is allowed
|
24
|
* `:escape_max_lines` – The number of lines an escape sequence is allowed
|
25
25
|
to span, defaults to 10.
|
26
|
- * `:field_transform` – A function with arity 1 that will get called with
|
26
|
* `:field_transform` – A function with arity 1 that will get called with
|
27
27
|
each field and can apply transformations. Defaults to identity function.
|
28
28
|
This function will get called for every field and therefore should return
|
29
29
|
quickly.
|
30
|
- * `:headers` – When set to `true`, will take the first row of
|
30
|
* `:headers` – When set to `true`, will take the first row of
|
31
31
|
the csv and use it as header values.
|
32
32
|
When set to a list, will use the given list as header values.
|
33
33
|
When set to `false` (default), will use no header values.
|
34
34
|
When set to anything but `false`, the resulting rows in the matrix will
|
35
35
|
be maps instead of lists.
|
36
|
- * `:validate_row_length` – When set to `true`, will take the first row of
|
36
|
* `:validate_row_length` – When set to `true`, will take the first row of
|
37
37
|
the csv or its headers and validate that following rows are of the same
|
38
38
|
length. Defaults to `false`.
|
39
|
- * `:unescape_formulas` – When set to `true`, will remove formula escaping
|
39
|
* `:unescape_formulas` – When set to `true`, will remove formula escaping
|
40
40
|
inserted to prevent [CSV Injection](https://owasp.org/www-community/attacks/CSV_Injection).
|
41
41
|
|
42
42
|
## Examples
|
|
@@ -181,6 181,10 @@ defmodule CSV do
|
181
181
|
length. Will raise an error if validation fails. Defaults to `false`.
|
182
182
|
* `:unescape_formulas` – When set to `true`, will remove formula escaping
|
183
183
|
inserted to prevent [CSV Injection](https://owasp.org/www-community/attacks/CSV_Injection).
|
184
|
* `:unredact_exceptions` – When set to `true`, will show csv data in
|
185
|
message output of exceptions thrown. Only use this when using CSV strict
|
186
|
mode in environments and situations where there is no concern with
|
187
|
exception message data leaking in logs. Defaults to `false`.
|
184
188
|
|
185
189
|
## Examples
|
186
190
|
|
|
@@ -280,22 284,24 @@ defmodule CSV do
|
280
284
|
|
281
285
|
"""
|
282
286
|
|
283
|
- @spec decode!(Enumerable.t(), [decode_options()]) :: Enumerable.t()
|
287
|
@spec decode!(Enumerable.t(), [decode_options() | {:unredact_exceptions, boolean()}]) ::
|
288
|
Enumerable.t()
|
284
289
|
def decode!(stream, options \\ []) do
|
285
290
|
stream |> Decoder.decode(options) |> raise_errors!(options)
|
286
291
|
end
|
287
292
|
|
288
293
|
defp raise_errors!(stream, options) do
|
289
294
|
escape_max_lines = options |> Keyword.get(:escape_max_lines, @escape_max_lines)
|
295
|
unredact_exceptions = options |> Keyword.get(:unredact_exceptions, false)
|
290
296
|
|
291
|
- stream |> Stream.map(&yield_or_raise!(&1, escape_max_lines))
|
297
|
stream |> Stream.map(&yield_or_raise!(&1, escape_max_lines, unredact_exceptions))
|
292
298
|
end
|
293
299
|
|
294
|
- defp yield_or_raise!({:error, mod, args}, _) do
|
295
|
- raise mod, args [mode: :strict]
|
300
|
defp yield_or_raise!({:error, mod, args}, _, unredact_exceptions) do
|
301
|
raise mod, args [mode: :strict, unredact: unredact_exceptions]
|
296
302
|
end
|
297
303
|
|
298
|
- defp yield_or_raise!({:ok, row}, _), do: row
|
304
|
defp yield_or_raise!({:ok, row}, _, _), do: row
|
299
305
|
|
300
306
|
defp inline_errors!(stream, options) do
|
301
307
|
escape_max_lines = options |> Keyword.get(:escape_max_lines, @escape_max_lines)
|
|
@@ -304,7 310,7 @@ defmodule CSV do
|
304
310
|
end
|
305
311
|
|
306
312
|
defp yield_or_inline!({:error, mod, args}, _) do
|
307
|
- {:error, mod.exception(args [mode: :normal]).message}
|
313
|
{:error, mod.exception(args [mode: :normal, unredact: true]).message}
|
308
314
|
end
|
309
315
|
|
310
316
|
defp yield_or_inline!(value, _), do: value
|
changed
lib/csv/exceptions.ex
|
@@ -1,6 1,6 @@
|
1
1
|
defmodule CSV.RowLengthError do
|
2
2
|
@moduledoc """
|
3
|
- Raised at runtime when the CSV has rows of variable length
|
3
|
Raised at runtime when the CSV has rows of variable length
|
4
4
|
and `validate_row_length` is set to true.
|
5
5
|
"""
|
6
6
|
|
|
@@ -29,7 29,8 @@ defmodule CSV.StrayEscapeCharacterError do
|
29
29
|
|
30
30
|
def exception(options) do
|
31
31
|
line = options |> Keyword.fetch!(:line)
|
32
|
- sequence = options |> Keyword.fetch!(:sequence)
|
32
|
unredact = options |> Keyword.get(:unredact, false)
|
33
|
sequence = options |> Keyword.fetch!(:sequence) |> get_sequence(unredact)
|
33
34
|
|
34
35
|
message =
|
35
36
|
"Stray escape character on line #{line}:" <>
|
|
@@ -41,6 42,9 @@ defmodule CSV.StrayEscapeCharacterError do
|
41
42
|
message: message
|
42
43
|
}
|
43
44
|
end
|
45
|
|
46
|
defp get_sequence(_, false), do: "**redacted**"
|
47
|
defp get_sequence(sequence, true), do: sequence
|
44
48
|
end
|
45
49
|
|
46
50
|
defmodule CSV.EscapeSequenceError do
|
|
@@ -54,7 58,11 @@ defmodule CSV.EscapeSequenceError do
|
54
58
|
def exception(options) do
|
55
59
|
line = options |> Keyword.fetch!(:line)
|
56
60
|
stream_halted = options |> Keyword.get(:stream_halted, false)
|
57
|
- escape_sequence_start = options |> Keyword.fetch!(:escape_sequence_start)
|
61
|
unredact = options |> Keyword.get(:unredact, false)
|
62
|
|
63
|
escape_sequence_start =
|
64
|
options |> Keyword.fetch!(:escape_sequence_start) |> get_sequence(unredact)
|
65
|
|
58
66
|
mode = options |> Keyword.fetch!(:mode)
|
59
67
|
|
60
68
|
continues_parsing =
|
|
@@ -86,4 94,7 @@ defmodule CSV.EscapeSequenceError do
|
86
94
|
message: message
|
87
95
|
}
|
88
96
|
end
|
97
|
|
98
|
defp get_sequence(_, false), do: "**redacted**"
|
99
|
defp get_sequence(sequence, true), do: sequence
|
89
100
|
end
|
changed
mix.exs
|
@@ -6,7 6,7 @@ defmodule CSV.Mixfile do
|
6
6
|
def project do
|
7
7
|
[
|
8
8
|
app: :csv,
|
9
|
- version: "3.0.5",
|
9
|
version: "3.1.0",
|
10
10
|
elixir: "~> 1.5",
|
11
11
|
deps: deps(),
|
12
12
|
package: package(),
|
|
@@ -46,7 46,7 @@ defmodule CSV.Mixfile do
|
46
46
|
|
47
47
|
defp deps do
|
48
48
|
[
|
49
|
- {:dialyxir, "~> 1.0", only: [:dev, :test], runtime: false},
|
49
|
{:dialyxir, "~> 1.3.0", only: [:dev, :test], runtime: false},
|
50
50
|
{:credo, "~> 1.6", only: [:dev, :test], runtime: false},
|
51
51
|
{:excoveralls, "~> 0.15", only: :test},
|
52
52
|
{:benchfella, ">= 0.0.0", only: :bench},
|