make "new folder" button visible again
[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           <meta name="viewport" content="width=device-width" />
107           <title>Bookman - New Collection</title>
108         </head><body id="newcollection">
109           <h1>You have not yet set up a collection on this server.</h1>
110           Click <a href="${SCRIPT_NAME}?action=newid">here</a> to start a new collection.
111         </body></HTML>
112         EOF
113   exit 0
114 elif ! [ -d "${BDB}" ]; then
115   printf 'Status: 404 Not Found\r\n'
116   printf 'Content-Type: text/html; charset=utf-8\r\n\r\n'
117
118   cat <<-EOF
119         <!DOCTYPE HTML>
120         <HTML><head>
121           <meta name="viewport" content="width=device-width" />
122           <title>Bookman - 404</title>
123         </head><body id="missingcollection">
124           <h1>The collection you requested does not exist on this server.</h1>
125           Click <a href="${SCRIPT_NAME}?action=newid">here</a> to start a new collection.
126         </body></HTML>
127         EOF
128   exit 0
129 fi
130
131 case "$(GET action)" in
132   newid)
133     NEWID="$(genid)"
134
135     { git init "users/${NEWID}" || mkdir -p "users/${NEWID}"; } >&-
136
137     printf 'Status: 303 See Other\r\n'
138     printf 'Location: %s\r\n' "${SCRIPT_NAME}?id=${NEWID}"
139     SET_COOKIE +8640000 "id=${NEWID}"
140     printf '\r\n'
141     exit 0
142     ;;
143   newfolder)
144     name="$(POST name |head -n1)"
145     fid="$(timeid)"
146     order="$(
147       head -qn1 "${BDB}"/????????????.bm \
148       | cut -f3 \
149       | sort -n \
150       | tail -n1 \
151       || printf 1
152     )"
153     order="$(((order + 1000) / 1000 * 1000))"
154     if [ -n "$name" -a -d "${BDB}" ]; then
155       printf '%s\t%s\t%s\n' "$fid" "$(HTML "$name")" "$order" >"${BDB}/${fid}.bm"
156       git -C "${BDB}" add "${fid}.bm" >&-
157       git -C "${BDB}" commit -m "New bookmark folder: ${name} (${fid})" -- "${fid}.bm" >&-
158     fi
159     REDIRECT "${SCRIPT_NAME}#${fid}"
160     ;;
161   modfolder)
162     name="$(POST name |head -n1)"
163     fid="$(POST fid | checkid)"
164     file="${BDB}/${fid}.bm"
165     if [ "$(POST control)" = confirm -a -n "$name" -a -f "$file" ]; then
166       sed -ri "1s'^(${fid}\t)[^\t]+(\t.+)$'\1${name}\2';" "$file"
167       git -C "${BDB}" add "${fid}.bm" >&-
168       git -C "${BDB}" commit -m "Renamed folder ${fid} to '${name}'" -- "${fid}.bm" >&-
169     fi
170     REDIRECT "${SCRIPT_NAME}#${fid}"
171     ;;
172   delfolder)
173     fid="$(POST fid | checkid)"
174     target="$(POST target | checkid)"
175     file="${BDB}/${fid}.bm"
176     tfile="${BDB}/${target}.bm"
177     if [ "$(POST control)" = confirm -a -f "$file" ]; then
178       if [ "$target" = "____________" ]; then
179         rm -f "$file" "${file%.bm}.cache" >&-
180         git -C "${BDB}" commit -m "Deleted folder ${fid}" -- "${fid}.bm" >&-
181       elif tail -n+2 "$file" >>"$tfile"; then
182         rm -f "$file" "${file%.bm}.cache" >&-
183         git -C "${BDB}" commit -m "Deleted folder ${fid}" -- "${fid}.bm" "${target}.bm" >&-
184       fi
185     fi
186     REDIRECT "${SCRIPT_NAME}#${target}"
187     ;;
188   movefolder)
189     fid="$(POST fid | checkid)"
190     target="$(POST target | checkid)"
191     file="${BDB}/${fid}.bm"
192     tfile="${BDB}/${target}.bm"
193     if [ "$target" = "____________" -a -f "$file" -a "$(POST control)" = confirm ]; then
194       read nan1 nan2 last nan3 <"${BDB}/$(list_folders |tail -n1 |cut -f1).bm"
195       sed -ri "1s;^(([^\t]+\t){2})[^\t]+(.*)$;\1$((${last:-0} + 1000))\3;;" "$file"
196     elif [ -f "$tfile" -a -f "$file" -a "$(POST control)" = confirm ]; then
197       read nan1 nan2 tid nan3 <"$tfile"
198       sed -ri "1s;^(([^\t]+\t){2})[^\t]+(.*)$;\1$((${tid:-1} -1))\2;;" "$file"
199       order_files
200     fi
201     git -C "${BDB}" commit -a -m "Modified folder order (moved ${fid})" >&-
202     REDIRECT "${SCRIPT_NAME}#${fid}"
203     ;;
204   newbookmark)
205     fid="$(POST fid | checkid)"
206     name="$(POST name |head -n1)"
207     url="$(POST url |head -n1)"
208     file="${BDB}/${fid}.bm"
209     bid="$(timeid)"
210     ctl="$(POST control)"
211     [ "$url" = "${url#*://}" ] && url="http://$url"
212
213     if [ -n "$name" -a -n "$url" -a -f "${file}" -a "$ctl" = "confirm" ]; then
214       printf '%s\t%s\t%s\n' "$bid" "$(HTML "$name")" "$url" >>"${file}"
215       git -C "${BDB}" commit -m "New Bookmark: ${name} in ${fid}" -- "${fid}.bm" >&-
216       getFavicon "$url" "$bid"
217       REDIRECT "${SCRIPT_NAME}#${fid}"
218     elif [ "$ctl" = "confirm" ]; then
219       REDIRECT "${SCRIPT_NAME}?newbm=${fid}&nbmurl=${url}&nbmname=${name}"
220     else
221       REDIRECT "${SCRIPT_NAME}#${fid}"
222     fi
223     ;;
224   modbookmark)
225     bid="$(POST bid | checkid)"
226     name="$(POST name |head -n1)"
227     url="$(POST url |head -n1)"
228     file="$(grep -lm1 "^${bid}" "${BDB}"/????????????.bm |head -n1)"
229     if [ -n "$name" -a -n "$url" -a "$(POST control)" = confirm -a -w "$file" ]; then
230       sed -rni "/^${bid}\t/!p; /^${bid}\t/i${bid}\t$(HTML "$name")\t${url}" "$file"
231       git -C "${BDB}" commit -m "Modified Bookmark: ${name} (${bid}) in ${file##*/}" -- "${file##*/}" >&-
232     fi
233     getFavicon "$url" "$bid"
234     REDIRECT "${SCRIPT_NAME}#${fid}"
235     ;;
236   movebookmark)
237     bid="$(POST bid | checkid)"
238     fid="$(POST target | checkid)"
239     sfile="$(grep -lm1 "^${bid}" "${BDB}"/????????????.bm |head -n1)"
240     tfile="${BDB}/${fid}.bm"
241
242     if [ "$(POST control)" = confirm -a -n "$bid" -a -w "$sfile" -a -w "$tfile" ]; then
243       grep -m1 "^${bid}" "$sfile" >>"$tfile" \
244       && sed -ri "0,/^${bid}/{/^${bid}/d;}" "$sfile"
245       git -C "${BDB}" commit -m "Moved Bookmark ${bid} from ${sfile##*/} to ${tfile##*/}" \
246           -- "${sfile##*/}" "${tfile##*/}" >&-
247     fi
248     REDIRECT "${SCRIPT_NAME}#${fid}"
249     ;;
250   bmup)
251     fid="$(GET fid |checkid)"
252     bid="$(GET bid |checkid)"
253     file="${BDB}/${fid}.bm"
254
255     if [ -n "$bid" -a -n "$fid" ] && grep -q "^${bid}" "$file"; then
256       sed -ri ":X;\$bY;N;bX;:Y; s;(\n[^\n]+)(\n${bid}\t[^\n]+);\2\1;;" "$file"
257       git -C "${BDB}" commit -m "Modified bookmark order in ${fid} (raised ${bid})" -- "${fid}.bm" >&-
258     fi
259     REDIRECT "${SCRIPT_NAME}#${fid}"
260     ;;
261   bmdn)
262     fid="$(GET fid |checkid)"
263     bid="$(GET bid |checkid)"
264     file="${BDB}/${fid}.bm"
265
266     if [ -n "$bid" -a -n "$fid" ] && grep -q "^${bid}" "$file"; then
267       sed -ri ":X;\$bY;N;bX;:Y; s;(\n${bid}\t[^\n]+)(\n[^\n]+);\2\1;;" "$file"
268       git -C "${BDB}" commit -m "Modified bookmark order in ${fid} (lowered ${bid})" -- "${fid}.bm" >&-
269     fi
270     REDIRECT "${SCRIPT_NAME}#${fid}"
271     ;;
272   query)
273     fid="$(POST fid |checkid)"
274     bid="$(POST bid |checkid)"
275     file="${BDB}/${fid}.bm"
276     query="$(URL "$(POST query)")"
277
278
279     url="$(grep -m1 "^${bid}" "$file" |cut -f3-)"
280     urlpfx="${url%\{@\}*}"
281     urlsfx="${url#*\{@\}}"
282
283     REDIRECT "${urlpfx}${query}${urlsfx}"
284     ;;
285 esac
286
287 bookmarkgen(){
288   fid="$(GET newbm |checkid)"
289   name="$(GET nbmname)"
290   url="$(GET nbmurl)"
291   file="${BDB}/${fid}.bm"
292
293   if [ -z "$name" -a -n "$url" ]; then
294     name="$(wget -O- "$url" \
295             | head -c4096 \
296             | sed -rn ':X;$bY;N;bX;:Y; s;^.*<title[^>]*>([^<]+)<.*$;\1;p;'
297           )"
298   fi
299
300   if [ -n "$fid" -o -n "$name" -o -n "$url" ]; then
301     [ "$url" = "${url#*://}" ] && url="http://$url"
302     cat <<-EOF
303         <form class="dialog newbookmark" method="POST" action="${SCRIPT_NAME}?action=newbookmark">
304           <h1>New Bookmark</h1>
305           <label>Folder:</label>
306           <select name="fid">
307             $(list_folders |while read id n; do
308               [ "$id" = "$fid" ] \
309               && printf '<option value="%s" selected="selected">%s</option>' "$id" "$n" \
310               || printf '<option value="%s">%s</option>' "$id" "$n"
311             done)
312             $(printf '<option value="%s">%s</option>' $(list_folders))
313           </select>
314           <label>Name:</label>
315           <input type="text" name="name" value="$(HTML ${name})")" placeholder="Name" />
316           <label>URL:</label>
317           <input type="text" name="url"  value="$(HTML "${url}")")" placeholder="URL" />
318           <button type="submit" name="control" value="confirm">OK</button>
319           <button type="submit" name="control" value="cancel">Cancel</button>
320         </form>
321         EOF
322   fi
323 }
324
325 bookmarkmod(){
326   bmod="$(GET bmodify |checkid)"
327   bmove="$(GET bmove |checkid)"
328
329   if [ -n "$bmod" ]; then
330     file="$(grep -lm1 "^${bmod}" "${BDB}/"????????????.bm |head -n1)"
331     read bid name url <<-EOF
332         $(grep -m1 "^${bmod}" "$file")
333         EOF
334     cat <<-EOF
335         <form class="dialog modbookmark" method="POST" action="${SCRIPT_NAME}?action=modbookmark">
336           <input type="hidden" name="bid" value="${bid}" />
337           <h1>Modify: ${name}</h1>
338           <label class="tab">Modify</label>
339           <a class="tab" href="${SCRIPT_NAME}?bmove=${bid}">Move</a>
340           <label>Name:</label>
341           <input type="text" name="name" value="${name}")" placeholder="Name" />
342           <label>URL:</label>
343           <input type="text" name="url"  value="$(HTML "${url}")")" placeholder="URL" />
344           <button type="submit" name="control" value="confirm">OK</button>
345           <button type="submit" name="control" value="cancel">Cancel</button>
346         </form>
347         EOF
348   elif [ -n "$bmove" ]; then
349     file="$(grep -lm1 "^${bmove}" "${BDB}/"????????????.bm |head -n1)"
350     read bid name url <<-EOF
351         $(grep -m1 "^${bmove}" "$file")
352         EOF
353     cat <<-EOF
354         <form class="dialog modbookmark" method="POST" action="${SCRIPT_NAME}?action=movebookmark">
355           <input type="hidden" name="bid" value="${bid}" />
356           <h1>Move: ${name}</h1>
357           <a class="tab" href="${SCRIPT_NAME}?bmodify=${bid}">Modify</a>
358           <label class="tab">Move</label>
359           <label>Move to Folder:</label>
360           <select name="target">
361             $(printf '<option value="%s">%s</option>' $(list_folders))
362           </select>
363           <button type="submit" name="control" value="confirm">OK</button>
364           <button type="submit" name="control" value="cancel">Cancel</button>
365         </form>
366         EOF
367   fi
368 }
369
370 show_bookmarks(){
371   fid="$1"
372   bmodify="$(GET bmodify |checkid)"
373
374   tail -n+2 "${BDB}/${fid}.bm" \
375   | while read bid name url; do
376     if [ "${url%\{@\}*}" = "${url}" ]; then
377       cat <<-EOF
378         <div class="bookmark">
379           <a class="modify" href="${SCRIPT_NAME}?bmodify=${bid}">Modify</a>
380           <a class="link" target="_blank" href="$(HTML "${url}")")">
381             <img alt="@" src="${BDB}/favicons/${bid}.ico"/>${name}
382           </a>
383           <a class="bmove" href="${SCRIPT_NAME}?action=bmup&fid=${fid}&bid=${bid}">Move up</a>
384           <a class="bmove" href="${SCRIPT_NAME}?action=bmdn&fid=${fid}&bid=${bid}">Move down</a>
385         </div>
386         EOF
387     else
388       cat <<-EOF
389         <form class="bookmark" target="_blank" method="POST" action="${SCRIPT_NAME}?action=query">
390           <a class="modify" href="${SCRIPT_NAME}?bmodify=${bid}">Modify</a>
391           <input type="hidden" name="fid" value="$fid" />
392           <input type="hidden" name="bid" value="$bid" />
393           <img alt="${name}" src="${BDB}/favicons/${bid}.ico"/>
394           <input name="query" placeholder="$name"/>
395           <a class="bmove" href="${SCRIPT_NAME}?action=bmup&fid=${fid}&bid=${bid}">Move up</a>
396           <a class="bmove" href="${SCRIPT_NAME}?action=bmdn&fid=${fid}&bid=${bid}">Move down</a>
397         </form>
398         EOF
399     fi
400   done
401 }
402
403 foldermod(){
404   fmodify="$(GET fmodify |checkid )"
405   fdelete="$(GET fdelete |checkid )"
406   fmove="$(GET fmove |checkid )"
407
408   if [ -n "$fmodify" ]; then
409     read fid fname order <"${BDB}/${fmodify}.bm"
410     cat <<-EOF
411         <form class="dialog modfolder rename" method="POST" action="${SCRIPT_NAME}?action=modfolder">
412           <h1>Rename Folder: ${fname}</h1>
413           <input type="hidden" name="fid" value="${fid}" />
414           <label class="tab">Rename</label>
415           <a class="tab"
416             href="${SCRIPT_NAME}?fdelete=${fid}">Delete</a>
417           <a class="tab"
418             href="${SCRIPT_NAME}?fmove=${fid}">Move</a>
419           <input type="text" name="name" value="${fname}" />
420           <button type="submit" name="control" value="confirm">OK</button>
421           <button type="submit" name="control" value="cancel">Cancel</button>
422         </form>
423         EOF
424   elif [ -n "$fdelete" ]; then
425     read fid fname order <"${BDB}/${fdelete}.bm"
426     cat <<-EOF
427         <form class="dialog modfolder delete" method="POST" action="${SCRIPT_NAME}?action=delfolder">
428           <h1>Delete Folder: ${fname}</h1>
429           <input type="hidden" name="fid" value="${fid}" />
430           <a class="tab" href="${SCRIPT_NAME}?fmodify=${fid}">Rename</a>
431           <label class="tab">Delete</label>
432           <a class="tab"
433             href="${SCRIPT_NAME}?fmove=${fid}">Move</a>
434           <label>Pass Bookmarks on to:</label>
435           <select name="target">
436             $(printf '<option value="%s">%s</option>' $(list_folders |grep -v "^${fid}"))
437             <option value="____________">(discard)</option>
438           </select>
439           <button type="submit" name="control" value="confirm">OK</button>
440           <button type="submit" name="control" value="cancel">Cancel</button>
441         </form>
442         EOF
443   elif [ -n "$fmove" ]; then
444     read fid fname order <"${BDB}/${fmove}.bm"
445     cat <<-EOF
446         <form class="dialog modfolder move" method="POST" action="${SCRIPT_NAME}?action=movefolder">
447           <h1>Move Folder: ${fname}</h1>
448           <input type="hidden" name="fid" value="${fid}" />
449           <a class="tab" href="${SCRIPT_NAME}?fmodify=${fid}">Rename</a>
450           <a class="tab" href="${SCRIPT_NAME}?fdelete=${fid}">Delete</a>
451           <label class="tab">Move</label>
452           <label>Move before folder:</label>
453           <select name="target">
454             $(printf '<option value="%s">%s</option>' $(list_folders |grep -v "^${fid}"))
455             <option value="____________">(last)</option>
456           </select>
457           <button type="submit" name="control" value="confirm">OK</button>
458           <button type="submit" name="control" value="cancel">Cancel</button>
459         </form>
460         EOF
461   fi
462 }
463
464 show_folders(){
465   list_folders \
466   | while read fid fname order; do
467     file="${BDB}/${fid}.bm"
468     cache="${BDB}/${fid}.cache"
469     if [ "${cache}" -nt "${file}" -a "${cache}" -nt "$0" ]; then
470       cat "$cache"
471     else
472       tee "$cache" <<-EOF
473         <section class="folder" id="${fid}">
474           <h1>${fname}</h1>
475           <a class="modify" href="${SCRIPT_NAME}?fmodify=${fid}">Modify folder "${fname}"</a>
476           $(show_bookmarks "$fid")
477           <a class="new bookmark" href="${SCRIPT_NAME}?newbm=${fid}">New Bookmark</a>
478         </section>
479         EOF
480     fi
481   done
482 }
483
484 SET_COOKIE +8640000 "id=${COKID}"      # Refresh Cookie
485 printf 'Content-Type: text/html; charset=utf-8\r\n\r\n'
486
487 cat <<EOF
488 <!DOCTYPE HTML>
489 <HTML><head>
490   <meta name="viewport" content="width=device-width" />
491   <title>Bookman - Your Collection</title>
492   <link rel="stylesheet" type="text/css" href="bookmarks.css" />
493 </head><body id="collection">
494   $(foldermod)
495   $(bookmarkmod)
496   $(bookmarkgen)
497   $(show_folders)
498   <form class="newfolder" method="POST" action="${SCRIPT_NAME}?action=newfolder">
499     <input type="text" name="name" value="" placeholder="New Folder" />
500     <button type="submit">New</button>
501   </form>
502   <footer>
503     <a href="${SCRIPT_NAME}?id=${COKID}">Permalink for this Collection</a>
504   </footer>
505 </body></HTML>
506 EOF
507
508 #set filetype=sh