tinycm view index.cgi @ rev 28

Bunch of improvments and fixes
author Christophe Lincoln <pankso@slitaz.org>
date Mon Jan 06 14:49:12 2014 +0000 (2014-01-06)
parents fd29a41905a3
children 9d1c1115562a
line source
1 #!/bin/sh
2 #
3 # TinyCM - Small, fast and elegent CGI/SHell Content Manager
4 #
5 # Copyright (C) 2012-2014 SliTaz GNU/Linux - BSD License
6 #
7 . /usr/lib/slitaz/httphelper
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 po="en fr"
15 content="content"
16 wiki="$content/wiki"
17 index="index"
18 cache="cache"
19 plugins="plugins"
20 tmp="/tmp/tinycm"
21 sessions="$tmp/sessions"
22 script="$SCRIPT_NAME"
23 activity="$cache/log/activity.log"
25 # Content negotiation for Gettext
26 IFS=","
27 for lang in $HTTP_ACCEPT_LANGUAGE
28 do
29 lang=${lang%;*} lang=${lang# } lang=${lang%-*}
30 if echo "$po" | fgrep -q "$lang"; then
31 break
32 fi
33 case "$lang" in
34 en) lang="C" ;;
35 fr) lang="fr_FR" ;;
36 pt) lang="pt_BR" ;;
37 ru) lang="ru_RU" ;;
38 esac
39 done
40 unset IFS
41 export LANG=$lang LC_ALL=$lang
43 #
44 # Functions
45 #
47 # Used by edit to display language name and the language box. This is
48 # for CM content not gettext support.
49 get_lang() {
50 lang=$(echo $d | cut -d "/" -f 1)
51 doc=${d#$lang/}
52 echo '<div id="lang">'
53 for l in $LANGUAGES
54 do
55 case $lang in
56 en) i18n="English" ;;
57 fr) i18n="Français" ;;
58 pt) i18n="Português" ;;
59 ru) i18n="Русский" ;;
60 *) i18n="*" ;;
61 esac
62 echo "<a href='?d=$l/$doc'>$l</a>"
63 done
64 echo '</div>'
65 }
67 # HTML 5 header.
68 html_header() {
69 if [ -f "$tiny/lib/header.html" ]; then
70 cat $tiny/lib/header.html | sed -e s!'%TITLE%'!"$TITLE - $d"!g
71 else
72 cat << EOT
73 <!DOCTYPE html>
74 <html xmlns="http://www.w3.org/1999/xhtml">
75 <head>
76 <title>$TITLE</title>
77 <meta charset="utf-8" />
78 <style type="text/css">body { margin: 40px 120px; }</style>
79 </head>
80 <body>
81 <!-- Content -->
82 <div id="content">
83 EOT
84 fi
85 }
87 # HTML 5 footer.
88 html_footer() {
89 if [ -f "$tiny/lib/footer.html" ]; then
90 cat $tiny/lib/footer.html
91 else
92 cat << EOT
94 <!-- End content -->
95 </div>
97 <div id="footer">&hearts;</div>
99 </body>
100 </html>
101 EOT
102 fi
103 }
105 # Default index if missing
106 default_index() {
107 mkdir -p "$wiki"
108 cat > $wiki/$index.txt << EOT
109 ==== Welcome ====
111 This is the default index page of your CM, you can start to edit and adding
112 some content to your TinyCM.
114 EOT
115 }
117 # Log main activity.
118 log_activity() {
119 [ -d "$cache/log" ] || mkdir -p ${cache}/log
120 #gravatar="$(get_gravatar $MAIL 24)"
121 grep ^[A-Z] | \
122 sed s"#^[A-Z]\([^']*\)#$user|$(date '+%Y-%m-%d')|\0#" \
123 >> $cache/log/activity.log
124 }
126 # Log documents activity.
127 log() {
128 grep ^[A-Z] | \
129 sed s"#^[A-Z]\([^']*\)#$(date '+%Y-%m-%d %H:%M') : \0#" \
130 >> $cache/$d/activity.log
131 }
133 # Check if user is auth
134 check_auth() {
135 auth="$(COOKIE auth)"
136 user="$(echo $auth | cut -d ":" -f 1)"
137 md5cookie="$(echo $auth | cut -d ":" -f 2)"
138 [ -f "$sessions/$user" ] && md5session="$(cat $sessions/$user)"
139 if [ "$md5cookie" == "$md5session" ] && [ "$auth" ]; then
140 . $PEOPLE/$user/account.conf
141 return 0
142 else
143 return 1
144 fi
145 }
147 # Check if user is admin
148 admin_user() {
149 fgrep -q 'ADMIN_USER="yes"' ${PEOPLE}/${user}/account.conf
150 }
152 # Authentified or not
153 user_box() {
154 if check_auth; then
155 cat << EOT
157 <div id="user">
158 <a href="$script?user=$user">$(get_gravatar $MAIL 20)</a>
159 <a href="$script?logout">Logout</a>
160 </div>
162 EOT
163 else
164 cat << EOT
166 <div id="user">
167 <a href="$script?login"><img src="images/avatar.png" alt="[ User ]" /></a>
168 <a href="$script?login">Login</a>
169 </div>
171 EOT
172 fi
173 cat << EOT
174 <!--
175 <div id="search">
176 <form method="get" action="$script">
177 <input type="text" name="search" placeholder="$(gettext "Search")" />
178 </form>
179 </div>
180 -->
181 EOT
182 }
184 # Link for online signup if enabled.
185 online_signup() {
186 if [ "$ONLINE_SIGNUP" == "yes" ]; then
187 echo -n "<p><a href='$script?signup'>"
188 gettext "Create a new account"
189 echo '</a></p>'
190 fi
191 }
193 # Login page
194 login_page() {
195 cat << EOT
196 <h2>$(gettext "Login")</h2>
198 <div id="account-info">
199 $(gettext "No account yet or trouble with you account? Please send
200 a request to $ADMIN_MAIL with your real name, user name, mail and password.")
201 $(online_signup)
202 </div>
204 <div id="login">
205 <form method="post" action="$script">
206 <input type="text" name="auth" placeholder="$(gettext "User name")" />
207 <input type="password" name="pass" placeholder="$(gettext "Password")" />
208 <div>
209 <input type="submit" value="Login" /> $error
210 </div>
211 </form>
212 </div>
214 <div style="clear: both;"></div>
215 EOT
216 }
218 # Signup page
219 signup_page() {
220 cat << EOT
222 <div id="signup">
223 <form method="post" name="signup" action="$script" onsubmit="return checkSignup();">
224 <input type="hidden" name="signup" value="new" />
225 <input type="text" name="name" placeholder="$(gettext "Real name")" />
226 <input type="text" name="user" placeholder="$(gettext "User name")" />
227 <input type="text" name="mail" placeholder="$(gettext "Email")" />
228 <input type="password" name="pass" placeholder="$(gettext "Password")" />
229 <div>
230 <input type="submit" value="$(gettext "Create new account")" />
231 </div>
232 </form>
233 </div>
235 EOT
236 }
238 # Create a new user in AUTH_FILE and PEOPLE
239 new_user_config() {
240 if [ ! -f "$AUTH_FILE" ];then
241 touch $(DESTDIR)$(LOGIN)/auth/people
242 chmod 0600 $(DESTDIR)$(LOGIN)/auth/people
243 fi
244 key=$(echo -n "$user:$mail:$pass" | md5sum | awk '{print $1}')
245 echo "$user:$pass" >> $AUTH_FILE
246 mkdir -p $PEOPLE/$user/
247 cat > $PEOPLE/$user/account.conf << EOT
248 # SliTaz user configuration
249 #
251 NAME="$name"
252 USER="$user"
253 MAIL="$mail"
254 KEY="$key"
256 EOT
257 chmod 0600 $PEOPLE/$user/account.conf
258 # First created user is admin
259 if [ $(ls ${PEOPLE} | wc -l) == "1" ]; then
260 echo 'ADMIN_USER="yes"' >> $PEOPLE/$user/account.conf
261 fi
262 }
264 # Display user public profile.
265 public_people() {
266 cat << EOT
267 <pre>
268 Real name : $NAME
269 </pre>
270 EOT
271 # Display personnal user profile
272 if [ -f "$PEOPLE/$USER/profile.txt" ]; then
273 cat $PEOPLE/$USER/profile.txt | wiki_parser
274 fi
275 }
277 # Display authentified user profile. TODO: change password
278 auth_people() {
279 cat << EOT
280 <pre>
281 Real name : $NAME
282 Email : $MAIL
283 Secure key : $KEY
284 </pre>
285 EOT
286 # Each user can have personal profile page
287 if [ -f "$PEOPLE/$USER/profile.txt" ]; then
288 cat $PEOPLE/$USER/profile.txt | wiki_parser
289 cat << EOT
290 <div id="tools">
291 <a href="$script?edit=profile">$(gettext "Edit profile")</a>
292 </div>
293 EOT
294 else
295 cat << EOT
296 <div id="tools">
297 <a href="$script?edit=profile">$(gettext "Create a profile page")</a>
298 </div>
299 EOT
300 fi
301 }
303 # The CM style parser. Just a title, simple text formating and internal
304 # links, as well as images and use HTML for other stuff. Keep it fast!
305 # To make TinyCM as easy as possible we have a small HTML editor/helper
306 # written in Javascript
307 wiki_parser() {
308 doc="[0-9a-zA-Z\.\#/~\_%=\?\&,\+\:@;!\(\)\*\$'\-]*"
309 sed \
310 -e s"#====\([^']*\)====#<h2>\1</h2>#"g \
311 -e s"#===\([^']*\)===#<h3>\1</h3>#"g \
312 -e s"#==\([^']*\)==#<h4>\1</h4>#"g \
313 -e s"#\*\*\([^']*\)\*\*#<b>\1</b>#"g \
314 -e s"#''\([^']*\)''#<em>\1</em>#"g \
315 -e s"#__\([^']*\)__#<u>\1</u>#"g \
316 -e s"#\[\([^]]*\)|\($doc\)\]#<a href='$script?d=\2'>\1</a>#"g \
317 -e s"#\[\([^]]*\)!\($doc\)\]#<a href='\2'>\1</a>#"g \
318 -e s"#\[\(http://*[^]]*.png\)\]#<img src='\1' />#"g \
319 -e s"#\[\([^]]*.png\)\]#<img src='content/cloud/\1' />#"g
320 }
322 link_user() {
323 echo "<a href='$(basename $script)?user=$user'>$user</a>"
324 }
326 # Save a document. Do we need more than 1 backup and diff ?
327 save_document() {
328 mkdir -p $cache/$d $(dirname $wiki/$d)
329 # May be a new page.
330 if [ ! -f "$wiki/$d.txt" ]; then
331 new=0
332 touch $wiki/$d.txt
333 fi
334 cp $wiki/$d.txt $cache/$d/last.bak
335 sed "s/$(echo -en '\r') /\n/g" > $wiki/$d.txt << EOT
336 $(GET content)
337 EOT
338 diff $cache/$d/last.bak $wiki/$d.txt > $cache/$d/last.diff
339 # Log
340 if [ "$new" ]; then
341 echo "Page created by: $(link_user)" | log
343 if [ "$HG" == "yes" ]; then
344 cd $content && hg -q add
345 hg commit -q -u "$NAME <$MAIL>" -m "Created new document: $d"
346 cd $tiny
347 fi
348 else
349 # Here we may clean log: cat && tail -n 40
350 echo "Page edited by: $(link_user)" | log
351 echo "New document: <a href='$script?d=$d'>$d</a>" | log_activity
352 if [ "$HG" == "yes" ]; then
353 cd $content && hg commit -q -u "$NAME <$MAIL>" \
354 -m "Edited document: $d"
355 cd $tiny
356 fi
357 fi
358 }
360 # Save a user profile.
361 save_profile() {
362 path="$PEOPLE/$user"
363 cp -f ${path}/${d}.txt ${path}/${d}.bak
364 sed "s/$(echo -en '\r') /\n/g" > ${path}/${d}.txt << EOT
365 $(GET content)
366 EOT
367 }
369 # CM tools (edit, diff, etc).
370 wiki_tools() {
371 cat << EOT
372 <div id="tools">
373 <a href="$script?edit=$d">$(gettext "Edit document")</a>
374 <a href="$script?diff=$d">$(gettext "Last diff")</a>
375 <a href="$script?log=$d">$(gettext "Activity")</a>
376 <a href="$script?dashboard">Dashboard</a>
377 $([ "$HG" == "yes" ] && echo "<a href='$script?hg'>Hg Log</a>")
378 </div>
379 EOT
380 }
382 # Get and display Gravatar image: get_gravatar email size
383 # Link to profile: <a href="http://www.gravatar.com/$md5">...</a>
384 get_gravatar() {
385 email=$1
386 size=$2
387 [ "$size" ] || size=48
388 url="http://www.gravatar.com/avatar"
389 md5=$(md5crypt $email)
390 echo "<img src='$url/$md5?d=identicon&s=$size' alt='&lowast;' />"
391 }
393 # List hg logs
394 hg_log() {
395 cd $content
396 cat << EOT
397 <table>
398 <thead>
399 <td>$(gettext "User")</td>
400 <td>$(gettext "Description")</td>
401 <td>$(gettext "Revision")</td>
402 </thead>
403 EOT
404 hg log --template "<tr><td>{author}</td><td>{desc}</td><td>{rev}</td></tr>\n"
405 echo '</table>'
406 }
408 #
409 # POST actions
410 #
412 case " $(POST) " in
413 *\ auth\ *)
414 # Authenticate user. Create a session file in $sessions to be used
415 # by check_auth. We have the user login name and a peer session
416 # md5 string in the COOKIE.
417 user="$(POST auth)"
418 pass="$(md5crypt "$(POST pass)")"
419 valid=$(fgrep "${user}:" $AUTH_FILE | cut -d ":" -f 2)
420 if [ "$pass" == "$valid" ] && [ "$pass" != "" ]; then
421 md5session=$(echo -n "$$:$user:$pass:$$" | md5sum | awk '{print $1}')
422 [ -d $sessions ] || mkdir -p $sessions
423 echo "$md5session" > $sessions/$user
424 header "Location: $script" \
425 "Set-Cookie: auth=$user:$md5session; HttpOnly"
426 else
427 header "Location: $script?login&error"
428 fi ;;
429 *\ signup\ *)
430 # POST action for signup
431 name="$(POST name)"
432 user="$(POST user)"
433 mail="$(POST mail)"
434 pass="$(md5crypt "$(POST pass)")"
435 if ! grep "^${user}:" $AUTH_FILE; then
436 new_user_config
437 header "Location: $script?login"
438 else
439 header
440 html_header
441 user_box
442 echo "<h2>gettext "User already exists: $user"</h2>"
443 html_footer
444 fi ;;
445 esac
447 #
448 # Plugins
449 #
450 for p in $(ls -1 $plugins)
451 do
452 [ -f "$plugins/$p/$p.conf" ] && . $plugins/$p/$p.conf
453 [ -x "$plugins/$p/$p.cgi" ] && . $plugins/$p/$p.cgi
454 done
456 #
457 # GET actions
458 #
460 case " $(GET) " in
461 *\ edit\ *)
462 d="$(GET edit)"
463 header
464 html_header
465 user_box
466 get_lang
467 if check_auth; then
468 if [ "$doc" == "profile" ]; then
469 wiki="$PEOPLE/$user"
470 fi
471 cat << EOT
472 <h2>$(gettext "Edit $doc [ $i18n ]")</h2>
474 <div id="edit">
476 <form method="get" action="$script" name="editor">
477 <input type="hidden" name="save" value="$d" />
478 <textarea name="content">$(cat "$wiki/$d.txt")</textarea>
479 <input type="submit" value="$(gettext "Save document")" />
480 $(gettext "Code Helper:")
481 $(cat lib/jseditor.html)
482 </form>
484 </div>
485 EOT
486 else
487 gettext "You must be logged in to edit pages"
488 fi
489 html_footer ;;
491 *\ save\ *)
492 d="$(GET save)"
493 if check_auth; then
494 # User profile
495 if [ "$d" == "profile" ]; then
496 save_profile
497 header "Location: $script?user=$user"
498 else
499 save_document
500 fi
501 fi
502 header "Location: $script?d=$d" ;;
504 *\ log\ *)
505 d="$(GET log)"
506 header
507 html_header
508 user_box
509 # Main activity
510 if [ "$d" == "log" ]; then
511 echo "<h2>$(gettext "Activity")</h2>"
512 if check_auth; then
513 echo "<div id='tools'>"
514 echo "<a href='$script?dashboard'>Dashboard</a>"
515 echo "</div>"
516 fi
517 echo '<pre>'
518 if [ -f "$cache/log/activity.log" ]; then
519 IFS="|"
520 tac $cache/log/activity.log | while read USER DATE LOG
521 do
522 . ${PEOPLE}/${USER}/account.conf
523 cat << EOT
524 <a href='$script?user=$USER'>$(get_gravatar $MAIL 24)</a>\
525 <span class='date'>$DATE -</span> $LOG
526 EOT
527 done
528 unset IFS
529 else
530 gettext "No activity log yet"; echo
531 fi
532 echo '</pre>'
533 html_footer && exit 0
534 fi
535 get_lang
536 echo "<h2>$(gettext "Activity for:") <a href='$script?d=$d'>$d</a></h2>"
537 echo '<pre>'
538 if [ -f "$cache/$d/activity.log" ]; then
539 tac $cache/$d/activity.log
540 else
541 gettext "No log for: $d"; echo
542 fi
543 echo '</pre>'
544 if check_auth; then
545 wiki_tools
546 fi
547 html_footer ;;
549 *\ ls\ *)
550 d="Document list"
551 header
552 html_header
553 user_box
554 echo "<h2>$(gettext "Document list")</h2>"
555 if check_auth; then
556 echo "<div id='tools'>"
557 echo "<a href='$script?dashboard'>Dashboard</a>"
558 echo "</div>"
559 fi
560 echo '<pre>'
561 cd ${wiki}
562 for d in $(find . -type f | sed s'/.\///')
563 do
564 echo "<a href='$script?d=${d%.txt}'>${d%.txt}</a>"
565 done
566 echo '</pre>'
567 html_footer ;;
569 *\ diff\ *)
570 d="$(GET diff)"
571 date="last"
572 header
573 html_header
574 user_box
575 get_lang
576 echo "<h2>$(gettext "Diff for:") <a href='$script?d=$d'>$d</a></h2>"
577 echo '<pre>'
578 if [ -f "$cache/$d/$date.diff" ]; then
579 cat $cache/$d/$date.diff | sed \
580 -e 's|&|\&amp;|g' -e 's|<|\&lt;|g' -e 's|>|\&gt;|g' \
581 -e s"#^-\([^']*\).#<span style='color: red;'>\0</span>#"g \
582 -e s"#^+\([^']*\).#<span style='color: green;'>\0</span>#"g \
583 -e s"#@@\([^']*\)@@#<span style='color: blue;'>@@\1@@</span>#"g
584 else
585 gettext "No diff for: $d"; echo
586 fi
587 echo '</pre>'
588 if check_auth; then
589 wiki_tools
590 fi
591 html_footer ;;
593 *\ login\ *)
594 # The login page
595 d="Login"
596 [ "$(GET error)" ] && \
597 error="<p class="error">$(gettext "Bad login or pass")</p>"
598 header
599 html_header
600 user_box
601 login_page
602 html_footer ;;
604 *\ signup\ *)
605 # The login page
606 d="$(gettext "Sign Up")"
607 header
608 html_header
609 user_box
610 echo "<h2>$d</h2>"
611 if [ "$ONLINE_SIGNUP" == "yes" ]; then
612 signup_page
613 else
614 gettext "Online registration is disabled"
615 fi
616 html_footer ;;
618 *\ logout\ *)
619 # Set a Cookie in the past to logout.
620 expires="Expires=Wed, 01-Jan-1980 00:00:00 GMT"
621 if check_auth; then
622 rm -f "$sessions/$user"
623 header "Location: $script" "Set-Cookie: auth=none; $expires; HttpOnly"
624 fi ;;
626 *\ user\ *)
627 # User profile
628 d="$(GET user)"
629 header
630 html_header
631 user_box
632 . $PEOPLE/"$(GET user)"/account.conf
633 echo "<h2>$(get_gravatar $MAIL) $(GET user)</h2>"
634 loglines=$(fgrep $(GET user) $(find $cache -name *.log) | wc -l)
635 gettext "Activities:"; echo " $loglines"
636 if check_auth && [ "$(GET user)" == "$user" ]; then
637 auth_people
638 else
639 # check_auth will set VARS to current logged user: re-source
640 . $PEOPLE/"$(GET user)"/account.conf
641 public_people
642 fi
643 html_footer ;;
645 *\ dashboard\ *)
646 # For now simply list plugins and users info. We could have a
647 # dashbord only for ADMINS found in the config file. The dashboard
648 # should also be a plugin.
649 d="Dashboard"
650 header
651 html_header
652 user_box
653 users=$(ls -1 $PEOPLE | wc -l)
654 docs=$(find $wiki -type f | wc -l)
655 wikisize="$(du -sh $wiki | awk '{print $1}')"
656 cachesize="$(du -sh $cache | awk '{print $1}')"
657 [ "$HG" != "yes" ] && hg=$(gettext "disabled")
658 [ "$HG" == "yes" ] && hg=$(gettext "enabled")
659 echo "<h2>$d</h2>"
660 if check_auth; then
661 cat << EOT
662 <div id="tools">
663 <a href='$script?log'>Activity log</a>
664 <a href='$script?ls'>List files</a>
665 </div>
667 <pre>
668 Users : $users
669 Wiki : $docs ($wikisize)
670 Cache : $cachesize
671 Mercurial : $hg
672 </pre>
673 <h3>Admin users</h3>
674 EOT
675 # Get the list of administrators
676 for u in $(ls $PEOPLE)
677 do
678 user=${u}
679 if admin_user; then
680 echo "<a href='?user=$u'>$u</a>"
681 fi
682 done
683 cat << EOT
684 <h3>$(gettext "Plugins")</h3>
685 <pre>
686 EOT
687 for p in $(ls -1 $plugins)
688 do
689 . $plugins/$p/$p.conf
690 echo "<a href='?$p'>$PLUGIN</a> - $SHORT_DESC"
691 done
692 echo '</pre>'
693 else
694 gettext "You must be logged in to view the dashboard."
695 fi
696 html_footer ;;
698 *\ hg\ *)
699 header
700 [ "$HG" != "yes" ] && gettext "Hg is disabled" && exit 0
701 [ ! -x /usr/bin/hg ] && gettext "Hg is not installed" && exit 0
702 d="Hg Log"
703 html_header
704 user_box
705 echo "<h2>$d</h2>"
706 case " $(GET hg) " in
707 *\ init\ *)
708 if check_auth; then
709 [ -d "$content/.hg" ] && exit 0
710 echo '<pre>'
711 gettext "Executing: hg init"; echo
712 cd $content/ && hg init
713 echo '[hooks]' > .hg/hgrc
714 echo 'incoming = hg update' >> .hg/hgrc
715 gettext "Adding current content and committing"; echo
716 [ ! -f "$wiki/index.txt" ] && touch $wiki/$index.txt
717 hg add && hg commit -u "$NAME <$MAIL>" \
718 -m "Initial commit with current content"
719 echo '</pre>' && cd ..
720 fi ;;
721 esac
722 hg_log
723 html_footer ;;
725 *)
726 # Display requested page
727 d="$(GET d)"
728 [ "$d" ] || d=$index
729 header
730 html_header
731 user_box
732 get_lang
733 # Generate a default index on first run.
734 if [ ! -f "$wiki/$index.txt" ]; then
735 default_index
736 fi
737 if [ ! -f "$wiki/$d.txt" ]; then
738 echo "<h2>$d</h2>"
739 gettext "The document does not exist. You can create it or read the"
740 echo " <a href='?d=en/help'>help</a>"
741 else
742 if fgrep -q [NOWIKI] $wiki/$d.txt; then
743 cat $wiki/$d.txt | sed '/[NOWIKI]/'d
744 else
745 cat $wiki/$d.txt | wiki_parser
746 fi
747 fi
748 if check_auth; then
749 wiki_tools
750 if [ "$HG" == "yes" ] && [ ! -d "$content/.hg" ]; then
751 echo '<p class="error box">'
752 gettext "Mercurial is enabled but no repository found"
753 echo ": <a href='?hg=init'>Hg init</a>"
754 echo '</p>'
755 fi
756 fi
757 html_footer ;;
758 esac
760 exit 0