tinycm view index.cgi @ rev 95

fic blog archive sorting
author Christophe Lincoln <pankso@slitaz.org>
date Fri Feb 17 13:49:00 2017 +0100 (2017-02-17)
parents d6f01c5019bb
children 69214a677729
line source
1 #!/bin/sh
2 #
3 # TinyCM - Small, fast and elegant CGI/SHell Content Manager
4 #
5 # Copyright (C) 2012-2017 SliTaz GNU/Linux - BSD License
6 #
7 . /usr/lib/slitaz/httphelper.sh
9 # Let's have a peer site config file with a .cgi extension so content
10 # is secure even if left in a web server directory.
11 . ./config.cgi
13 tiny="$PWD"
14 content="content"
15 wiki="$content/wiki"
16 index="index"
17 cache="cache"
18 plugins="plugins"
19 tmp="/tmp/tinycm"
20 sessions="$tmp/sessions"
21 script="$SCRIPT_NAME"
22 activity="$cache/log/activity.log"
24 # Content negotiation for Gettext
25 IFS=","
26 for lang in $HTTP_ACCEPT_LANGUAGE
27 do
28 lang=${lang%;*} lang=${lang# } lang=${lang%-*}
29 case "$lang" in
30 en) lang="C" && break ;;
31 fr) lang="fr_FR" && break ;;
32 pt) lang="pt_BR" && break ;;
33 ru) lang="ru_RU" && break ;;
34 esac
35 done
36 unset IFS
37 export LANG=$lang LC_ALL=$lang
39 # Internationalization
40 . /usr/bin/gettext.sh
41 TEXTDOMAIN='tinycm'
42 export TEXTDOMAIN
44 #
45 # Functions
46 #
48 # Used by edit to display language name and the language box. This is
49 # for CM content not gettext support.
50 get_lang() {
51 dlang=$(echo $d | cut -d "/" -f 1)
52 doc=${d#$dlang/}
53 echo '<div id="lang">'
54 for l in $LANGUAGES
55 do
56 case $dlang in
57 en) i18n="English" ;;
58 fr) i18n="Français" ;;
59 pt) i18n="Português" ;;
60 ru) i18n="Русский" ;;
61 *) i18n="*" ;;
62 esac
63 echo "<a href='?d=$l/$doc'>$l</a>"
64 done
65 echo '</div>'
66 }
68 # HTML 5 header.
69 html_header() {
70 if [ -f "$tiny/lib/header.html" ]; then
71 cat $tiny/lib/header.html | sed -e s!'%TITLE%'!"$TITLE - $d"!g
72 else
73 cat << EOT
74 <!DOCTYPE html>
75 <html xmlns="http://www.w3.org/1999/xhtml">
76 <head>
77 <title>$TITLE</title>
78 <meta charset="utf-8" />
79 <style type="text/css">body { margin: 40px 120px; }</style>
80 </head>
81 <body>
82 <!-- Content -->
83 <div id="content">
84 EOT
85 fi
86 }
88 # HTML 5 footer.
89 html_footer() {
90 if [ -f "$tiny/lib/footer.html" ]; then
91 cat $tiny/lib/footer.html
92 else
93 cat << EOT
95 <!-- End content -->
96 </div>
98 <div id="footer">
99 I &hearts; <a href="http://tinycm.slitaz.org/">TinyCM</a>
100 </div>
102 </body>
103 </html>
104 EOT
105 fi
106 }
108 # Default index if missing
109 default_index() {
110 mkdir -p "$wiki"
111 cat > $wiki/$index.txt << EOT
112 ==== Welcome ====
114 <p>
115 This is the default index page of your TinyCM, you can login then start to
116 edit and add some content. You can read the help about text formating
117 and functions: [Help page|en/help]
118 </p>
120 EOT
121 }
123 # Log main activity.
124 log_activity() {
125 [ -d "$cache/log" ] || mkdir -p ${cache}/log
126 #gravatar="$(get_gravatar $MAIL 24)"
127 grep ^[A-Z] | \
128 sed s"#^[A-Z]\([^']*\)#$user|$(date '+%Y-%m-%d')|\0#" \
129 >> $cache/log/activity.log
130 }
132 # Log documents activity.
133 log() {
134 grep ^[A-Z] | \
135 sed s"#^[A-Z]\([^']*\)#$(date '+%Y-%m-%d %H:%M') : \0#" \
136 >> $cache/$d/activity.log
137 }
139 # Check if user is auth
140 check_auth() {
141 auth="$(COOKIE auth)"
142 user="$(echo $auth | cut -d ":" -f 1)"
143 md5cookie="$(echo $auth | cut -d ":" -f 2)"
144 [ -f "$sessions/$user" ] && md5session="$(cat $sessions/$user)"
145 if [ "$md5cookie" == "$md5session" ] && [ "$auth" ]; then
146 . $PEOPLE/$user/account.conf
147 return 0
148 else
149 return 1
150 fi
151 }
153 # Check if user is admin
154 admin_user() {
155 fgrep -q 'ADMIN_USER="yes"' ${PEOPLE}/${user}/account.conf
156 }
158 # Authenticated or not
159 user_box() {
160 if check_auth; then
161 cat << EOT
163 <div id="user">
164 <a href="$script?user=$user">$(get_gravatar $MAIL 20)</a>
165 <a href="$script?logout">Logout</a>
166 </div>
168 EOT
169 else
170 cat << EOT
172 <div id="user">
173 <a href="$script?login"><img src="images/avatar.png" alt="[ User ]" /></a>
174 <a href="$script?login">Login</a>
175 </div>
177 EOT
178 fi
179 cat << EOT
180 <!--
181 <div id="search">
182 <form method="get" action="$script">
183 <input type="text" name="search" placeholder="$(gettext "Search")" />
184 </form>
185 </div>
186 -->
187 EOT
188 }
190 # Link for online signup if enabled.
191 online_signup() {
192 if [ "$ONLINE_SIGNUP" == "yes" ]; then
193 echo -n "<p><a href='$script?signup'>"
194 gettext "Create a new account"
195 echo '</a></p>'
196 fi
197 }
199 # Login page
200 login_page() {
201 cat << EOT
202 <h2>$(gettext "Login")</h2>
204 <div id="account-info">
205 $(gettext "No account yet or trouble with your account? Please send
206 a request to $ADMIN_MAIL with your real name, user name, mail and password.")
207 $(online_signup)
208 </div>
210 <div id="login">
211 <form method="post" action="$script">
212 <input type="text" name="auth" placeholder="$(gettext "User name")" />
213 <input type="password" name="pass" placeholder="$(gettext "Password")" />
214 <div>
215 <input type="submit" value="Login" /> $error
216 </div>
217 </form>
218 </div>
220 <div style="clear: both;"></div>
221 EOT
222 }
224 # Signup page
225 signup_page() {
226 cat << EOT
228 <div id="signup">
229 <form method="post" name="signup" action="$script" onsubmit="return checkSignup();">
230 <input type="hidden" name="signup" value="new" />
231 <input type="text" name="name" placeholder="$(gettext "Real name")" />
232 <input type="text" name="user" placeholder="$(gettext "User name")" />
233 <input type="text" name="mail" placeholder="$(gettext "Email")" />
234 <input type="password" name="pass" placeholder="$(gettext "Password")" />
235 <div>
236 <input type="submit" value="$(gettext "Create new account")" />
237 </div>
238 </form>
239 </div>
241 EOT
242 }
244 # Create a new user in AUTH_FILE and PEOPLE
245 new_user_config() {
246 if [ ! -f "$AUTH_FILE" ];then
247 touch $AUTH_FILE
248 chmod 0600 $AUTH_FILE
249 fi
250 key=$(echo -n "$user:$mail:$pass" | md5sum | awk '{print $1}')
251 echo "$user:$pass" >> $AUTH_FILE
252 mkdir -p $PEOPLE/$user/
253 cat > $PEOPLE/$user/account.conf << EOT
254 # SliTaz user configuration
255 #
257 NAME="$name"
258 USER="$user"
259 MAIL="$mail"
260 KEY="$key"
262 EOT
263 chmod 0600 $PEOPLE/$user/account.conf
264 # First created user is admin
265 if [ $(ls ${PEOPLE} | wc -l) == "1" ]; then
266 echo 'ADMIN_USER="yes"' >> $PEOPLE/$user/account.conf
267 fi
268 }
270 # The CM style parser. Just a title, simple text formatting and internal
271 # links, as well as images and use HTML for other stuff. Keep it fast!
272 # To make TinyCM as easy as possible we have a small HTML editor/helper
273 # written in Javascript
274 wiki_parser() {
275 doc="[0-9a-zA-Z\.\#/~\_%=\?\&,\+\:@;!\(\)\*\$'\-]*"
276 sed \
277 -e s"#====\([^']*\)====#<h2>\1</h2>#"g \
278 -e s"#===\([^']*\)===#<h3>\1</h3>#"g \
279 -e s"#==\([^']*\)==#<h4>\1</h4>#"g \
280 -e s"#\*\*\([^']*\)\*\*#<b>\1</b>#"g \
281 -e s"#''\([^']*\)''#<em>\1</em>#"g \
282 -e s"#__\([^']*\)__#<u>\1</u>#"g \
283 -e s"#\[\([^]]*\)|\($doc\)\]#<a href='$script?d=\2'>\1</a>#"g \
284 -e s"#\[\([^]]*\)!\($doc\)\]#<a href='\2'>\1</a>#"g \
285 -e s"#\[\(http://*[^]]*.png\)\]#<img src='\1' />#"g \
286 -e s"#\[\([^]]*.png\)\]#<img src='content/cloud/\1' />#"g \
287 -e s"#@\([^']*\)@#<a href='$script?user=\1'>\1</a>#"g
288 }
290 link_user() {
291 echo "<a href='$(basename $script)?user=$user'>$user</a>"
292 }
294 # Save a document. Do we need more than 1 backup and diff ?
295 save_document() {
296 mkdir -p $cache/$d $(dirname $wiki/$d)
297 # May be a new page.
298 if [ ! -f "$wiki/$d.txt" ]; then
299 new=0
300 touch $wiki/$d.txt
301 fi
302 cp $wiki/$d.txt $cache/$d/last.bak
303 sed "s/$(echo -en '\r') /\n/g" > $wiki/$d.txt << EOT
304 $(GET content)
305 EOT
306 diff $cache/$d/last.bak $wiki/$d.txt > $cache/$d/last.diff
307 # Log
308 if [ "$new" ]; then
309 echo "Page created by: $(link_user)" | log
310 echo "New document: <a href='$script?d=$d'>$d</a>" | log_activity
311 if [ "$HG" == "yes" ]; then
312 cd $content && hg -q add
313 hg commit -q -u "$NAME <$MAIL>" -m "Created new document: $d"
314 cd $tiny
315 fi
316 else
317 # Here we may clean log: cat && tail -n 40
318 echo "Page edited by: $(link_user)" | log
319 if [ "$HG" == "yes" ]; then
320 cd $content && hg commit -q -u "$NAME <$MAIL>" \
321 -m "Edited document: $d"
322 cd $tiny
323 fi
324 fi
325 }
327 # CM tools (edit, diff, etc) for auth users
328 wiki_tools() {
329 if check_auth; then
330 cat << EOT
331 <div id="tools">
332 <a href="$script?edit=$d">$(gettext "Edit document")</a>
333 <a href="$script?log=$d">$(gettext "File log")</a>
334 <a href="$script?diff=$d">$(gettext "Last diff")</a>
335 $PLUGINS_TOOLS
336 EOT
337 [ "$HG" == "yes" ] && echo "<a href='$script?hg'>Hg Log</a>"
338 echo "</div>"
339 fi
340 }
342 # Built-in tools such as log/ls and PLUGINS_TOOLS
343 tiny_tools() {
344 if check_auth; then
345 cat << EOT
346 <div id='tools'>
347 <a href='$script?log'>Activity log</a>
348 <a href='$script?ls'>Pages list</a>
349 $PLUGINS_TOOLS
350 </div>
351 EOT
352 fi
353 }
355 # Get and display Gravatar image: get_gravatar email size
356 # Link to profile: <a href="http://www.gravatar.com/$md5">...</a>
357 get_gravatar() {
358 email=$1
359 size=$2
360 [ "$size" ] || size=48
361 url="http://www.gravatar.com/avatar"
362 md5=$(md5crypt $email)
363 echo "<img src='$url/$md5?d=identicon&s=$size' alt='&lowast;' />"
364 }
366 # List hg logs
367 hg_log() {
368 cd $content
369 cat << EOT
370 <table>
371 <thead>
372 <td>$(gettext "User")</td>
373 <td>$(gettext "Description")</td>
374 <td>$(gettext "Revision")</td>
375 </thead>
376 EOT
377 hg log --template "<tr><td>{author}</td><td>{desc}</td><td>{rev}</td></tr>\n"
378 echo '</table>'
379 }
381 #
382 # POST actions
383 #
385 case " $(POST) " in
386 *\ auth\ *)
387 # Authenticate user. Create a session file in $sessions to be used
388 # by check_auth. We have the user login name and a peer session
389 # md5 string in the COOKIE.
390 user="$(POST auth)"
391 pass="$(md5crypt "$(POST pass)")"
392 valid=$(fgrep "${user}:" $AUTH_FILE | cut -d ":" -f 2)
393 if [ "$pass" == "$valid" ] && [ "$pass" != "" ]; then
394 md5session=$(echo -n "$$:$user:$pass:$$" | md5sum | awk '{print $1}')
395 [ -d $sessions ] || mkdir -p $sessions
396 date '+%Y-%m-%d' > ${PEOPLE}/${user}/last
397 echo "$md5session" > $sessions/$user
398 header "Location: $script" \
399 "Set-Cookie: auth=$user:$md5session; HttpOnly"
400 else
401 header "Location: $script?login&error"
402 fi ;;
403 *\ signup\ *)
404 # POST action for signup
405 name="$(POST name)"
406 user="$(POST user)"
407 mail="$(POST mail)"
408 pass="$(md5crypt "$(POST pass)")"
409 if ! grep "^${user}:" $AUTH_FILE; then
410 new_user_config
411 header "Location: $script?login"
412 else
413 header
414 html_header
415 user_box
416 echo "<h2>$(gettext 'User already exists:') $user</h2>"
417 html_footer
418 fi ;;
419 esac
421 #
422 # Plugins
423 #
424 for p in $(ls -1 $plugins)
425 do
426 [ -f "$plugins/$p/$p.conf" ] && . $plugins/$p/$p.conf
427 [ -x "$plugins/$p/$p.cgi" ] && . $plugins/$p/$p.cgi
428 done
430 #
431 # GET actions
432 #
434 case " $(GET) " in
435 *\ edit\ *)
436 d="$(GET edit)"
437 header
438 html_header
439 user_box
440 get_lang
441 wiki_tools
442 if check_auth; then
443 cat << EOT
444 <h2>$(gettext "Edit $doc [ $i18n ]")</h2>
446 <div id="edit">
448 <form method="get" action="$script" name="editor">
449 <input type="hidden" name="save" value="$d" />
450 <textarea name="content">$(cat "$wiki/$d.txt")</textarea>
451 <input type="submit" value="$(gettext "Save document")" />
452 $(gettext "Code Helper:")
453 $(cat lib/jseditor.html)
454 </form>
456 </div>
457 EOT
458 else
459 gettext "You must be logged in to edit pages"
460 fi
461 html_footer ;;
463 *\ save\ *)
464 d="$(GET save)"
465 if check_auth; then
466 save_document
467 fi
468 header "Location: $script?d=$d" ;;
470 *\ log\ *)
471 d="$(GET log)"
472 header
473 html_header
474 user_box
475 # Main activity
476 if [ "$d" == "log" ]; then
477 tiny_tools
478 echo "<h2>$(gettext "Activity log")</h2>"
479 echo '<pre>'
480 if [ -f "$cache/log/activity.log" ]; then
481 IFS="|"
482 tac $cache/log/activity.log | while read USER DATE LOG
483 do
484 . ${PEOPLE}/${USER}/account.conf
485 cat << EOT
486 <a href='$script?user=$USER'>$(get_gravatar $MAIL 24)</a>\
487 <span class='date'>$DATE -</span> $LOG
488 EOT
489 done
490 unset IFS
491 else
492 gettext "No activity log yet"; echo
493 fi
494 echo '</pre>'
495 html_footer && exit 0
496 fi
497 # Document activity
498 get_lang
499 wiki_tools
500 echo "<h2>$(gettext "Activity for:") <a href='$script?d=$d'>$d</a></h2>"
501 echo '<pre>'
502 if [ -f "$cache/$d/activity.log" ]; then
503 tac $cache/$d/activity.log
504 else
505 gettext "No log for: $d"; echo
506 fi
507 echo '</pre>'
508 html_footer ;;
510 *\ ls\ *)
511 d="Document list"
512 header
513 html_header
514 user_box
515 tiny_tools
516 [ ! check_auth ] && auth=0
517 echo "<h2>$(gettext "Pages list")</h2>"
518 echo '<pre>'
519 cd ${wiki}
520 for d in $(find . -type f | sed s'/.\///')
521 do
522 echo -n "<a href='$script?d=${d%.txt}'>${d%.txt}</a>"
523 if [ "$auth" ]; then
524 cat << EOT
525 : <a href="$script?edit=$d">$(gettext "Edit")</a> || \
526 <a href="$script?rm=$d">$(gettext "Remove")</a>
527 EOT
528 else
529 echo ""
530 fi
531 done && unset auth
532 echo '</pre>'
533 html_footer ;;
535 *\ rm\ *)
536 [ ! check_auth ] && exit 1
537 d="$(GET rm)"
538 rm ${wiki}/"${d}"
539 rm -rf ${cache}/"${d%.txt}"
540 header "Location: $script?ls" ;;
542 *\ diff\ *)
543 d="$(GET diff)"
544 date="last"
545 header
546 html_header
547 user_box
548 get_lang
549 wiki_tools
550 echo "<h2>$(gettext "Diff for:") <a href='$script?d=$d'>$d</a></h2>"
551 echo '<pre>'
552 if [ -f "$cache/$d/$date.diff" ]; then
553 cat $cache/$d/$date.diff | sed \
554 -e 's|&|\&amp;|g' -e 's|<|\&lt;|g' -e 's|>|\&gt;|g' \
555 -e s"#^-\([^']*\).#<span style='color: red;'>\0</span>#"g \
556 -e s"#^+\([^']*\).#<span style='color: green;'>\0</span>#"g \
557 -e s"#@@\([^']*\)@@#<span style='color: blue;'>@@\1@@</span>#"g
558 else
559 gettext "No diff for:"; echo " $d"
560 fi
561 echo '</pre>'
562 html_footer ;;
564 *\ login\ *)
565 # The login page
566 d="Login"
567 [ "$(GET error)" ] && \
568 error="<p class="error">$(gettext "Bad login or pass")</p>"
569 header
570 html_header
571 user_box
572 login_page
573 html_footer ;;
575 *\ signup\ *)
576 # The login page
577 d="$(gettext "Sign Up")"
578 header
579 html_header
580 user_box
581 echo "<h2>$d</h2>"
582 if [ "$ONLINE_SIGNUP" == "yes" ]; then
583 signup_page
584 else
585 gettext "Online registration is disabled"
586 fi
587 html_footer ;;
589 *\ logout\ *)
590 # Set a Cookie in the past to logout.
591 expires="Expires=Wed, 01-Jan-1980 00:00:00 GMT"
592 if check_auth; then
593 rm -f "$sessions/$user"
594 header "Location: $script" "Set-Cookie: auth=none; $expires; HttpOnly"
595 fi ;;
597 *\ user\ *)
598 # Basic user profile. Use the users plugin for more functions
599 d="$(GET user)"
600 last="$(cat $PEOPLE/"$(GET user)"/last)"
601 header
602 html_header
603 user_box
604 . $PEOPLE/"$(GET user)"/account.conf
605 cat << EOT
606 <h2>$(get_gravatar $MAIL) $NAME</h2>
608 <pre>
609 $(gettext "User name :") $USER
610 $(gettext "Last login :") $last
611 </pre>
612 EOT
613 html_footer ;;
615 *\ hg\ *)
616 d="Hg Log"
617 header
618 html_header
619 user_box
620 [ "$HG" != "yes" ] && gettext "Hg is disabled" && exit 0
621 [ ! -x /usr/bin/hg ] && gettext "Hg is not installed" && exit 0
622 echo "<h2>$d</h2>"
623 case " $(GET hg) " in
624 *\ init\ *)
625 if check_auth; then
626 [ -d "$content/.hg" ] && exit 0
627 echo '<pre>'
628 gettext "Executing: hg init"; echo
629 cd $content/ && hg init
630 echo '[hooks]' > .hg/hgrc
631 echo 'incoming = hg update' >> .hg/hgrc
632 gettext "Adding current content and committing"; echo
633 [ ! -f "$wiki/index.txt" ] && default_index
634 hg add && hg commit -u "$NAME <$MAIL>" \
635 -m "Initial commit with current content"
636 echo '</pre>' && cd ..
637 fi ;;
638 esac
639 hg_log
640 html_footer ;;
642 *)
643 # Display requested page
644 d="$(GET d)"
645 [ "$d" ] || d=$index
646 header
647 html_header
648 user_box
649 get_lang
651 # Generate a default index on first run
652 if [ ! -f "$wiki/$index.txt" ]; then
653 if ! default_index; then
654 echo "<pre class='error'>Directory : content/ is not writeable</pre>"
655 html_footer && exit 0
656 fi
657 fi
659 # Check cache dir
660 if [ ! -w "$cache" ]; then
661 echo "<pre class='error'>Directory : cache/ is not writeable"
662 echo "Command : install -m 0777 -d $tiny/cache</pre>"
663 html_footer && exit 0
664 fi
666 # Hg warning if enabled but not initiated
667 if [ "$HG" == "yes" ] && [ ! -d "$content/.hg" ]; then
668 echo '<p class="error box">'
669 gettext "Mercurial is enabled but no repository found"
670 echo ": <a href='$script?hg=init'>Hg init</a>"
671 echo '</p>'
672 fi
674 # Wiki tools
675 wiki_tools
677 # Wiki document
678 if [ ! -f "$wiki/$d.txt" ]; then
679 echo "<h2>$d</h2>"
680 gettext "The document does not exist. You can create it or read the"
681 echo " <a href='$script?d=en/help'>help</a>"
682 else
683 if fgrep -q [NOWIKI] $wiki/$d.txt; then
684 cat $wiki/$d.txt | sed '/\[NOWIKI\]/'d
685 else
686 cat $wiki/$d.txt | wiki_parser
687 fi
688 fi
689 html_footer ;;
690 esac
692 exit 0