[Appli Web] StickyNotes (pense-bête)

Vous aimez les post-its? Moi oui, du coup je me suis programmé une petite appli web à installer sur mon serveur web perso, pour pouvoir y accéder depuis n’importe ou 8)

Pré-requis :

  • serveur web Apache 2.x (pas besoin de php ni de base de données)
  • haserl ( apt-get install haserl )

Installation :

  • créér un dossier “sticky” contenant un sous-dossier “notes” (ou choisir un autre nom mais penser à le modifier dans le script) dans votre arborescence web
mkdir -p /var/www/sticky/notes
  • copier le script principal (voir en bas de ce 1er post) dans /var/www/sticky/, nommer le fichier “notes.cgi” et le rendre exécutable

chown root:root /var/www/sticky/notes.cgi chmod +x /var/www/sticky/notes.cgi

  • autoriser l’écriture dans le sous-dossier “notes” (c’est dans ce dossier que seront stockés les données des post-it)

chown www-data /var/www/sticky/notes chmod +w /var/www/sticky/notes

  • copier le .htaccess suivant dans /var/www/sticky (à modifier selon vos choix)
# Security : only allow localhost by default
# comment or adjust these 3 lines after installation
Order Deny,Allow
Deny from all
Allow from localhost

# Use Apache's basic auth (be sure your server is SSL-enabled!)
# this will also allow multi-user operation
#AuthType Basic
#AuthName "Restricted Access"
#AuthBasicProvider file
#AuthUserFile /etc/apache2/htpasswd.sticky
#Require valid-user

# Security : allow only safe HTTP methods
<LimitExcept POST GET>
  Order Allow,Deny
  Deny from all

# allow CGIs in current directory
Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch -Indexes
AddHandler cgi-script .cgi

(cet .htaccess aura pour effet d’autoriser l’exécution des .cgi dans le répertoire)

  • enfin, créer le .htaccess suivant dans /var/www/sticky/notes/ :
Order Allow,Deny
Deny from all

(afin d’interdire l’accès direct aux données des post-it)

Vous devriez avoir l’arborescence suivante :

ls -alR /var/www/sticky
total 32
drwxr-xr-x 3 root     root  4096 oct.  27 19:24 .
drwxr-xr-x 7 root     root  4096 oct.  27 18:30 ..
-rw-r--r-- 1 root     root   478 oct.  28 12:16 .htaccess
-rw-r--r-- 1 root     root    82 oct.  27 18:25 index.html
drwxr-xr-x 2 www-data root  4096 oct.  28 12:28 notes
-rwxr-xr-x 1 root     root 10369 oct.  28 12:03 notes.cgi

total 12
drwxr-xr-x 2 www-data root 4096 oct.  28 12:28 .
drwxr-xr-x 3 root     root 4096 oct.  27 19:24 ..
-rw-r--r-- 1 root     root   32 oct.   3 21:03 .htaccess

Remarque : pour que les fichiers .htaccess soient bien pris en compte, il faut les autoriser dans votre config Apache principale

nano /etc/apache2/sites-enabled/000-default

remplacer AllowOverride None par AllowOverride All dans :

<Directory /var/www/> Options Indexes FollowSymLinks MultiViews AllowOverride All Order allow,deny allow from all </Directory>

  • Vous pouvez accéder à votre appli via l’adresse :


Il est possible de crééer un fichier index.html dans /var/www/sticky, pour une redirection automatique

<html><head><meta http-equiv="refresh" content="0; URL=notes.cgi"></head></html>

ainsi vous pourrez accéder directement à l’appli via :


Voici donc le script principal, notes.cgi :

