added git tracking
[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 '%s 303 See Other\r\n' "$SERVER_PROTOCOL"
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 '%s 404 Not Found\r\n' "$SERVER_PROTOCOL"
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 '%s 303 See Other\r\n' "$SERVER_PROTOCOL"
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" = "____________" ] || tail -n+2 "$file" >>"$tfile"; then
177         rm -f "$file" >&-
178         git -C "${BDB}" commit -m "Deleted folder ${fid}" -- "${fid}.bm" "${target}.bm" >&-
179       fi
180     fi
181     REDIRECT "${SCRIPT_NAME}#${target}"
182     ;;
183   movefolder)
184     fid="$(POST fid | checkid)"
185     target="$(POST target | checkid)"
186     file="${BDB}/${fid}.bm"
187     tfile="${BDB}/${target}.bm"
188     if [ "$target" = "____________" -a -f "$file" -a "$(POST control)" = confirm ]; then
189       read nan1 nan2 last nan3 <"${BDB}/$(list_folders |tail -n1 |cut -f1).bm"
190       sed -ri "1s;^(([^\t]+\t){2})[^\t]+(.*)$;\1$((${last:-0} + 1000))\3;;" "$file"
191     elif [ -f "$tfile" -a -f "$file" -a "$(POST control)" = confirm ]; then
192       read nan1 nan2 tid nan3 <"$tfile"
193       sed -ri "1s;^(([^\t]+\t){2})[^\t]+(.*)$;\1$((${tid:-1} -1))\2;;" "$file"
194       order_files
195     fi
196     git -C "${BDB}" commit -a -m "Modified folder order (moved ${fid})" >&-
197     REDIRECT "${SCRIPT_NAME}#${fid}"
198     ;;
199   newbookmark)
200     fid="$(POST fid | checkid)"
201     name="$(POST name |head -n1)"
202     url="$(POST url |head -n1)"
203     file="${BDB}/${fid}.bm"
204     bid="$(timeid)"
205     if [ -n "$name" -a -f "${file}" ]; then
206       printf '%s\t%s\t%s\n' "$bid" "$(HTML "$name")" "$url" >>"${file}"
207       git -C "${BDB}" commit -m "New Bookmark: ${name} in ${fid}" -- "${fid}.bm" >&-
208     fi
209     getFavicon "$url" "$bid"
210     REDIRECT "${SCRIPT_NAME}#${fid}"
211     ;;
212   modbookmark)
213     bid="$(POST bid | checkid)"
214     name="$(POST name |head -n1)"
215     url="$(POST url |head -n1)"
216     file="$(grep -lm1 "^${bid}" "${BDB}"/????????????.bm |head -n1)"
217     if [ -n "$name" -a -n "$url" -a "$(POST control)" = confirm -a -w "$file" ]; then
218       sed -rni "/^${bid}\t/!p; /^${bid}\t/i${bid}\t$(HTML "$name")\t${url}" "$file"
219       git -C "${BDB}" commit -m "Modified Bookmark: ${name} (${bid}) in ${file##*/}" -- "${file##*/}" >&-
220     fi
221     getFavicon "$url" "$bid"
222     REDIRECT "${SCRIPT_NAME}#${fid}"
223     ;;
224   movebookmark)
225     bid="$(POST bid | checkid)"
226     fid="$(POST target | checkid)"
227     sfile="$(grep -lm1 "^${bid}" "${BDB}"/????????????.bm |head -n1)"
228     tfile="${BDB}/${fid}.bm"
229
230     if [ "$(POST control)" = confirm -a -n "$bid" -a -w "$sfile" -a -w "$tfile" ]; then
231       grep -m1 "^${bid}" "$sfile" >>"$tfile" \
232       && sed -ri "0,/^${bid}/{/^${bid}/d;}" "$sfile"
233       git -C "${BDB}" commit -m "Moved Bookmark ${bid} from ${sfile##*/} to ${tfile##*/}" \
234           -- "${sfile##*/}" "${tfile##*/}" >&-
235     fi
236     REDIRECT "${SCRIPT_NAME}#${fid}"
237     ;;
238   bmup)
239     fid="$(GET fid |checkid)"
240     bid="$(GET bid |checkid)"
241     file="${BDB}/${fid}.bm"
242
243     if [ -n "$bid" -a -n "$fid" ] && grep -q "^${bid}" "$file"; then
244       sed -ri ":X;\$bY;N;bX;:Y; s;(\n[^\n]+)(\n${bid}\t[^\n]+);\2\1;;" "$file"
245       git -C "${BDB}" commit -m "Modified bookmark order in ${fid} (raised ${bid})" -- "${fid}.bm" >&-
246     fi
247     REDIRECT "${SCRIPT_NAME}#${fid}"
248     ;;
249   bmdn)
250     fid="$(GET fid |checkid)"
251     bid="$(GET bid |checkid)"
252     file="${BDB}/${fid}.bm"
253
254     if [ -n "$bid" -a -n "$fid" ] && grep -q "^${bid}" "$file"; then
255       sed -ri ":X;\$bY;N;bX;:Y; s;(\n${bid}\t[^\n]+)(\n[^\n]+);\2\1;;" "$file"
256       git -C "${BDB}" commit -m "Modified bookmark order in ${fid} (lowered ${bid})" -- "${fid}.bm" >&-
257     fi
258     REDIRECT "${SCRIPT_NAME}#${fid}"
259     ;;
260   query)
261     fid="$(POST fid |checkid)"
262     bid="$(POST bid |checkid)"
263     file="${BDB}/${fid}.bm"
264     query="$(URL "$(POST query)")"
265
266
267     url="$(grep -m1 "^${bid}" "$file" |cut -f3-)"
268     urlpfx="${url%\{@\}*}"
269     urlsfx="${url#*\{@\}}"
270
271     REDIRECT "${urlpfx}${query}${urlsfx}"
272     ;;
273 esac
274
275 bookmarkmod(){
276   bmod="$(GET bmodify |checkid)"
277   bmove="$(GET bmove |checkid)"
278
279   if [ -n "$bmod" ]; then
280     file="$(grep -lm1 "^${bmod}" "${BDB}/"????????????.bm |head -n1)"
281     read bid name url <<-EOF
282         $(grep -m1 "^${bmod}" "$file")
283         EOF
284     cat <<-EOF
285         <form class="modbookmark" method="POST" action="${SCRIPT_NAME}?action=modbookmark">
286           <input type="hidden" name="bid" value="${bid}" />
287           <h1>Modify: ${name}</h1>
288           <label class="tab">Modify</label>
289           <a class="tab" href="${SCRIPT_NAME}?bmove=${bid}">Move</a>
290           <label>Name:</label>
291           <input type="text" name="name" value="${name}")" placeholder="Name" />
292           <label>URL:</label>
293           <input type="text" name="url"  value="$(HTML "${url}")")" placeholder="URL" />
294           <button type="submit" name="control" value="confirm">OK</button>
295           <button type="submit" name="control" value="cancel">Cancel</button>
296         </form>
297         EOF
298   elif [ -n "$bmove" ]; then
299     file="$(grep -lm1 "^${bmove}" "${BDB}/"????????????.bm |head -n1)"
300     read bid name url <<-EOF
301         $(grep -m1 "^${bmove}" "$file")
302         EOF
303     cat <<-EOF
304         <form class="modbookmark" method="POST" action="${SCRIPT_NAME}?action=movebookmark">
305           <input type="hidden" name="bid" value="${bid}" />
306           <h1>Move: ${name}</h1>
307           <a class="tab" href="${SCRIPT_NAME}?bmodify=${bid}">Modify</a>
308           <label class="tab">Move</label>
309           <label>Move to Folder:</label>
310           <select name="target">
311             $(printf '<option value="%s">%s</option>' $(list_folders))
312           </select>
313           <button type="submit" name="control" value="confirm">OK</button>
314           <button type="submit" name="control" value="cancel">Cancel</button>
315         </form>
316         EOF
317   fi
318 }
319
320 show_bookmarks(){
321   fid="$1"
322   bmodify="$(GET bmodify |checkid)"
323
324   tail -n+2 "${BDB}/${fid}.bm" \
325   | while read bid name url; do
326     if [ "${url%\{@\}*}" = "${url}" ]; then
327       cat <<-EOF
328         <div class="bookmark">
329           <a class="modify" href="${SCRIPT_NAME}?bmodify=${bid}">Modify</a>
330           <a class="link" target="_blank" href="$(HTML "${url}")")">
331             <img alt="@" src="${BDB}/favicons/${bid}.ico"/>${name}
332           </a>
333           <a class="bmove" href="${SCRIPT_NAME}?action=bmup&fid=${fid}&bid=${bid}">Move up</a>
334           <a class="bmove" href="${SCRIPT_NAME}?action=bmdn&fid=${fid}&bid=${bid}">Move down</a>
335         </div>
336         EOF
337     else
338       cat <<-EOF
339         <form class="bookmark" target="_blank" method="POST" action="${SCRIPT_NAME}?action=query">
340           <a class="modify" href="${SCRIPT_NAME}?bmodify=${bid}">Modify</a>
341           <input type="hidden" name="fid" value="$fid" />
342           <input type="hidden" name="bid" value="$bid" />
343           <img alt="${name}" src="${BDB}/favicons/${bid}.ico"/>
344           <input name="query" placeholder="$name"/>
345           <a class="bmove" href="${SCRIPT_NAME}?action=bmup&fid=${fid}&bid=${bid}">Move up</a>
346           <a class="bmove" href="${SCRIPT_NAME}?action=bmdn&fid=${fid}&bid=${bid}">Move down</a>
347         </form>
348         EOF
349     fi
350   done
351 }
352
353 foldermod(){
354   fmodify="$(GET fmodify |checkid )"
355   fdelete="$(GET fdelete |checkid )"
356   fmove="$(GET fmove |checkid )"
357
358   if [ -n "$fmodify" ]; then
359     read fid fname order <"${BDB}/${fmodify}.bm"
360     cat <<-EOF
361         <form class="modfolder rename" method="POST" action="${SCRIPT_NAME}?action=modfolder">
362           <h1>Rename Folder: ${fname}</h1>
363           <input type="hidden" name="fid" value="${fid}" />
364           <label class="tab">Rename</label>
365           <a class="tab"
366             href="${SCRIPT_NAME}?fdelete=${fid}">Delete</a>
367           <a class="tab"
368             href="${SCRIPT_NAME}?fmove=${fid}">Move</a>
369           <input type="text" name="name" value="${fname}" />
370           <button type="submit" name="control" value="confirm">OK</button>
371           <button type="submit" name="control" value="cancel">Cancel</button>
372         </form>
373         EOF
374   elif [ -n "$fdelete" ]; then
375     read fid fname order <"${BDB}/${fdelete}.bm"
376     cat <<-EOF
377         <form class="modfolder delete" method="POST" action="${SCRIPT_NAME}?action=delfolder">
378           <h1>Delete Folder: ${fname}</h1>
379           <input type="hidden" name="fid" value="${fid}" />
380           <a class="tab" href="${SCRIPT_NAME}?fmodify=${fid}">Rename</a>
381           <label class="tab">Delete</label>
382           <a class="tab"
383             href="${SCRIPT_NAME}?fmove=${fid}">Move</a>
384           <label>Pass Bookmarks on to:</label>
385           <select name="target">
386             $(printf '<option value="%s">%s</option>' $(list_folders |grep -v "^${fid}"))
387             <option value="____________">(discard)</option>
388           </select>
389           <button type="submit" name="control" value="confirm">OK</button>
390           <button type="submit" name="control" value="cancel">Cancel</button>
391         </form>
392         EOF
393   elif [ -n "$fmove" ]; then
394     read fid fname order <"${BDB}/${fmove}.bm"
395     cat <<-EOF
396         <form class="modfolder move" method="POST" action="${SCRIPT_NAME}?action=movefolder">
397           <h1>Move Folder: ${fname}</h1>
398           <input type="hidden" name="fid" value="${fid}" />
399           <a class="tab" href="${SCRIPT_NAME}?fmodify=${fid}">Rename</a>
400           <a class="tab" href="${SCRIPT_NAME}?fdelete=${fid}">Delete</a>
401           <label class="tab">Move</label>
402           <label>Move before folder:</label>
403           <select name="target">
404             $(printf '<option value="%s">%s</option>' $(list_folders |grep -v "^${fid}"))
405             <option value="____________">(last)</option>
406           </select>
407           <button type="submit" name="control" value="confirm">OK</button>
408           <button type="submit" name="control" value="cancel">Cancel</button>
409         </form>
410         EOF
411   fi
412 }
413
414 show_folders(){
415   list_folders \
416   | while read fid fname order; do
417     file="${BDB}/${fid}.bm"
418     cache="${BDB}/${fid}.cache"
419     if [ "${cache}" -nt "${file}" ]; then
420       cat "$cache"
421     else
422       tee "$cache" <<-EOF
423         <section class="folder" id="${fid}">
424           <h1>${fname}</h1>
425           <a class="modify" href="${SCRIPT_NAME}?fmodify=${fid}">Modify folder "${fname}"</a>
426           $(show_bookmarks "$fid")
427           <form class="newbookmark" method="POST" action="${SCRIPT_NAME}?action=newbookmark">
428             <input type="hidden" name="fid" value="${fid}" />
429             <input type="text" name="name" value="" placeholder="Name" />
430             <input type="text" name="url"  value="" placeholder="URL" />
431             <button type="submit">New Bookmark</button>
432           </form>
433         </section>
434         EOF
435     fi
436   done
437 }
438
439 SET_COOKIE +8640000 "id=${COKID}"      # Refresh Cookie
440 printf 'Content-Type: text/html; charset=utf-8\r\n\r\n'
441
442 cat <<EOF
443 <!DOCTYPE HTML>
444 <HTML><head>
445   <title>Bookman - Your Collection</title>
446   <link rel="stylesheet" type="text/css" href="bookmarks.css" />
447 </head><body id="collection">
448   $(foldermod)
449   $(bookmarkmod)
450   $(show_folders)
451   <form class="newfolder" method="POST" action="${SCRIPT_NAME}?action=newfolder">
452     <input type="text" name="name" value="" placeholder="New Folder" />
453     <button type="submit">New</button>
454   </form>
455 </body></HTML>
456 EOF
457
458 #set filetype=sh