class Phlex::CSV
def __escape__(buffer, value, escape_csv_injection:, strip_whitespace:, escape_regex:)
def __escape__(buffer, value, escape_csv_injection:, strip_whitespace:, escape_regex:) case value ring mbol name to_s p_whitespace = value.strip ape_csv_injection lue.empty? er << value FORMULA_PREFIXES_MAP[value.getbyte(0)] e.gsub!('"', '""') er << '"\'' << value << '"' value.match?(escape_regex) e.gsub!('"', '""') er << '"' << value << '"' er << value not escaping CSV injection r << value not stripping whitespace ape_csv_injection _byte = value.getbyte(0) lue.empty? er << '""' FORMULA_PREFIXES_MAP[first_byte] er << '"\'' << value.gsub('"', '""') << '"' value.match?(escape_regex) er << '"' << value.gsub('"', '""') << '"' er << value not escaping CSV injection lue.empty? er << '""' value.match?(escape_regex) er << '"' << value.gsub('"', '""') << '"' er << value
def around_row(...)
def around_row(...) row_template(...) @row_appender.call end
def call(buffer = +"", context: nil, delimiter: self.delimiter)
def call(buffer = +"", context: nil, delimiter: self.delimiter) ensure_escape_csv_injection_configured! strip_whitespace = trim_whitespace? escape_csv_injection = escape_csv_injection? row_buffer = @_row_buffer headers = @_headers has_yielder = respond_to?(:yielder, true) first_row = true render_headers = render_headers? if delimiter.length != 1 raise Phlex::ArgumentError.new("Delimiter must be a single character") end if strip_whitespace escape_regex = /[\n"#{delimiter}]/ else escape_regex = /^\s|\s$|[\n"#{delimiter}]/ end if has_yielder warn <<~MESSAGE Custom yielders are deprecated in Phlex::CSV. Please replace your yielder with an `around_row` method. You should be able to just rename your yielder method and change `yield` to `super`. MESSAGE end row_appender = -> { row = row_buffer if first_row first_row = false i = 0 number_of_columns = row.length first_col = true while i < number_of_columns header, = row[i] headers[i] = header if render_headers if first_col first_col = false else buffer << delimiter end __escape__(buffer, header, escape_csv_injection:, strip_whitespace:, escape_regex:) end i += 1 end buffer << "\n" if render_headers end i = 0 number_of_columns = row.length first_col = true while i < number_of_columns header, value = row[i] unless headers[i] == header raise Phlex::RuntimeError.new("Header mismatch at index #{i}: expected #{headers[i]}, got #{header}.") end if first_col first_col = false else buffer << delimiter end __escape__(buffer, value, escape_csv_injection:, strip_whitespace:, escape_regex:) i += 1 end buffer << "\n" row_buffer.clear } if has_yielder each_item do |record| yielder(record) do |*a, **k| row_template(*a, **k) row_appender.call end end else @row_appender = row_appender each_item do |record| around_row(record) end end buffer end
def column(header = nil, value)
def column(header = nil, value) uffer << [header, value]
def content_type
def content_type "text/csv" end
def delimiter
def delimiter "," end
def each_item(&)
def each_item(&) ion.each(&)
def ensure_escape_csv_injection_configured!
def ensure_escape_csv_injection_configured! pe_csv_injection? == UNDEFINED <<~MESSAGE eed to define `escape_csv_injection?` in #{self.class.name}. njection is a security vulnerability where malicious spreadsheet lae are used to execute code or exfiltrate data when a CSV is opened spreadsheet program such as Microsoft Excel or Google Sheets. ore information, see https://owasp.org/www-community/attacks/CSV_Injection u’re sure this CSV will never be opened in a spreadsheet program, an *disable* CSV injection escapes: escape_csv_injection? = false is useful when using CSVs for byte-for-byte data exchange between secure systems. natively, you can *enable* CSV injection escapes at the cost of data integrity: escape_csv_injection? = true ing the CSV injection escapes will prefix with a single quote `'` any s that start with: `=`, `+`, `-`, `@`, `\\t`, `\\r` tunately, there is no one-size-fits-all solution to CSV injection. eed to decide based on your specific use case. E
def escape_csv_injection?
def escape_csv_injection? ED
def filename
def filename nil end
def initialize(collection)
def initialize(collection) @collection = collection @_row_buffer = [] @_headers = [] @_row_appender = nil end
def method_missing(method_name, ...)
def method_missing(method_name, ...) od_name == :row_template && respond_to?(:view_template) Deprecated: Use `row_template` instead of `view_template` in Phlex CSVs." lass.alias_method :row_template, :view_template emplate(...)
def render_headers?
def render_headers?
def respond_to_missing?(method_name, include_private)
def respond_to_missing?(method_name, include_private) _name == :row_template && respond_to?(:view_template)) || super
def trim_whitespace?
def trim_whitespace?