#!/usr/bin/haserl --shell=/bin/bash
<% echo -en "content-type: text/html\r\n\r\n"
# Fredo's Sticky Notes
# http://agentoss.wordpress.com / fredo696@gmail.com
# created 29/09/2013
# last modified 29/10/2013
# REQUIRED : bash, haserl. (tested with Apache httpd 2.2 on Debian GNU/Linux)
#  - read the INSTALL file for instructions
#  - adjust some variables in the *** PROGRAM'S VARIABLES *** section below
#  - sticky notes are displayed in filename order
# TODO :
#  - short embedded help page (pure CSS modal box)
#  - test app on mobile devices
#  - allow editing of existing stickies
#  - allow some html tags in stickies
#  - fix various CSS bugs / fix min-height for .stickynote / Looks ugly on Android's 2.3.6 native webkit browser :(
#  - add a hotkey to kill a sticky
#  - move sticky notes with mouse?
#  - different colors for sticky notes
#  - use a nice image/pattern for background
# BUGS : please report them to the adress above!
# %>
<!DOCTYPE html>
<HTML lang="fr">
<HEAD><META charset="UTF-8">
<!-- disable caching so that pages refresh correctly -->
<!-- <META http-equiv="PRAGMA" content="NO-CACHE">
<META name="viewport" content="user-scalable=yes, initial-scale=1, width=device-width">
# Get the user's name from the CGI environment variable
# (in case you are using your web server's auth module)
if [[ -z "$REMOTE_USER" ]];then
echo "<TITLE>Sticky Notes : $username</TITLE>"
<STYLE type="text/css">
html {min-height:100%;}
body {color:#564b47; font-size:1em; font-family:Arial, Sans-serif; text-align:center; margin:2%;
/* thanks to http://www.colorzilla.com/gradient-editor/ */
background: #a1dbff;
background: -moz-linear-gradient(top, #a1dbff 0%, #cbebff 53%, #f0f9ff 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#a1dbff), color-stop(53%,#cbebff), color-stop(100%,#f0f9ff));
background: -webkit-linear-gradient(top, #a1dbff 0%,#cbebff 53%,#f0f9ff 100%);
background: -o-linear-gradient(top, #a1dbff 0%,#cbebff 53%,#f0f9ff 100%);
background: -ms-linear-gradient(top, #a1dbff 0%,#cbebff 53%,#f0f9ff 100%);
background: linear-gradient(to bottom, #a1dbff 0%,#cbebff 53%,#f0f9ff 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#a1dbff', endColorstr='#f0f9ff',GradientType=0 );
#footer {position:absolute; bottom:0px; left:0px; text-align:left; font-size:0.60em; opacity:0.5; z-index:-1;}
#footer:hover {opacity:1;}
.stickynote { float:left; border-left:1px solid grey; border-top:1px solid grey; border-right:1px solid black; border-bottom:1px solid black;
box-shadow:4px 4px 6px Grey; padding:10px; margin:10px; text-align:left;

/* adjust stickies dimensions here.  TODO: adjust min-height to relative value */
min-width:15%; max-width:35%; min-height:150px;

background: gold;
background: -moz-linear-gradient(top, white 0%, gold 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,white), color-stop(100%,gold));
background: -webkit-linear-gradient(top, white 0%,gold 100%);
background: -o-linear-gradient(top, white 0%,gold 100%);
background: -ms-linear-gradient(top, white 0%,gold 100%);
background: linear-gradient(to bottom, white 0%,gold 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='white', endColorstr='gold',GradientType=0 );

/* slower transition for zoom effect */
/* thanks to http://www.solucior.com/fr/15-Zoom_en_CSS3.html  */
-webkit-transition: all .2s ease-out;
-moz-transition: all .2s ease-out;
-o-transition: all .2s ease-out;
transition: all .2s ease-out;

.stickynote:hover {
/* zoom effect - slight differences between browsers */
transform: scale(1.25);
-moz-transform: scale(1.25);
-webkit-transform: scale(1.25);
-o-transform: scale(1.25);

.rotated {
/* slight rotation */
-ms-transform:rotate(-5deg); /* IE 9 */
-webkit-transform:rotate(-5deg); /* Safari and Chrome */
pre {font:inherit;
-webkit-hyphens: auto;
-moz-hyphens: auto;
-ms-hyphens: auto;
-o-hyphens: auto;
hyphens: auto;
a:link {color:LightSlateGrey; text-decoration:none;}
a:active {color:black; text-decoration:none;}
a:visited {color:black; text-decoration:none;}
a:hover {text-decoration:none;color:red;}
.mybutton {max-width:100%; color:black;}
.killnote {position:absolute; top:0px; right:0px;}
.killbutton {border:0px; opacity:0.5; background-color:transparent; color:black; cursor:crosshair;}
textarea {max-width:100%; background-color:transparent; font:inherit; border:hidden;}
# uncomment to enable debugging messages

# adjust HERE the ABSOLUTE PATH where to store the stickies
# (this directory must be WRITABLE by the web server)

# We append the username (so that each user has his own private stickies directory)
#Â (don't change here unless you know what you are doing)

# adjust filename template for mktemp ("man mktemp" for more info)

# maximum number of characters for the text of a sticky note

[[ $DEBUG ]] && echo "(debug) FORM_mode=$FORM_mode | STICKY_DIR=$STICKY_DIR | REMOTE_USER=$REMOTE_USER"

case "$FORM_mode" in
 # new sticky note creation mode

 # ensure FORM_textfield is not empty and does not exceed STICKY_MAX_LENGTH chars
 if [[ "$len" -gt 0 && "$len" -le "$STICKY_MAX_LENGTH" ]]; then

  # filter FORM_textfield to prevent XSS
  FORM_textfield=$( echo "$FORM_textfield" | sed "s/<[^>]*>//g" )

  # we use the handy mktemp shell command to create a new file with a unique filename
  newfile=$(mktemp --tmpdir="$STICKY_DIR" "$STICKY_FILENAME")
  if [[ ! "$newfile" ]]; then
   echo "Unable to create new file! Please report the problem to the system admin!"
   [[ $DEBUG ]] && echo "(debug) FORM_textfield=$FORM_textfield | newfile=$newfile"
   # file created ok, write text and make the file readable by all
   echo >"$newfile" "$FORM_textfield" && echo "<H1>New sticky note created. Please click <A HREF=$SCRIPT_NAME>here</A> to refresh the page.</H1>"
   # TODO: automatically refresh page to show newly created sticky note (javascript)
   # we could also add a delay as an anti-flood measure
   #sleep 3
  echo "<H1>Sticky note not created : Text is empty or too long (max. is $STICKY_MAX_LENGTH chars)!</H1>"

# kill selected sticky note
# first we sanitize $FORM_id to prevent deleting files outside our private directory
# (remove any non-alphanumeric characters in string, since our filenames are generated by the mktemp command)
[[ $DEBUG ]] && echo "(debug) Kill sticky note : $STICKY_DIR/$filename"
[[ $DEBUG ]] || rm "$STICKY_DIR/$filename" && echo "<H1>Your sticky note has been deleted! Go <A HREF=$SCRIPT_NAME>back</A> to the stickies.</H1>"

 # default action: display all existing sticky notes for current user
# count=0
 if [[ -d "$STICKY_DIR" ]]; then
  for f in "$STICKY_DIR"/*; do
   #Â check if regular file
   if [[ -f "$f" ]];then
#    let count++
    id=$(basename $f)
    # we enclose the plain text between <PRE></PRE> tags so that linefeeds are preserved
    echo "<DIV CLASS=\"stickynote rotated\" id=$id><PRE>$(<$f)</PRE>"\
         "<DIV CLASS=\"killnote\"><FORM ACTION=\"$SCRIPT_NAME\" METHOD=\"POST\">"\
         "<INPUT NAME=\"mode\" TYPE=\"hidden\" VALUE=\"kill\">"\
         "<INPUT NAME=\"id\" TYPE=\"hidden\" VALUE=\"$id\">"\
         "<BUTTON CLASS=\"killbutton\" TITLE=\"Click to kill this note!\" TYPE=\"submit\">&spades;</BUTTON>"\
  # show message if no stickies found yet
#  if [[ $count -eq 0 ]];then
#   echo "<H1>There are no stickies yet!</H1>"
#  fi
  # try to create the user's sticky subdir
  echo -n "First time user, creating your private stickies directory... "
  mkdir "$STICKY_DIR" || echo -e "FAILED... Please check directory permissions!\n"

%><DIV CLASS="stickynote"><FORM ACTION="<% echo -n $SCRIPT_NAME %>" METHOD="POST">
<INPUT NAME="mode" TYPE="hidden" VALUE="new"><BR>
<INPUT TYPE="submit" CLASS="mybutton" VALUE="Create new">
<DIV id="footer">Another minimalistic webapp from Fredo (c) 2013 <A HREF="http://agentoss.wordpress.com">Agent OSS</A>.
 Just some bits of HTML, CSS, Bash and <A HREF="http://haserl.sourceforge.net/">Haserl</A>. Close your browser to logout.

Si vous avez 5 minutes pour tester, n’hésitez pas! :stuck_out_tongue:

EDIT : màj du script, ajout d’un contrôle de variable dans la partie “kill”)
EDIT2 : légère réorganisation du code, suppression de l’icône embarquée. Utilisation inutile de cat, supprimée.
EDIT3 : modification du .htaccess principal pour une meilleure sécurité

Intéressant, je testerai :smiley:

et surtout, si vous voyez des trous de sécurité potentiels, faites remonter :stuck_out_tongue:

(si vous suivez le mode d’installation, l’appli est uniquement accessible depuis la machine localhost, par mesure de sécurité)

[quote=“agentsteel”]et surtout, si vous voyez des trous de sécurité potentiels, faites remonter :stuck_out_tongue:

(si vous suivez le mode d’installation, l’appli est [/buniquement accessible depuis la machine localhost, par mesure de sécurité)[/quote]Mais alors où est l’intérêt si on ne peut voir les notes que dans son LAN ?
Le côté qui m’intéresserait, serait justement de pouvoir lire (en sécurité) mes notes quand je ne suis pas à la maison, sur une machine autre que les miennes.

C’était juste par mesure de sécurité. Après il faut adapter le 1er .htaccess, par exemple pour protéger avec un identifiant/mot de passe :

(par exemple)

# Security : comment/modify these 3 lines after installation
#Order Deny,Allow
#Deny from all
#Allow from localhost

# Use Apache's basic auth (be sure your server is SSL-enabled!)
# this will also allow multi-user operation
AuthType Basic
AuthName "Restricted Access"
AuthBasicProvider file
AuthUserFile /etc/apache2/htpasswd.sticky
Require valid-user

# allow CGIs in current directory
Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch -Indexes
AddHandler cgi-script .cgi

il faut créér les identifiants/mot de passe via la commande htpasswd
(pour la création du premier utilisateur :

pour les users suivants :

Attention à ce que ton serveur soit configuré avec SSL (HTTPS) sinon les mots de passe transitent en clair.

( isalo.org/wiki.debian-fr/Ap … tification )

J’ai testé la sécurité de l’appli avec Skipfish. Du coup modification du .htaccess principal pour renforcer la sécurité (directive )