mirror of
https://github.com/spiral-project/ihatemoney.git
synced 2025-04-28 17:32:38 +02:00
escape csv formulae
This is only needed for unsecure spreadsheet applications (hi Google Docs and MS Excel) that load formulae by default. See https://owasp.org/www-community/attacks/CSV_Injection for some mitigation explanation. This is not complete, but it should be OK for now.
This commit is contained in:
parent
8b9370088f
commit
042b33aeb2
2 changed files with 47 additions and 1 deletions
|
@ -596,6 +596,38 @@ class ExportTestCase(IhatemoneyTestCase):
|
|||
set(line.split(",")), set(received_lines[i].strip("\r").split(","))
|
||||
)
|
||||
|
||||
def test_export_escape_formulae(self):
|
||||
self.post_project("raclette", default_currency="EUR")
|
||||
|
||||
# add participants
|
||||
self.client.post("/raclette/members/add", data={"name": "zorglub"})
|
||||
|
||||
# create bills
|
||||
self.client.post(
|
||||
"/raclette/add",
|
||||
data={
|
||||
"date": "2016-12-31",
|
||||
"what": "=COS(36)",
|
||||
"payer": 1,
|
||||
"payed_for": [1],
|
||||
"amount": "10.0",
|
||||
"original_currency": "EUR",
|
||||
},
|
||||
)
|
||||
|
||||
# generate csv export of bills
|
||||
resp = self.client.get("/raclette/export/bills.csv")
|
||||
expected = [
|
||||
"date,what,amount,currency,payer_name,payer_weight,owers",
|
||||
"2016-12-31,'=COS(36),10.0,EUR,zorglub,1.0,zorglub",
|
||||
]
|
||||
received_lines = resp.data.decode("utf-8").split("\n")
|
||||
|
||||
for i, line in enumerate(expected):
|
||||
self.assertEqual(
|
||||
set(line.split(",")), set(received_lines[i].strip("\r").split(","))
|
||||
)
|
||||
|
||||
|
||||
class ImportTestCaseJSON(CommonTestCase.Import):
|
||||
def generate_form_data(self, data):
|
||||
|
|
|
@ -152,6 +152,17 @@ def list_of_dicts2json(dict_to_convert):
|
|||
return BytesIO(dumps(dict_to_convert).encode("utf-8"))
|
||||
|
||||
|
||||
def escape_csv_formulae(value):
|
||||
# See https://owasp.org/www-community/attacks/CSV_Injection
|
||||
if (
|
||||
value
|
||||
and isinstance(value, str)
|
||||
and value[0] in ["=", "+", "-", "@", "\t", "\n"]
|
||||
):
|
||||
return f"'{value}"
|
||||
return value
|
||||
|
||||
|
||||
def list_of_dicts2csv(dict_to_convert):
|
||||
"""Take a list of dictionnaries and turns it into
|
||||
a csv in-memory file, assume all dict have the same keys
|
||||
|
@ -164,7 +175,10 @@ def list_of_dicts2csv(dict_to_convert):
|
|||
# (expecting a sequence getting a view)
|
||||
csv_data = [list(dict_to_convert[0].keys())]
|
||||
for dic in dict_to_convert:
|
||||
csv_data.append([dic[h] for h in dict_to_convert[0].keys()])
|
||||
csv_data.append(
|
||||
[escape_csv_formulae(dic[h]) for h in dict_to_convert[0].keys()]
|
||||
)
|
||||
# csv_data.append([dic[h] for h in dict_to_convert[0].keys()])
|
||||
except (KeyError, IndexError):
|
||||
csv_data = []
|
||||
writer = csv.writer(csv_file)
|
||||
|
|
Loading…
Reference in a new issue