From a8d370f09eb54c50759fa6b92c2c50ffc4b4c604 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Paul=20H=C3=A4nsch?= Date: Sat, 3 Aug 2019 17:11:47 +0200 Subject: [PATCH] session aware file locking --- cards/edit_card.sh | 12 ++--- cards/list.sh | 124 +++++++++++++++++++++++-------------------- cards/update_card.sh | 118 ++++++++++++++++++++-------------------- session_lock.sh | 60 +++++++++++++++++++++ 4 files changed, 193 insertions(+), 121 deletions(-) create mode 100644 session_lock.sh diff --git a/cards/edit_card.sh b/cards/edit_card.sh index 6b62395..5a05194 100755 --- a/cards/edit_card.sh +++ b/cards/edit_card.sh @@ -17,19 +17,19 @@ # You should have received a copy of the GNU Affero General Public License # along with Confetti. If not, see . -mkdir -p "${_DATA}/lock" +locktimeout=900 +. "$_EXEC"/session_lock.sh card="$(GET card)" -lock="$_DATA/lock/${card}/" cardfile="$_DATA/vcard/$card" -tempfile="$lock/temp.vcf" - filter="$(REF f)" order="$(REF o)" -if mkdir "$lock" 2>&-; then - cp "$cardfile" "$tempfile" +if tempfile="$(SLOCK "$cardfile" "$locktimeout")"; then REDIRECT "/cards/?o=${order}&f=${filter}&e=${card}" +elif [ -f "$tempfile" ]; then + SET_COOKIE session message="SESSLOCK" + REDIRECT "/cards/?o=${order}&f=${filter}#${card}" else SET_COOKIE session message="EDITLOCK" REDIRECT "/cards/?o=${order}&f=${filter}#${card}" diff --git a/cards/list.sh b/cards/list.sh index a7d39e2..f598cd2 100755 --- a/cards/list.sh +++ b/cards/list.sh @@ -146,65 +146,73 @@ edit_item(){ edit_card(){ local cardfile="$_DATA/vcard/$1" - local card="$(pdi_load "$cardfile")" + local tempfile card - cat <<-EOF - [form .card #${cardfile##*/} action="/cards/update_card.sh" method="POST" - [div .section .basic $( - edit_item "$card" N GENDER - [ "$(pdi_count "$card" NICKNAME)" -gt 0 ] \ - && edit_item "$card" NICKNAME - edit_item "$card" X-ZACK-JOINDATE - [ "$(pdi_count "$card" X-ZACK-LEAVEDATE)" -gt 0 ] \ - && edit_item "$card" X-ZACK-LEAVEDATE - card_item "$card" SOUND PHOTO LOGO - )] - [div .section .phone $(edit_item "$card" TEL)] - [div .section .message $( - edit_item "$card" EMAIL - [ $(pdi_count "$card" IMPP) -gt 0 ] && edit_item "$card" IMPP - [ $(pdi_count "$card" URL ) -gt 0 ] && edit_item "$card" URL - )] - [div .section .address $(edit_item "$card" ADR)] - [div .section .note $(edit_item "$card" NOTE)] - [div .section .attendance - [h3 $(l10n course_attendance) ] $( - for course in "$_DATA"/ical/*.ics; do - printf '[label [input type="checkbox" name="attendance" value="%s" %s] %s]' \ - "${course##*/}" \ - "$(grep -qF "${course##*/} ${cardfile##*/}" "$_DATA/mappings/attendance" \ - && printf 'checked="checked"' - )" \ - "$(pdi_value "$(pdi_load "$course")" SUMMARY |unescape |HTML)" - done) - [h3 $(l10n CATEGORIES) ] $( - grep -xE '[^ ]+' "$_DATA"/mappings/categories |while read -r cat; do - printf '[label [input type="checkbox" name="attendance" value="%s" %s] %s]' \ - "$(HTML "$cat")" \ - "$(seq 1 $(pdi_count "$card" CATEGORIES) |while read c; do - pdi_value "$card" CATEGORIES $c |grep -qxF "$cat" \ - && printf 'checked="checked"' && break - done)" \ - "$(HTML "$cat")" - done) - ] - [div .control - [select .item name="newfield" - [option value="" disabled="disabled" selected="selected" $(l10n edit_addfieldtext)] - $(for f in ; do - printf '[option value="%s" %s] ' "$f" "$(l10n "$f")" - done) - ] - [button .item type="submit" name="action" value="addfield" $(l10n edit_addfield)] - [button .item type="submit" name="action" value="update" $(l10n edit_update)] - [input type="checkbox" #delete] [label .item for="delete" $(l10n edit_delete)] - [button .item type="submit" name="action" value="delete"] - [button .item type="submit" name="action" value="cancel" $(l10n edit_cancel)] - ] - [input type="hidden" name="UID" value="$(pdi_value "$card" UID |HTML)"] - [input type="hidden" name="card" value="${cardfile##*/}"] - ] + . $_EXEC/session_lock.sh + + if ! tempfile="$(CHECK_SLOCK "$cardfile")"; then + printf '[div .message %s]' "$(l10n "This card is not set up for editing within this session.")" + else + card="$(pdi_load "$tempfile")" + cat <<-EOF + [form .card #${cardfile##*/} action="/cards/update_card.sh" method="POST" + [input type="hidden" name="tid" value="$(transid ${tempfile})"] + [div .section .basic $( + edit_item "$card" N GENDER + [ "$(pdi_count "$card" NICKNAME)" -gt 0 ] \ + && edit_item "$card" NICKNAME + edit_item "$card" X-ZACK-JOINDATE + [ "$(pdi_count "$card" X-ZACK-LEAVEDATE)" -gt 0 ] \ + && edit_item "$card" X-ZACK-LEAVEDATE + card_item "$card" SOUND PHOTO LOGO + )] + [div .section .phone $(edit_item "$card" TEL)] + [div .section .message $( + edit_item "$card" EMAIL + [ $(pdi_count "$card" IMPP) -gt 0 ] && edit_item "$card" IMPP + [ $(pdi_count "$card" URL ) -gt 0 ] && edit_item "$card" URL + )] + [div .section .address $(edit_item "$card" ADR)] + [div .section .note $(edit_item "$card" NOTE)] + [div .section .attendance + [h3 $(l10n course_attendance) ] $( + for course in "$_DATA"/ical/*.ics; do + printf '[label [input type="checkbox" name="attendance" value="%s" %s] %s]' \ + "${course##*/}" \ + "$(grep -qF "${course##*/} ${cardfile##*/}" "$_DATA/mappings/attendance" \ + && printf 'checked="checked"' + )" \ + "$(pdi_value "$(pdi_load "$course")" SUMMARY |unescape |HTML)" + done) + [h3 $(l10n CATEGORIES) ] $( + grep -xE '[^ ]+' "$_DATA"/mappings/categories |while read -r cat; do + printf '[label [input type="checkbox" name="attendance" value="%s" %s] %s]' \ + "$(HTML "$cat")" \ + "$(seq 1 $(pdi_count "$card" CATEGORIES) |while read c; do + pdi_value "$card" CATEGORIES $c |grep -qxF "$cat" \ + && printf 'checked="checked"' && break + done)" \ + "$(HTML "$cat")" + done) + ] + [div .control + [select .item name="newfield" + [option value="" disabled="disabled" selected="selected" $(l10n edit_addfieldtext)] + $(for f in ; do + printf '[option value="%s" %s] ' "$f" "$(l10n "$f")" + done) + ] + [button .item type="submit" name="action" value="addfield" $(l10n edit_addfield)] + [button .item type="submit" name="action" value="update" $(l10n edit_update)] + [input type="checkbox" #delete] [label .item for="delete" $(l10n edit_delete)] + [button .item type="submit" name="action" value="delete"] + [button .item type="submit" name="action" value="cancel" $(l10n edit_cancel)] + ] + [input type="hidden" name="UID" value="$(pdi_value "$card" UID |HTML)"] + [input type="hidden" name="card" value="${cardfile##*/}"] + ] EOF + fi } print_card(){ @@ -233,7 +241,7 @@ print_card(){ $(card_item "$card" CATEGORIES) ] [div .control - [a .item href="/cards/?e=${cardfile##*/}" $(l10n edit)] + [a .item href="/cards/edit_card.sh?card=${cardfile##*/}" $(l10n edit)] [a .item href="/cards/?x=${cardfile##*/}" $(l10n vcf_export)] ] ] diff --git a/cards/update_card.sh b/cards/update_card.sh index a1143c3..ee9ac3e 100755 --- a/cards/update_card.sh +++ b/cards/update_card.sh @@ -1,6 +1,6 @@ #!/bin/zsh -# Copyright 2014, 2016 Paul Hänsch +# Copyright 2014, 2016, 2019 Paul Hänsch # # This file is part of Confetti. # @@ -17,86 +17,90 @@ # You should have received a copy of the GNU Affero General Public License # along with Confetti. If not, see . -cgi_refdata +. "$_EXEC/pdiread.sh" +. "$_EXEC/session_lock.sh" -filter="&filter=${_REF[filter]}" -filtertype="&filtertype=${_REF[filtertype]}" -order="&order=${_REF[order]}" +filter="$(REF f)" +order="$(REF o)" -card="${_POST[card]}" -tempfile="$_DATA/temp/$card" +card="$(POST card)" cardfile="$_DATA/vcard/$card" attfile="$_DATA/mappings/attendance" +if ! tempfile=$(CHECK_SLOCK "$cardfile"); then + SET_COOKIE 0 message="NO VALID FILE LOCK" + REDIRECT "/cards/?o=${order}&f=${filter}&e=${card}" + exit 0 +elif [ "$(POST tid)" != "$(transid "$tempfile")" ]; then + SET_COOKIE 0 message="INVALID TRANSACTION ID" + REDIRECT "/cards/?o=${order}&f=${filter}&e=${card}" + exit 0 +fi + vcf_escape(){ for each in "$@"; do printf %s\\n "$each" \ - | sed -r ':X;$!{N;bX}; s;\r\n;\n;g; s;([;,\\]);\\\1;g; s;\n;\\n;g;' + | sed -E ':X;$!{N;bX}; s;\r\n;\n;g; s;([;,\\]);\\\1;g; s;\n;\\n;g;' done \ - | sed -r ':X;$!{N;bX}; s;\n;\;;g' + | sed -E ':X;$!{N;bX}; s;\n;\;;g' } -[ "${_POST[hi_select]}" = "list" ] || _POST[hi_company]="${_POST[hi_other]}" -[ -n "${_POST[hi_company]}${_POST[hi_number]}${_POST[hi_status]}" ] \ -&& _POST[X-HEALTH-INSURANCE]="$(vcf_escape "${_POST[hi_company]}" "${_POST[hi_number]}" "${_POST[hi_status]}")" +# [ "${_POST[hi_select]}" = "list" ] || _POST[hi_company]="${_POST[hi_other]}" +# [ -n "${_POST[hi_company]}${_POST[hi_number]}${_POST[hi_status]}" ] \ +# && _POST[X-HEALTH-INSURANCE]="$(vcf_escape "${_POST[hi_company]}" "${_POST[hi_number]}" "${_POST[hi_status]}")" -sed -r 's;$;\r;' >"$tempfile" <"$tempfile" -case "${_POST[action]}" in +case "$(POST action)" in addfield) - redirect "?p=cards${filter}${filtertype}${order}&edit=$card" + REDIRECT "/cards/?o=${order}&f=${filter}&e=${card}" ;; update) - attendance=() - for att in attendance attendance{0..100}; do - [ -n "${_POST[$att]}" ] && attendance+=("${_POST[$att]}") - done - sed -rn 's:^(.+)'$card'$:\1:p' "$attfile" |while read course; do - touch "$_DATA/ical/$course" - done - sed -i -r '/^(.+)\t'$card'$/d' "$attfile" - for each in $attendance; do - echo "$each\t$card" - done >>"$attfile" - sed -rn 's:^(.+)'$card'$:\1:p' "$attfile" |while read course; do - touch "$_DATA/ical/$course" - done + # attendance=() + # for att in attendance attendance{0..100}; do + # [ -n "${_POST[$att]}" ] && attendance+=("${_POST[$att]}") + # done + # sed -rn 's:^(.+)'$card'$:\1:p' "$attfile" |while read course; do + # touch "$_DATA/ical/$course" + # done + # sed -i -r '/^(.+)\t'$card'$/d' "$attfile" + # for each in $attendance; do + # echo "$each\t$card" + # done >>"$attfile" + # sed -rn 's:^(.+)'$card'$:\1:p' "$attfile" |while read course; do + # touch "$_DATA/ical/$course" + # done - mv "$tempfile" "$cardfile" - redirect "?p=cards${filter}${filtertype}${order}#$card" + cp "$tempfile" "$cardfile" + RELEASE_SLOCK "$cardfile" + REDIRECT "/cards/?o=${order}&f=${filter}#${card}" ;; cancel) - rm "$tempfile" + RELEASE_SLOCK "$cardfile" [ -f "$cardfile" ] \ - && redirect "?p=cards${filter}${filtertype}${order}#$card" \ - || redirect "?p=cards${filter}${filtertype}${order}" + && REDIRECT "/cards/?o=${order}&f=${filter}#${card}" \ + || REDIRECT "/cards/?o=${order}&f=${filter}" ;; delete) - rm "$tempfile" "$cardfile" - redirect "?p=cards${filter}${filtertype}${order}" + rm "$cardfile" + RELEASE_SLOCK "$cardfile" + REDIRECT "/cards/?o=${order}&f=${filter}" ;; esac diff --git a/session_lock.sh b/session_lock.sh new file mode 100644 index 0000000..17af0e6 --- /dev/null +++ b/session_lock.sh @@ -0,0 +1,60 @@ +#!/bin/sh + +[ "$include_session_lock" ] && return 0 +include_session_lock="$0" + +SLOCK(){ + local file="$1"; + local timeout="${2-900}" + local lockdir="$_DATA/lock/${file#$_DATA}"; lockdir="${lockdir%/}" + local ovlock="${lockdir%/*}/delete.${lockdir##*/}" + local tempfile="$lockdir/${SESSION_ID}" + local lockexpire=$(( $(date +%s) - timeout )) + + mkdir -p "$_DATA/lock/${file%/*}" + + if [ -e "$lockdir" ] \ + && [ "$(stat -c %Y "$lockdir")" -lt "$lockexpire" ] \ + && mkdir "$ovlock"; then + [ "$(stat -c %Y "$lockdir")" -lt "$lockexpire" ] \ + && rm -r "$lockdir" + rmdir "$ovlock" + fi + + printf '%s\n' "$tempfile" + if mkdir "$lockdir" 2>&-; then + cp "$file" "$tempfile" + return 0 + else + return 1 + fi +} + +CHECK_SLOCK(){ + local file="$1"; + local lockdir="$_DATA/lock/${file#$_DATA}"; lockdir="${lockdir%/}" + local tempfile="$lockdir/${SESSION_ID}" + + printf '%s\n' "$tempfile" + if [ -f "$tempfile" ]; then + touch "$lockdir" + return 0 + else + return 1 + fi +} + +RELEASE_SLOCK(){ + local file="$1"; + local lockdir="$_DATA/lock/${file#$_DATA}"; lockdir="${lockdir%/}" + local ovlock="${lockdir%/*}/delete.${lockdir##*/}" + local tempfile="$lockdir/${SESSION_ID}" + + if [ -f "$tempfile" ] && mkdir "$ovlock"; then + [ -f "$tempfile" ] && rm -r "$lockdir" + rmdir "$ovlock" + return 0 + else + return 1 + fi +} -- 2.39.2