--- /dev/null
+#!/bin/sh
+
+# Copyright 2024 Paul Hänsch
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+# IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+if [ "${CONTENT_TYPE%%;*}" != "multipart/form-data" ]; then
+ SET_COOKIE 0 message="Not an upload"
+ REDIRECT "${_BASE}/ledgers/"
+fi
+
+. "$_EXEC/multipart.sh"
+multipart_cache
+
+# Validate session id from form to prevent CSRF
+if [ "$(multipart session_id)" != "$SESSION_ID" ]; then
+ rm -- "$multipart_cachefile"
+ SET_COOKIE 0 message="INVALID SESSION ID IN FORM"
+ REDIRECT "${_BASE}/ledgers/"
+fi
+
+mkdir -p "$_DATA/ledgers/"
+CSV="$(multipart "csv" 1 | "$_EXEC/ledgers/csv_upload.awk")"
+rm -- "$multipart_cachefile"
+
+read dtu_start dt_start dtu_end dt_end balance_start balance_end <<-EOF
+ ${CSV%%${BR}*}
+EOF
+
+if [ ! "$dtu_end" -o ! "$dtu_start" ] || [ "$dtu_end" -lt "$dtu_start" ]; then
+ SET_COOKIE 0 message="No valid date range in upload"
+else
+ num=0; while [ ! "$filename" -o -f "$_DATA/ledgers/$filename" ]; do
+ num=$((num + 1)); filename="${dt_start} - ${dt_end} - $(printf '%04i' $num).tbl"
+ done
+ printf '%s\n' "$CSV" >"$_DATA/ledgers/$filename"
+fi
+
+REDIRECT "${_BASE}/ledgers/"