tinycm view index.cgi @ rev 120

Better blog archive, fix rss feed
author Christophe Lincoln <pankso@slitaz.org>
date Fri Mar 24 23:59:43 2017 +0100 (2017-03-24)
parents 511841d477a3
children 0893f1ab023c
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 grep -w -q "$user" ${ADMIN_USERS}
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 && chmod 0600 $AUTH_FILE
248 fi
249 echo "$user:$pass" >> $AUTH_FILE
250 mkdir -pm0700 $PEOPLE/${user}
251 cat > $PEOPLE/$user/account.conf << EOT
252 # User configuration
253 NAME="$name"
254 USER="$user"
255 MAIL="$mail"
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 "$user" > ${ADMIN_USERS}
261 fi
262 }
264 # The CM style parser. Just a title, simple text formatting and internal
265 # links, as well as images and use HTML for other stuff. Keep it fast!
266 # To make TinyCM as easy as possible we have a small HTML editor/helper
267 # written in Javascript
268 wiki_parser() {
269 doc="[0-9a-zA-Z\.\#/~\_%=\?\&,\+\:@;!\(\)\*\$'\-]*"
270 sed \
271 -e s"#====\([^']*\)====#<h2>\1</h2>#"g \
272 -e s"#===\([^']*\)===#<h3>\1</h3>#"g \
273 -e s"#==\([^']*\)==#<h4>\1</h4>#"g \
274 -e s"#\*\*\([^']*\)\*\*#<b>\1</b>#"g \
275 -e s"#''\([^']*\)''#<em>\1</em>#"g \
276 -e s"#__\([^']*\)__#<u>\1</u>#"g \
277 -e s"#\[\([^]]*\)|\($doc\)\]#<a href='$script?d=\2'>\1</a>#"g \
278 -e s"#\[\([^]]*\)!\($doc\)\]#<a href='\2'>\1</a>#"g \
279 -e s"#\[\(http://*[^]]*.png\)\]#<img src='\1' />#"g \
280 -e s"#\[\([^]]*.png\)\]#<img src='content/cloud/\1' />#"g \
281 -e s"#@\([^']*\)@#<a href='$script?user=\1'>\1</a>#"g
282 }
284 link_user() {
285 echo "<a href='$(basename $script)?user=$user'>$user</a>"
286 }
288 # Save a document. Do we need more than 1 backup and diff ?
289 save_document() {
290 mkdir -p $cache/$d $(dirname $wiki/$d)
291 # May be a new page.
292 if [ ! -f "$wiki/$d.txt" ]; then
293 new=0
294 touch $wiki/$d.txt
295 fi
296 cp $wiki/$d.txt $cache/$d/last.bak
297 sed "s/$(echo -en '\r') /\n/g" > $wiki/$d.txt << EOT
298 $(GET content)
299 EOT
300 diff $cache/$d/last.bak $wiki/$d.txt > $cache/$d/last.diff
301 # Log
302 if [ "$new" ]; then
303 echo "Page created by: $(link_user)" | log
304 echo "New document: <a href='$script?d=$d'>$d</a>" | log_activity
305 if [ "$HG" == "yes" ]; then
306 cd $content && hg -q add
307 hg commit -q -u "$NAME <$MAIL>" -m "Created new document: $d"
308 cd $tiny
309 fi
310 else
311 # Here we may clean log: cat && tail -n 40
312 echo "Page edited by: $(link_user)" | log
313 if [ "$HG" == "yes" ]; then
314 cd $content && hg commit -q -u "$NAME <$MAIL>" \
315 -m "Edited document: $d"
316 cd $tiny
317 fi
318 fi
319 }
321 # CM tools (edit, diff, etc) for auth users
322 wiki_tools() {
323 if check_auth; then
324 cat << EOT
325 <div id="tools">
326 <a href="$script?edit=$d">$(gettext "Edit document")</a>
327 <a href="$script?log=$d">$(gettext "File log")</a>
328 <a href="$script?diff=$d">$(gettext "Last diff")</a>
329 $PLUGINS_TOOLS
330 EOT
331 [ "$HG" == "yes" ] && echo "<a href='$script?hg'>Hg Log</a>"
332 echo "</div>"
333 fi
334 }
336 # Built-in tools such as log/ls and PLUGINS_TOOLS
337 tiny_tools() {
338 if check_auth; then
339 cat << EOT
340 <div id='tools'>
341 <a href='$script?log'>Activity log</a>
342 <a href='$script?ls'>Pages list</a>
343 $PLUGINS_TOOLS
344 </div>
345 EOT
346 fi
347 }
349 # Get and display Gravatar image: get_gravatar email size
350 # Link to profile: <a href="http://www.gravatar.com/$md5">...</a>
351 get_gravatar() {
352 email=$1
353 size=$2
354 [ "$size" ] || size=48
355 url="http://www.gravatar.com/avatar"
356 md5=$(md5crypt $email)
357 echo "<img src='$url/$md5?d=identicon&s=$size' alt='&lowast;' />"
358 }
360 # List hg logs
361 hg_log() {
362 cd $content
363 cat << EOT
364 <table>
365 <thead>
366 <td>$(gettext "User")</td>
367 <td>$(gettext "Description")</td>
368 <td>$(gettext "Revision")</td>
369 </thead>
370 EOT
371 hg log --template "<tr><td>{author}</td><td>{desc}</td><td>{rev}</td></tr>\n"
372 echo '</table>'
373 }
375 #
376 # POST actions
377 #
379 case " $(POST) " in
380 *\ auth\ *)
381 # Authenticate user. Create a session file in $sessions to be used
382 # by check_auth. We have the user login name and a peer session
383 # md5 string in the COOKIE.
384 user="$(POST auth)"
385 pass="$(md5crypt "$(POST pass)")"
386 valid=$(fgrep "${user}:" $AUTH_FILE | cut -d ":" -f 2)
387 if [ "$pass" == "$valid" ] && [ "$pass" != "" ]; then
388 md5session=$(echo -n "$$:$user:$pass:$$" | md5sum | awk '{print $1}')
389 [ -d $sessions ] || mkdir -p $sessions
390 date '+%Y-%m-%d' > ${PEOPLE}/${user}/last
391 echo "$md5session" > $sessions/$user
392 header "Location: $script" \
393 "Set-Cookie: auth=$user:$md5session; HttpOnly"
394 else
395 header "Location: $script?login&error"
396 fi ;;
397 *\ signup\ *)
398 # POST action for signup
399 name="$(POST name)"
400 user="$(POST user)"
401 mail="$(POST mail)"
402 pass="$(md5crypt "$(POST pass)")"
403 if ! grep "^${user}:" $AUTH_FILE; then
404 new_user_config
405 header "Location: $script?login"
406 else
407 header
408 html_header
409 user_box
410 echo "<h2>$(gettext 'User already exists:') $user</h2>"
411 html_footer
412 fi ;;
413 esac
415 #
416 # Plugins
417 #
418 for p in $(ls -1 $plugins)
419 do
420 [ -f "$plugins/$p/$p.conf" ] && . $plugins/$p/$p.conf
421 [ -x "$plugins/$p/$p.cgi" ] && . $plugins/$p/$p.cgi
422 done
424 #
425 # GET actions
426 #
428 case " $(GET) " in
429 *\ edit\ *)
430 d="$(GET edit)"
431 header
432 html_header
433 user_box
434 get_lang
435 wiki_tools
436 if check_auth; then
437 cat << EOT
438 <h2>$(gettext "Edit $doc [ $i18n ]")</h2>
440 <div id="edit">
442 <form method="get" action="$script" name="editor">
443 <input type="hidden" name="save" value="$d" />
444 <textarea name="content">$(cat "$wiki/$d.txt")</textarea>
445 <input type="submit" value="$(gettext "Save document")" />
446 $(gettext "Code Helper:")
447 $(cat lib/jseditor.html)
448 </form>
450 </div>
451 EOT
452 else
453 gettext "You must be logged in to edit pages"
454 fi
455 html_footer ;;
457 *\ save\ *)
458 d="$(GET save)"
459 if check_auth; then
460 save_document
461 fi
462 header "Location: $script?d=$d" ;;
464 *\ log\ *)
465 d="$(GET log)"
466 header
467 html_header
468 user_box
469 # Main activity
470 if [ "$d" == "log" ]; then
471 tiny_tools
472 echo "<h2>$(gettext "Activity log")</h2>"
473 echo '<pre>'
474 if [ -f "$cache/log/activity.log" ]; then
475 IFS="|"
476 tac $cache/log/activity.log | while read USER DATE LOG
477 do
478 . ${PEOPLE}/${USER}/account.conf
479 cat << EOT
480 <a href='$script?user=$USER'>$(get_gravatar $MAIL 24)</a>\
481 <span class='date'>$DATE -</span> $LOG
482 EOT
483 done
484 unset IFS
485 else
486 gettext "No activity log yet"; echo
487 fi
488 echo '</pre>'
489 html_footer && exit 0
490 fi
491 # Document activity
492 get_lang
493 wiki_tools
494 echo "<h2>$(gettext "Activity for:") <a href='$script?d=$d'>$d</a></h2>"
495 echo '<pre>'
496 if [ -f "$cache/$d/activity.log" ]; then
497 tac $cache/$d/activity.log
498 else
499 gettext "No log for: $d"; echo
500 fi
501 echo '</pre>'
502 html_footer ;;
504 *\ ls\ *)
505 d="Document list"
506 header
507 html_header
508 user_box
509 tiny_tools
510 [ ! check_auth ] && auth=0
511 cat << EOT
512 <h2>$(gettext "Pages list")</h2>
513 <div id="plugins">
514 <table>
515 <thead>
516 <td>$(gettext "Name")</td>
517 <td>$(gettext "Title")</td>
518 <td>$(gettext "Action")</td>
519 </thead>
521 EOT
522 cd ${wiki}
523 for d in $(find . -type f | sed s'/.\///')
524 do
525 d="${d%.txt}"
526 title=$(grep '^====' ${d}.txt | sed s'/====//'g)
527 echo "<tr><td><a href='$script?d=${d}'>${d}</a></td>"
528 echo "<td>$title</td>"
529 if [ "$auth" ]; then
530 cat << EOT
531 <td><a href="$script?edit=$d">$(gettext "Edit")</a> | \
532 <a href="$script?rm=$d">$(gettext "Remove")</a></td></tr>
533 EOT
534 else
535 echo "N/A"
536 fi
537 done && unset auth
538 echo '</table></div>'
539 html_footer ;;
541 *\ rm\ *)
542 [ ! check_auth ] && exit 1
543 d="$(GET rm)"
544 rm ${wiki}/"${d}"
545 rm -rf ${cache}/"${d%.txt}"
546 header "Location: $script?ls" ;;
548 *\ diff\ *)
549 d="$(GET diff)"
550 date="last"
551 header
552 html_header
553 user_box
554 get_lang
555 wiki_tools
556 echo "<h2>$(gettext "Diff for:") <a href='$script?d=$d'>$d</a></h2>"
557 echo '<pre>'
558 if [ -f "$cache/$d/$date.diff" ]; then
559 cat $cache/$d/$date.diff | sed \
560 -e 's|&|\&amp;|g' -e 's|<|\&lt;|g' -e 's|>|\&gt;|g' \
561 -e s"#^-\([^']*\).#<span style='color: red;'>\0</span>#"g \
562 -e s"#^+\([^']*\).#<span style='color: green;'>\0</span>#"g \
563 -e s"#@@\([^']*\)@@#<span style='color: blue;'>@@\1@@</span>#"g
564 else
565 gettext "No diff for:"; echo " $d"
566 fi
567 echo '</pre>'
568 html_footer ;;
570 *\ login\ *)
571 # The login page
572 d="Login"
573 [ "$(GET error)" ] && \
574 error="<p class="error">$(gettext "Bad login or pass")</p>"
575 header
576 html_header
577 user_box
578 login_page
579 html_footer ;;
581 *\ signup\ *)
582 # The login page
583 d="$(gettext "Sign Up")"
584 header
585 html_header
586 user_box
587 echo "<h2>$d</h2>"
588 if [ "$ONLINE_SIGNUP" == "yes" ]; then
589 signup_page
590 else
591 gettext "Online registration is disabled"
592 fi
593 html_footer ;;
595 *\ logout\ *)
596 # Set a Cookie in the past to logout.
597 expires="Expires=Wed, 01-Jan-1980 00:00:00 GMT"
598 if check_auth; then
599 rm -f "$sessions/$user"
600 header "Location: $script" "Set-Cookie: auth=none; $expires; HttpOnly"
601 fi ;;
603 *\ user\ *)
604 # Basic user profile. Use the users plugin for more functions
605 d="$(GET user)"
606 last="$(cat $PEOPLE/"$(GET user)"/last)"
607 header
608 html_header
609 user_box
610 . $PEOPLE/"$(GET user)"/account.conf
611 cat << EOT
612 <h2>$(get_gravatar $MAIL) $NAME</h2>
614 <pre>
615 $(gettext "User name :") $USER
616 $(gettext "Last login :") $last
617 </pre>
618 EOT
619 html_footer ;;
621 *\ hg\ *)
622 d="Hg Log"
623 header
624 html_header
625 user_box
626 [ "$HG" != "yes" ] && gettext "Hg is disabled" && exit 0
627 [ ! -x /usr/bin/hg ] && gettext "Hg is not installed" && exit 0
628 echo "<h2>$d</h2>"
629 case " $(GET hg) " in
630 *\ init\ *)
631 if check_auth; then
632 [ -d "$content/.hg" ] && exit 0
633 echo '<pre>'
634 gettext "Executing: hg init"; echo
635 cd $content/ && hg init
636 echo '[hooks]' > .hg/hgrc
637 echo 'incoming = hg update' >> .hg/hgrc
638 gettext "Adding current content and committing"; echo
639 [ ! -f "$wiki/index.txt" ] && default_index
640 hg add && hg commit -u "$NAME <$MAIL>" \
641 -m "Initial commit with current content"
642 echo '</pre>' && cd ..
643 fi ;;
644 esac
645 hg_log
646 html_footer ;;
648 *)
649 # Display requested page
650 d="$(GET d)"
651 [ "$d" ] || d=$index
652 header
653 html_header
654 user_box
655 get_lang
657 # Generate a default index on first run
658 if [ ! -f "$wiki/$index.txt" ]; then
659 if ! default_index; then
660 echo "<pre class='error'>Directory : content/ is not writeable</pre>"
661 html_footer && exit 0
662 fi
663 fi
665 # Check cache dir
666 if [ ! -w "$cache" ]; then
667 echo "<pre class='error'>Directory : cache/ is not writeable"
668 echo "Command : install -m 0777 -d $tiny/cache</pre>"
669 html_footer && exit 0
670 fi
672 # Hg warning if enabled but not initiated
673 if [ "$HG" == "yes" ] && [ ! -d "$content/.hg" ]; then
674 echo '<p class="error box">'
675 gettext "Mercurial is enabled but no repository found"
676 echo ": <a href='$script?hg=init'>Hg init</a>"
677 echo '</p>'
678 fi
680 # Wiki tools
681 wiki_tools
683 # Wiki document
684 if [ ! -f "$wiki/$d.txt" ]; then
685 echo "<h2>$d</h2>"
686 gettext "The document does not exist. You can create it or read the"
687 echo " <a href='$script?d=en/help'>help</a>"
688 else
689 if fgrep -q [NOWIKI] $wiki/$d.txt; then
690 cat $wiki/$d.txt | sed '/\[NOWIKI\]/'d
691 else
692 cat $wiki/$d.txt | wiki_parser
693 fi
694 fi
695 html_footer ;;
696 esac
698 exit 0