send actual CGI status codes
[bookman] / index.cgi
1 #!/bin/sh
2
3 exec 2>>error.log
4
5 . shcgi/cgilite.sh
6 mkdir -p users
7 # printf 'POST: %s\n' "$cgilite_post" >>debug
8 # printf 'action: %s\n' "$(GET action)" >>debug
9
10 wget="$(which wget)"
11 wget(){ "$wget" -T 5 -t 1 -q -U '' $@; }
12 checkid(){ grep -m 1 -xE '[0-9a-zA-Z:_]{12}'; }
13
14 genid(){
15   # generate random ID
16   head -c9 /dev/urandom \
17   | uuencode -m - \
18   | sed -n '2{y;+/;:_;;p}'
19 }
20
21 timeid(){
22   # generate time based ID
23   d=$(date +%s)
24   { printf $(
25       while [ "$d" -gt 0 ]; do
26         printf \\%o $((d % 256))
27         d=$((d / 256))
28       done
29     ) | tac
30     head -c5 /dev/urandom
31   } \
32   | uuencode -m - \
33   | sed -n '2{y;+/;:_;;p}'
34 }
35
36 getFavicon(){
37   url="$1"
38   bid="$2"
39   prot=${url%%://*}
40   domain="${url#*://}"
41   domain="${domain%%/*}"
42   ubase="${prot}://${domain}"
43   file="${BDB}/favicons/${bid}.ico"
44
45   mkdir -p "${BDB}/favicons/" && chmod a+rx "${BDB}/favicons/"
46
47   favinfo="$(
48     wget -O- "$url" \
49     | head -c4096 \
50     | sed -rn \
51       's;^.*(<[Ll][Ii][Nn][Kk]( [^>]*)? [Rr][Re][Ll]='\''([Ss][Hh][Oo][Rr][Tt][Cc][Uu][Tt] )?[Ii][Cc][Oo][Nn]'\''[^>]*>).*$;\1;;
52        s;^.*(<[Ll][Ii][Nn][Kk]( [^>]*)? [Rr][Re][Ll]="([Ss][Hh][Oo][Rr][Tt][Cc][Uu][Tt] )?[Ii][Cc][Oo][Nn]"[^>]*>).*$;\1;;
53        tX; b; :X;
54        s;^.*<([^>]+) [Hh][Rr][Ee][Ff]="([^"]+)".*$:\2;;
55        s;^.*<([^>]+) [Hh][Rr][Ee][Ff]='\''([^'\'']+)'\''.*$:\2;;
56        tY; b; :Y; p
57       '
58   )"
59
60   # printf 'Shortcut icon for %s is %s\n' "$url" "$favinfo" >>debug
61   [ -z "$favinfo" ] && favinfo="${ubase}/favicon.ico"
62   case "$favinfo" in
63     http://*|https://*|//*) wget -O "$file" "$favinfo"
64     ;;
65     /*) wget -O "$file" "${ubase}/${favinfo}"
66     ;;
67     *) wget -O "$file" "${url%/*}/${favinfo}"
68     ;;
69   esac
70   [ -f "${file}.1" ] && mv "${file}.1" "$file"
71   chmod a+r "$file"
72 }
73
74 list_folders(){
75   head -qn1 "${BDB}"/????????????.bm \
76   | sort -nk3 \
77   | cut -f1,2
78 }
79
80 order_files(){
81   n=1000
82   list_folders \
83   | while read fid nan; do
84     file="${BDB}/${fid}.bm"
85     sed -ri "1s;^(([^\t]+\t){2})[^\t]+(.*)$;\1${n}\3;;" "$file"
86     n=$((n + 1000))
87   done
88 }
89
90 COKID="$(COOKIE id |checkid)"
91 QRYID="$(GET    id |checkid)"
92 BDB="users/${COKID}"
93
94 if [ -n "$QRYID" ]; then
95   printf 'Status: 303 See Other\r\n'
96   printf 'Location: %s\r\n' "${SCRIPT_NAME}?${QUERY_STRING#id=????????????}"
97   SET_COOKIE +8640000 "id=${QRYID}"
98   printf '\r\n'
99   exit 0
100 elif [ -z "$COKID" -a -z "$QRYID" ]; then
101   printf 'Content-Type: text/html; charset=utf-8\r\n\r\n'
102
103   cat <<-EOF
104         <!DOCTYPE HTML>
105         <HTML><head>
106           <title>Bookman - New Collection</title>
107         </head><body id="newcollection">
108           <h1>You have not yet set up a collection on this server.</h1>
109           Click <a href="${SCRIPT_NAME}?action=newid">here</a> to start a new collection.
110         </body></HTML>
111         EOF
112   exit 0
113 elif ! [ -d "${BDB}" ]; then
114   printf 'Status: 404 Not Found\r\n'
115   printf 'Content-Type: text/html; charset=utf-8\r\n\r\n'
116
117   cat <<-EOF
118         <!DOCTYPE HTML>
119         <HTML><head>
120           <title>Bookman - 404</title>
121         </head><body id="missingcollection">
122           <h1>The collection you requested does not exist on this server.</h1>
123           Click <a href="${SCRIPT_NAME}?action=newid">here</a> to start a new collection.
124         </body></HTML>
125         EOF
126   exit 0
127 fi
128
129 case "$(GET action)" in
130   newid)
131     NEWID="$(genid)"
132
133     { git init "users/${NEWID}" || mkdir -p "users/${NEWID}"; } >&-
134
135     printf 'Status: 303 See Other\r\n'
136     printf 'Location: %s\r\n' "${SCRIPT_NAME}?id=${NEWID}"
137     SET_COOKIE +8640000 "id=${NEWID}"
138     printf '\r\n'
139     exit 0
140     ;;
141   newfolder)
142     name="$(POST name |head -n1)"
143     fid="$(timeid)"
144     order="$(
145       head -qn1 "${BDB}"/????????????.bm \
146       | cut -f3 \
147       | sort -n \
148       | tail -n1 \
149       || printf 1
150     )"
151     order="$(((order + 1000) / 1000 * 1000))"
152     if [ -n "$name" -a -d "${BDB}" ]; then
153       printf '%s\t%s\t%s\n' "$fid" "$(HTML "$name")" "$order" >"${BDB}/${fid}.bm"
154       git -C "${BDB}" add "${fid}.bm" >&-
155       git -C "${BDB}" commit -m "New bookmark folder: ${name} (${fid})" -- "${fid}.bm" >&-
156     fi
157     REDIRECT "${SCRIPT_NAME}#${fid}"
158     ;;
159   modfolder)
160     name="$(POST name |head -n1)"
161     fid="$(POST fid | checkid)"
162     file="${BDB}/${fid}.bm"
163     if [ "$(POST control)" = confirm -a -n "$name" -a -f "$file" ]; then
164       sed -ri "1s'^(${fid}\t)[^\t]+(\t.+)$'\1${name}\2';" "$file"
165       git -C "${BDB}" add "${fid}.bm" >&-
166       git -C "${BDB}" commit -m "Renamed folder ${fid} to '${name}'" -- "${fid}.bm" >&-
167     fi
168     REDIRECT "${SCRIPT_NAME}#${fid}"
169     ;;
170   delfolder)
171     fid="$(POST fid | checkid)"
172     target="$(POST target | checkid)"
173     file="${BDB}/${fid}.bm"
174     tfile="${BDB}/${target}.bm"
175     if [ "$(POST control)" = confirm -a -f "$file" ]; then
176       if [ "$target" = "____________" ]; then
177         rm -f "$file" "${file%.bm}.cache" >&-
178         git -C "${BDB}" commit -m "Deleted folder ${fid}" -- "${fid}.bm" >&-
179       elif tail -n+2 "$file" >>"$tfile"; then
180         rm -f "$file" "${file%.bm}.cache" >&-
181         git -C "${BDB}" commit -m "Deleted folder ${fid}" -- "${fid}.bm" "${target}.bm" >&-
182       fi
183     fi
184     REDIRECT "${SCRIPT_NAME}#${target}"
185     ;;
186   movefolder)
187     fid="$(POST fid | checkid)"
188     target="$(POST target | checkid)"
189     file="${BDB}/${fid}.bm"
190     tfile="${BDB}/${target}.bm"
191     if [ "$target" = "____________" -a -f "$file" -a "$(POST control)" = confirm ]; then
192       read nan1 nan2 last nan3 <"${BDB}/$(list_folders |tail -n1 |cut -f1).bm"
193       sed -ri "1s;^(([^\t]+\t){2})[^\t]+(.*)$;\1$((${last:-0} + 1000))\3;;" "$file"
194     elif [ -f "$tfile" -a -f "$file" -a "$(POST control)" = confirm ]; then
195       read nan1 nan2 tid nan3 <"$tfile"
196       sed -ri "1s;^(([^\t]+\t){2})[^\t]+(.*)$;\1$((${tid:-1} -1))\2;;" "$file"
197       order_files
198     fi
199     git -C "${BDB}" commit -a -m "Modified folder order (moved ${fid})" >&-
200     REDIRECT "${SCRIPT_NAME}#${fid}"
201     ;;
202   newbookmark)
203     fid="$(POST fid | checkid)"
204     name="$(POST name |head -n1)"
205     url="$(POST url |head -n1)"
206     file="${BDB}/${fid}.bm"
207     bid="$(timeid)"
208     ctl="$(POST control)"
209     [ "$url" = "${url#*://}" ] && url="http://$url"
210
211     if [ -n "$name" -a -n "$url" -a -f "${file}" -a "$ctl" = "confirm" ]; then
212       printf '%s\t%s\t%s\n' "$bid" "$(HTML "$name")" "$url" >>"${file}"
213       git -C "${BDB}" commit -m "New Bookmark: ${name} in ${fid}" -- "${fid}.bm" >&-
214       getFavicon "$url" "$bid"
215       REDIRECT "${SCRIPT_NAME}#${fid}"
216     elif [ "$ctl" = "confirm" ]; then
217       REDIRECT "${SCRIPT_NAME}?newbm=${fid}&nbmurl=${url}&nbmname=${name}"
218     else
219       REDIRECT "${SCRIPT_NAME}#${fid}"
220     fi
221     ;;
222   modbookmark)
223     bid="$(POST bid | checkid)"
224     name="$(POST name |head -n1)"
225     url="$(POST url |head -n1)"
226     file="$(grep -lm1 "^${bid}" "${BDB}"/????????????.bm |head -n1)"
227     if [ -n "$name" -a -n "$url" -a "$(POST control)" = confirm -a -w "$file" ]; then
228       sed -rni "/^${bid}\t/!p; /^${bid}\t/i${bid}\t$(HTML "$name")\t${url}" "$file"
229       git -C "${BDB}" commit -m "Modified Bookmark: ${name} (${bid}) in ${file##*/}" -- "${file##*/}" >&-
230     fi
231     getFavicon "$url" "$bid"
232     REDIRECT "${SCRIPT_NAME}#${fid}"
233     ;;
234   movebookmark)
235     bid="$(POST bid | checkid)"
236     fid="$(POST target | checkid)"
237     sfile="$(grep -lm1 "^${bid}" "${BDB}"/????????????.bm |head -n1)"
238     tfile="${BDB}/${fid}.bm"
239
240     if [ "$(POST control)" = confirm -a -n "$bid" -a -w "$sfile" -a -w "$tfile" ]; then
241       grep -m1 "^${bid}" "$sfile" >>"$tfile" \
242       && sed -ri "0,/^${bid}/{/^${bid}/d;}" "$sfile"
243       git -C "${BDB}" commit -m "Moved Bookmark ${bid} from ${sfile##*/} to ${tfile##*/}" \
244           -- "${sfile##*/}" "${tfile##*/}" >&-
245     fi
246     REDIRECT "${SCRIPT_NAME}#${fid}"
247     ;;
248   bmup)
249     fid="$(GET fid |checkid)"
250     bid="$(GET bid |checkid)"
251     file="${BDB}/${fid}.bm"
252
253     if [ -n "$bid" -a -n "$fid" ] && grep -q "^${bid}" "$file"; then
254       sed -ri ":X;\$bY;N;bX;:Y; s;(\n[^\n]+)(\n${bid}\t[^\n]+);\2\1;;" "$file"
255       git -C "${BDB}" commit -m "Modified bookmark order in ${fid} (raised ${bid})" -- "${fid}.bm" >&-
256     fi
257     REDIRECT "${SCRIPT_NAME}#${fid}"
258     ;;
259   bmdn)
260     fid="$(GET fid |checkid)"
261     bid="$(GET bid |checkid)"
262     file="${BDB}/${fid}.bm"
263
264     if [ -n "$bid" -a -n "$fid" ] && grep -q "^${bid}" "$file"; then
265       sed -ri ":X;\$bY;N;bX;:Y; s;(\n${bid}\t[^\n]+)(\n[^\n]+);\2\1;;" "$file"
266       git -C "${BDB}" commit -m "Modified bookmark order in ${fid} (lowered ${bid})" -- "${fid}.bm" >&-
267     fi
268     REDIRECT "${SCRIPT_NAME}#${fid}"
269     ;;
270   query)
271     fid="$(POST fid |checkid)"
272     bid="$(POST bid |checkid)"
273     file="${BDB}/${fid}.bm"
274     query="$(URL "$(POST query)")"
275
276
277     url="$(grep -m1 "^${bid}" "$file" |cut -f3-)"
278     urlpfx="${url%\{@\}*}"
279     urlsfx="${url#*\{@\}}"
280
281     REDIRECT "${urlpfx}${query}${urlsfx}"
282     ;;
283 esac
284
285 bookmarkgen(){
286   fid="$(GET newbm |checkid)"
287   name="$(GET nbmname)"
288   url="$(GET nbmurl)"
289   file="${BDB}/${fid}.bm"
290
291   if [ -z "$name" -a -n "$url" ]; then
292     name="$(wget -O- "$url" \
293             | head -c4096 \
294             | sed -rn ':X;$bY;N;bX;:Y; s;^.*<title[^>]*>([^<]+)<.*$;\1;p;'
295           )"
296   fi
297
298   if [ -n "$fid" -o -n "$name" -o -n "$url" ]; then
299     [ "$url" = "${url#*://}" ] && url="http://$url"
300     cat <<-EOF
301         <form class="dialog newbookmark" method="POST" action="${SCRIPT_NAME}?action=newbookmark">
302           <h1>New Bookmark</h1>
303           <label>Folder:</label>
304           <select name="fid">
305             $(list_folders |while read id n; do
306               [ "$id" = "$fid" ] \
307               && printf '<option value="%s" selected="selected">%s</option>' "$id" "$n" \
308               || printf '<option value="%s">%s</option>' "$id" "$n"
309             done)
310             $(printf '<option value="%s">%s</option>' $(list_folders))
311           </select>
312           <label>Name:</label>
313           <input type="text" name="name" value="$(HTML ${name})")" placeholder="Name" />
314           <label>URL:</label>
315           <input type="text" name="url"  value="$(HTML "${url}")")" placeholder="URL" />
316           <button type="submit" name="control" value="confirm">OK</button>
317           <button type="submit" name="control" value="cancel">Cancel</button>
318         </form>
319         EOF
320   fi
321 }
322
323 bookmarkmod(){
324   bmod="$(GET bmodify |checkid)"
325   bmove="$(GET bmove |checkid)"
326
327   if [ -n "$bmod" ]; then
328     file="$(grep -lm1 "^${bmod}" "${BDB}/"????????????.bm |head -n1)"
329     read bid name url <<-EOF
330         $(grep -m1 "^${bmod}" "$file")
331         EOF
332     cat <<-EOF
333         <form class="dialog modbookmark" method="POST" action="${SCRIPT_NAME}?action=modbookmark">
334           <input type="hidden" name="bid" value="${bid}" />
335           <h1>Modify: ${name}</h1>
336           <label class="tab">Modify</label>
337           <a class="tab" href="${SCRIPT_NAME}?bmove=${bid}">Move</a>
338           <label>Name:</label>
339           <input type="text" name="name" value="${name}")" placeholder="Name" />
340           <label>URL:</label>
341           <input type="text" name="url"  value="$(HTML "${url}")")" placeholder="URL" />
342           <button type="submit" name="control" value="confirm">OK</button>
343           <button type="submit" name="control" value="cancel">Cancel</button>
344         </form>
345         EOF
346   elif [ -n "$bmove" ]; then
347     file="$(grep -lm1 "^${bmove}" "${BDB}/"????????????.bm |head -n1)"
348     read bid name url <<-EOF
349         $(grep -m1 "^${bmove}" "$file")
350         EOF
351     cat <<-EOF
352         <form class="dialog modbookmark" method="POST" action="${SCRIPT_NAME}?action=movebookmark">
353           <input type="hidden" name="bid" value="${bid}" />
354           <h1>Move: ${name}</h1>
355           <a class="tab" href="${SCRIPT_NAME}?bmodify=${bid}">Modify</a>
356           <label class="tab">Move</label>
357           <label>Move to Folder:</label>
358           <select name="target">
359             $(printf '<option value="%s">%s</option>' $(list_folders))
360           </select>
361           <button type="submit" name="control" value="confirm">OK</button>
362           <button type="submit" name="control" value="cancel">Cancel</button>
363         </form>
364         EOF
365   fi
366 }
367
368 show_bookmarks(){
369   fid="$1"
370   bmodify="$(GET bmodify |checkid)"
371
372   tail -n+2 "${BDB}/${fid}.bm" \
373   | while read bid name url; do
374     if [ "${url%\{@\}*}" = "${url}" ]; then
375       cat <<-EOF
376         <div class="bookmark">
377           <a class="modify" href="${SCRIPT_NAME}?bmodify=${bid}">Modify</a>
378           <a class="link" target="_blank" href="$(HTML "${url}")")">
379             <img alt="@" src="${BDB}/favicons/${bid}.ico"/>${name}
380           </a>
381           <a class="bmove" href="${SCRIPT_NAME}?action=bmup&fid=${fid}&bid=${bid}">Move up</a>
382           <a class="bmove" href="${SCRIPT_NAME}?action=bmdn&fid=${fid}&bid=${bid}">Move down</a>
383         </div>
384         EOF
385     else
386       cat <<-EOF
387         <form class="bookmark" target="_blank" method="POST" action="${SCRIPT_NAME}?action=query">
388           <a class="modify" href="${SCRIPT_NAME}?bmodify=${bid}">Modify</a>
389           <input type="hidden" name="fid" value="$fid" />
390           <input type="hidden" name="bid" value="$bid" />
391           <img alt="${name}" src="${BDB}/favicons/${bid}.ico"/>
392           <input name="query" placeholder="$name"/>
393           <a class="bmove" href="${SCRIPT_NAME}?action=bmup&fid=${fid}&bid=${bid}">Move up</a>
394           <a class="bmove" href="${SCRIPT_NAME}?action=bmdn&fid=${fid}&bid=${bid}">Move down</a>
395         </form>
396         EOF
397     fi
398   done
399 }
400
401 foldermod(){
402   fmodify="$(GET fmodify |checkid )"
403   fdelete="$(GET fdelete |checkid )"
404   fmove="$(GET fmove |checkid )"
405
406   if [ -n "$fmodify" ]; then
407     read fid fname order <"${BDB}/${fmodify}.bm"
408     cat <<-EOF
409         <form class="dialog modfolder rename" method="POST" action="${SCRIPT_NAME}?action=modfolder">
410           <h1>Rename Folder: ${fname}</h1>
411           <input type="hidden" name="fid" value="${fid}" />
412           <label class="tab">Rename</label>
413           <a class="tab"
414             href="${SCRIPT_NAME}?fdelete=${fid}">Delete</a>
415           <a class="tab"
416             href="${SCRIPT_NAME}?fmove=${fid}">Move</a>
417           <input type="text" name="name" value="${fname}" />
418           <button type="submit" name="control" value="confirm">OK</button>
419           <button type="submit" name="control" value="cancel">Cancel</button>
420         </form>
421         EOF
422   elif [ -n "$fdelete" ]; then
423     read fid fname order <"${BDB}/${fdelete}.bm"
424     cat <<-EOF
425         <form class="dialog modfolder delete" method="POST" action="${SCRIPT_NAME}?action=delfolder">
426           <h1>Delete Folder: ${fname}</h1>
427           <input type="hidden" name="fid" value="${fid}" />
428           <a class="tab" href="${SCRIPT_NAME}?fmodify=${fid}">Rename</a>
429           <label class="tab">Delete</label>
430           <a class="tab"
431             href="${SCRIPT_NAME}?fmove=${fid}">Move</a>
432           <label>Pass Bookmarks on to:</label>
433           <select name="target">
434             $(printf '<option value="%s">%s</option>' $(list_folders |grep -v "^${fid}"))
435             <option value="____________">(discard)</option>
436           </select>
437           <button type="submit" name="control" value="confirm">OK</button>
438           <button type="submit" name="control" value="cancel">Cancel</button>
439         </form>
440         EOF
441   elif [ -n "$fmove" ]; then
442     read fid fname order <"${BDB}/${fmove}.bm"
443     cat <<-EOF
444         <form class="dialog modfolder move" method="POST" action="${SCRIPT_NAME}?action=movefolder">
445           <h1>Move Folder: ${fname}</h1>
446           <input type="hidden" name="fid" value="${fid}" />
447           <a class="tab" href="${SCRIPT_NAME}?fmodify=${fid}">Rename</a>
448           <a class="tab" href="${SCRIPT_NAME}?fdelete=${fid}">Delete</a>
449           <label class="tab">Move</label>
450           <label>Move before folder:</label>
451           <select name="target">
452             $(printf '<option value="%s">%s</option>' $(list_folders |grep -v "^${fid}"))
453             <option value="____________">(last)</option>
454           </select>
455           <button type="submit" name="control" value="confirm">OK</button>
456           <button type="submit" name="control" value="cancel">Cancel</button>
457         </form>
458         EOF
459   fi
460 }
461
462 show_folders(){
463   list_folders \
464   | while read fid fname order; do
465     file="${BDB}/${fid}.bm"
466     cache="${BDB}/${fid}.cache"
467     if [ "${cache}" -nt "${file}" -a "${cache}" -nt "$0" ]; then
468       cat "$cache"
469     else
470       tee "$cache" <<-EOF
471         <section class="folder" id="${fid}">
472           <h1>${fname}</h1>
473           <a class="modify" href="${SCRIPT_NAME}?fmodify=${fid}">Modify folder "${fname}"</a>
474           $(show_bookmarks "$fid")
475           <a class="new bookmark" href="${SCRIPT_NAME}?newbm=${fid}">New Bookmark</a>
476         </section>
477         EOF
478     fi
479   done
480 }
481
482 SET_COOKIE +8640000 "id=${COKID}"      # Refresh Cookie
483 printf 'Content-Type: text/html; charset=utf-8\r\n\r\n'
484
485 cat <<EOF
486 <!DOCTYPE HTML>
487 <HTML><head>
488   <title>Bookman - Your Collection</title>
489   <link rel="stylesheet" type="text/css" href="bookmarks.css" />
490 </head><body id="collection">
491   $(foldermod)
492   $(bookmarkmod)
493   $(bookmarkgen)
494   $(show_folders)
495   <form class="newfolder" method="POST" action="${SCRIPT_NAME}?action=newfolder">
496     <input type="text" name="name" value="" placeholder="New Folder" />
497     <button type="submit">New</button>
498   </form>
499   <footer>
500     <a href="${SCRIPT_NAME}?id=${COKID}">Permalink for this Collection</a>
501   </footer>
502 </body></HTML>
503 EOF
504
505 #set filetype=sh