1# 2# 3# Librairie de fonctions TCL utilisables dans les scripts CGI 4# 5# Historique 6# 1999/02/25 : pda : conception en package 7# 2000/07/19 : pda : ajout de generer-menu 8# 2001/02/28 : pda : suppression de get-raw-data ajouté par je ne sais pas qui 9# 2001/05/02 : pda : utilisation du package Pgtcl pour l'accès à PostgreSQL 10# 2001/10/20 : pda : ajout de la procédure sortie-html 11# 2002/05/11 : pda : ajout de la procédure sortie-latex 12# 2002/05/11 : pda : ajout des variables tmp et pdflatex 13# 2002/05/20 : pda : ajout de la procédure send à la place de sortie-* 14# 2002/06/04 : pda : ajout de la procédure nologin 15# 2002/12/26 : pda : ajout de la procédure error-exit 16# 2003/06/07 : pda : ajout de la procédure call-cgi 17# 2003/06/27 : pda : ajout de la procédure cgi-exec 18# 2003/09/29 : pda : ajout de la procédure mail 19# 2003/11/05 : pda : utilisation de string equal à la place de string length 20# 2004/02/12 : pda/jean : ajout form-bool 21# 2005/04/13 : pda : correction d'un bug dans form-text 22# 2006/08/29 : pda : ajout de import-vars 23# 2007/10/05 : pda/jean : ajout des objets auth et user 24# 2007/10/23 : pda/jean : ajout de l'objet log 25# 2008/06/12 : pda/jean : ajout de interactive-tree et helem 26# 2010/11/05 : pda : méthode opened-postgresql pour l'objet log 27# 2010/11/09 : pda : suppression generer-menu 28# 2010/11/27 : pda : ajout locale 29# 2010/11/27 : pda : envoi utf-8 systématique 30# 2010/12/09 : pda : ajout form-submit et form-reset 31# 2010/12/16 : pda : add import-vars optional fspec parameter 32# 2012/01/09 : pda : add cmdpath 33# 2015/03/11 : pda/jean : add myurl 34# 35 36# packages nécessaires pour l'acces à la base d'authentification 37 38package require mime ; # tcllib 39package require snit ; # tcllib >= 1.10 40package require ldapx ; # tcllib >= 1.10 41package require pgsql ; # package local 42 43# package require Pgtcl 44 45package provide webapp 1.17 46 47namespace eval webapp { 48 namespace export log pathinfo user myurl locale \ 49 form-field form-yesno form-bool form-menu form-text form-hidden \ 50 form-submit form-reset \ 51 hide-parameters file-subst \ 52 helem interactive-tree \ 53 get-data import-vars valid-email \ 54 post-string html-string \ 55 call-cgi \ 56 mail \ 57 random \ 58 set-cookie get-cookie \ 59 nologin send redirect error-exit \ 60 debug cgidebug cgi-exec \ 61 cmdpath 62 63 variable tmpdir /tmp 64 variable pdflatex /usr/local/bin/pdflatex 65 variable debuginfos {} 66 variable sendmail {/usr/sbin/sendmail -t} 67 68 # element HTML (4.01) sans tag de fermeture 69 # cf http://www.w3.org/TR/1999/REC-html401-19991224/index/elements.html 70 variable noendtags {area base basefont br col frame hr img input isindex 71 link meta param} 72 # url des images (pour la génération d'arbre interactif) 73 # relativement à la racine du serveur web 74 variable treeimages css/images 75 76 # code Javascript de l'arbre interactif 77 variable treejs { 78 <script type="text/javascript"> 79 <!-- 80 // fonction pour initialiser la vue de l'arborescence 81 // id : id de l'ul de l'arborescence à initialiser 82 // disp : "none" ou "block" 83 // Cette fonction masque tous les ul compris sous l'id, 84 // puis affiche juste l'ul correspondant à l'id 85 function multide(id, disp) { 86 var x = document.getElementById (id) ; 87 // vérification de cohérence 88 if (! x || x.nodeName != "UL") 89 return 'PAS UN UL' ; 90 tab = x.getElementsByTagName ("UL") ; 91 for (var i = 0 ; i < tab.length ; i++) { 92 tab [i].style.display = disp ; 93 } 94 x.style.display = "block" ; 95 } 96 97 // fonction de déroulement/enroulement 98 // img : un objet de type IMG (élément HTML) dont on 99 // veut dérouler/enrouler la liste associée 100 // Typiquement, img est l'image "+" ou "-", et 101 // on veut dérouler/enrouler le ul qui suit 102 // dans la liste des frères 103 function de(img) { 104 var ul ; 105 // vérification de cohérence 106 if (img.nodeName != "IMG") 107 return 'PAS UN IMG' 108 // parcourir tous les frères pour trouver l'UL qui doit suivre 109 ul = img ; 110 while (ul && ul.nodeName != "UL") 111 ul = ul.nextSibling ; 112 if (! ul || ul.nodeName != "UL") 113 return 'PAS UN UL' 114 // dérouler ou enrouler ? 115 if (ul.style.display == "none") { 116 // dérouler 117 ul.style.display = "block" ; 118 img.src = "%TREEIMAGES%/tree-minus.gif" ; 119 img.alt = "[-]" ; 120 } else { 121 // enrouler 122 ul.style.display = "none" ; 123 img.src = "%TREEIMAGES%/tree-plus.gif" ; 124 img.alt = "[+]" ; 125 } 126 return 'OK' ; 127 } 128 //--> 129 </script> 130 } 131 132 # CSS de l'arbre interactif (avec un trou correspondant à l'id) 133 variable treecss { 134 <style type="text/css"> 135 <!-- 136 137 html body ul#%ID% ul { 138 background: url("%TREEIMAGES%/tree-line.gif") repeat-y 0px 0px; 139 padding-left: 24px; 140 margin-left: 0; 141 } 142 143 html body ul#%ID% ul.last { 144 background: none; 145 } 146 147 html body ul#%ID% li { 148 background: none; 149 list-style: none; 150 padding: 0; 151 margin: 0; 152 } 153 154 html body ul#%ID% li ul li { 155 background: none; 156 list-style: none; 157 padding: 0; 158 margin: 0; 159 } 160 161 html body ul#%ID% li ul li ul li{ 162 background: none; 163 list-style: none; 164 padding: 0; 165 margin: 0; 166 } 167 168 html body ul#%ID% li ul li ul li ul li{ 169 background: none; 170 list-style: none; 171 padding: 0; 172 margin: 0; 173 } 174 175 html body ul#%ID% a { 176 padding: 0; 177 margin: 0; 178 } 179 180 html body ul#%ID% img { 181 padding: 0; 182 margin: 0; 183 } 184 185 html body ul#%ID% img.click { 186 cursor: pointer; 187 } 188 189 --> 190 </style> 191 } 192} 193 194############################################################################## 195# Set some variables 196############################################################################## 197 198proc ::webapp::cmdpath {cmd path} { 199 switch $cmd { 200 pdflatex { set ::webapp::pdflatex $path } 201 tmpdir { set ::webapp::tmpdir $path } 202 sendmail { set ::webapp::sendmail $path } 203 } 204} 205 206############################################################################## 207# Debug de certaines fonctions du script 208############################################################################## 209 210# 211# Positionne les informations de debug 212# 213# Entrée : 214# - paramètres : 215# - infos : listes de comportements à déboguer 216# Sortie : 217# - valeur de retour : - 218# - variables globales : 219# - debuginfo : les informations de debug souhaitées 220# 221# Note : informations de debug possibles 222# latexfiles : laisse les fichiers latex en l'état dans /tmp 223# latexsource : sort le source latex et non le généré pdf 224# 225# Historique : 226# 2002/05/12 : pda : conception 227# 228 229proc ::webapp::debug {infos} { 230 set ::webapp::debuginfos $infos 231} 232 233 234############################################################################## 235# Fichier de log 236############################################################################## 237 238# 239# Ajoute une ligne dans un fichier de log 240# 241# Entrée : 242# - paramètres : 243# - fichier : nom du fichier de log 244# - message : message à envoyer dans le log 245# - variables d'environnement : 246# - SCRIPT_NAME : voir procédure script-name 247# - REMOTE_HOST, REMOTE_ADDR : nom du client, ou à défaut son adresse IP 248# Sortie : 249# - pas de sortie 250# 251# Historique : 252# 1999/04/06 : pda : conception 253# 2000/12/12 : ??? : signalement de l'erreur d'ouverture sur stderr 254# 255 256proc ::webapp::log {fichier message} { 257 global env 258 259 set name [::webapp::script-name] 260 261 if {[info exists env(REMOTE_HOST)]} then { 262 set remote $env(REMOTE_HOST) 263 } else { 264 set remote $env(REMOTE_ADDR) 265 } 266 267 set date [clock format [clock seconds]] 268 269 if {[catch {open $fichier a} fd] == 0} then { 270 puts $fd [format "%s %s %s %s" $name $date $remote $message] 271 close $fd 272 } else { 273 puts stderr "erreur ouverture $fichier" 274 } 275} 276 277 278 279############################################################################## 280# Traitement des variables d'environnement 281############################################################################## 282 283# 284# Renvoie le contenu de la variable PATH_INFO 285# 286# Entrée : 287# - variables d'environnement : 288# - PATH_INFO : une chaîne de la forme "/relative/path/to/script" 289# Sortie : 290# - valeur de retour : liste des composants 291# 292# Historique : 293# 1994/08/xx : pda : conception et codage 294# 1999/02/25 : pda : documentation 295# 296 297proc ::webapp::pathinfo {} { 298 global env 299 300 # vérifie que la variable existe 301 if {! [info exists env(PATH_INFO)]} then { 302 return {} 303 } 304 305 # découpe la variable en éléments de liste 306 set path [split $env(PATH_INFO) /] 307 # le premier élément est nul puisque le chemin commence par "/" 308 set path [lreplace $path 0 0] 309 310 return $path 311} 312 313 314# 315# Renvoie le nom du script courant 316# 317# Entrée : 318# - variables d'environnement : 319# - SCRIPT_NAME : une chaîne de la forme "/relative/path/to/script" 320# Sortie : 321# - valeur de retour : 322# - le nom, ou vide si rien 323# 324# Historique : 325# 1994/08/xx : pda : conception et codage 326# 1999/02/25 : pda : documentation 327# 1999/07/14 : pda : changement d'interface 328# 329 330proc ::webapp::script-name {} { 331 global env 332 333 if {[info exists env(SCRIPT_NAME)]} then { 334 set n [split $env(SCRIPT_NAME) "/"] 335 set nm [lindex $n [expr [llength $n]-1]] 336 } else { 337 set nm {} 338 } 339 return $nm 340} 341 342# 343# Renvoie le nom de l'utilisateur courant (authentification apache) 344# 345# Entrée : 346# - variables d'environnement : 347# - REMOTE_USER : une chaîne de la forme "login"" 348# Sortie : 349# - valeur de retour : 350# - le nom, ou vide si rien 351# 352# Historique : 353# 1999/10/24 : pda : conception et codage 354# 355 356proc ::webapp::user {} { 357 global env 358 359 if {[info exists env(REMOTE_USER)]} then { 360 set nm $env(REMOTE_USER) 361 } else { 362 set nm {} 363 } 364 return $nm 365} 366 367# 368# Returns the complete URL of current script 369# 370# Input: 371# - level: nomber of components to remove from the end of the URL 372# (0: complete URL, 1: remove the last component, etc.) 373# - environment variables: 374# - REQUEST_URI 375# - REQUEST_SCHEME 376# - SERVER_NAME 377# - SERVER_PORT 378# Output: 379# - return value: URL or empty string 380# 381# History: 382# 2015/03/11 : pda/jean : design 383# 384 385proc ::webapp::myurl {{level 0}} { 386 global env 387 388 foreach {v e} { 389 uri REQUEST_URI 390 scheme REQUEST_SCHEME 391 server SERVER_NAME 392 port SERVER_PORT 393 } { 394 if {! [info exists env($e)]} then { 395 return "" 396 } 397 set $v $env($e) 398 } 399 400 # special case for IPv6 numeric addresses 401 if {[regexp {:} $server]} then { 402 set server "\[$server\]" 403 } 404 405 # remove some components from the request URI 406 for {set i 0} {$i < $level} {incr i} { 407 regsub {/[^/]*$} $uri {} uri 408 } 409 if {$uri eq ""} then { 410 set uri "/" 411 } 412 413 return "$scheme://$server:$port$uri" 414} 415 416# 417# Renvoie les langues acceptées par l'utilisateur 418# 419# Entrée : 420# - avail : locales disponibles 421# - variables d'environnement : 422# - HTTP_ACCEPT_LANGUAGE : une chaîne au format RFC 2616 423# lang[;q=\d],... 424# Sortie : 425# - valeur de retour : locale à utiliser 426# 427# Historique : 428# 2010/11/27 : pda : conception et codage 429# 430 431proc ::webapp::locale {avail} { 432 global env 433 434 if {[info exists env(HTTP_ACCEPT_LANGUAGE)]} then { 435 # 436 # Analyse la chaîne et crée un tableau indexé par 437 # le facteur de qualité (q=) 438 # 439 set accepted [string tolower $env(HTTP_ACCEPT_LANGUAGE)] 440 foreach a [split $accepted ","] { 441 regsub -all {\s+} $a {} a 442 set s [split $a ";"] 443 set lang [lindex $s 0] 444 set q 1 445 foreach param [lreplace $s 0 0] { 446 regexp {^q=([.0-9]+)$} $param bidon q 447 } 448 lappend tabl($q) $lang 449 set tabq($lang) $q 450 } 451 # 452 # En cas de langage avec un sous-tag, ajouter le langage 453 # primaire s'il n'existe pas. 454 # Il peut y avoir un nombre quelconque de sous-tags 455 # fr-fr-paris-xive-alesia 456 # 457 foreach l [array names tabq] { 458 set q $tabq($l) 459 set ll [split $l "-"] 460 while {[llength $ll] > 1} { 461 set ll [lreplace $ll end end] 462 set llp [join $ll "-"] 463 if {! [info exists tabq($llp)]} then { 464 lappend tabl($q) $llp 465 set tabq($llp) $q 466 } 467 } 468 } 469 470 # 471 # Croise avec les langages disponibles, en tenant 472 # compte du facteur de qualité 473 # 474 set avail [string tolower $avail] 475 set locale "C" 476 foreach q [lsort -real -decreasing [array names tabl]] { 477 foreach l $tabl($q) { 478 if {[lsearch -exact $avail $l] != -1} then { 479 set locale $l 480 break 481 } 482 } 483 if {$locale ne "C"} then { 484 break 485 } 486 } 487 } else { 488 set locale "C" 489 } 490 491 return $locale 492} 493 494############################################################################## 495# Génération de fragments de code HTML 496############################################################################## 497 498# 499# Génération de balises HTML conformes à HTML 4.01 500# 501# Entrée : 502# - paramètres : 503# - tag : balise HTML ("img", "ul", "a", etc.) 504# - content : texte associé au tag (entre les balises) 505# - args : attributs de la balise 506# Sortie : 507# - code HTML généré 508# 509# Exemple : 510# puts [helem "a" "cliquer ici" "href" "http://www.tcl.tk"] 511# 512# Historique : 513# 2008/06/12 : pda/jean/lauce : intégration webapp 514# 515 516proc ::webapp::helem {tag content args} { 517 set tag [string tolower $tag] 518 set r "<$tag" 519 foreach {attr value} $args { 520 set attr [string tolower $attr] 521 append r " $attr=\"$value\"" 522 } 523 append r ">$content" 524 # ne mettre une fermeture que pour les tags qui ne figurent pas 525 # dans la liste des tags sans fermeture 526 if {[lsearch $::webapp::noendtags $tag] == -1} then { 527 append r "</$tag>" 528 } 529 return $r 530} 531 532# 533# Génération du code HTML pour éditer un champ 534# 535# Entrée : 536# - paramètres : 537# - spec : spécification du champ, sous la forme 538# string [<largeur> [<largeurmax>]] 539# hidden 540# text [<hauteur> [<largeur>]] 541# menu <item> ... <item>, où <item>={<valeur envoyée> <affichée>} 542# list <mono/multi> <taille> <item> ... <item> 543# password [<largeur> [<largeurmax>]] 544# bool 545# hidden 546# yesno [fmt] 547# - var : variable du formulaire 548# - val : valeur initiale (par défaut) 549# Sortie : 550# - code HTML généré 551# 552# Historique : 553# 2003/08/01 : pda : conception 554# 555 556proc ::webapp::form-field {spec var val} { 557 set nargs [llength $spec] 558 switch -- [lindex $spec 0] { 559 string { 560 switch $nargs { 561 2 { 562 set largeur [lindex $spec 1] 563 set max 0 564 } 565 3 { 566 set largeur [lindex $spec 1] 567 set max [lindex $spec 2] 568 } 569 default { 570 set largeur 0 571 set max 0 572 } 573 } 574 set h [::webapp::form-text $var 1 $largeur $max $val] 575 } 576 bool { 577 set h [::webapp::form-bool $var $val] 578 } 579 password { 580 set hval [::webapp::unpost-string $val] 581 set h <INPUT TYPE=PASSWORD NAME=$var VALUE=\"$hval\">" 582 } 583 text { 584 switch $nargs { 585 2 { 586 set hauteur [lindex $spec 1] 587 set largeur 0 588 } 589 3 { 590 set hauteur [lindex $spec 1] 591 set largeur [lindex $spec 2] 592 } 593 default { 594 set hauteur 0 595 set largeur 0 596 } 597 } 598 set h [::webapp::form-text $var $hauteur $largeur 0 $val] 599 } 600 menu { 601 set items [lreplace $spec 0 0] 602 set h [::webapp::form-menu $var 1 0 $items $val] 603 } 604 list { 605 set monomulti [lindex $spec 1] 606 set taille [lindex $spec 2] 607 set items [lreplace $spec 0 2] 608 set multiple 0 609 if {[string equal $monomulti "multi"]} then { 610 set multiple 1 611 } 612 set h [::webapp::form-menu $var $taille $multiple $items $val] 613 } 614 yesno { 615 set fmt {%1$s Oui %2$s Non} 616 if {$nargs >= 2} then { 617 set fmt [lindex $spec 1] 618 } 619 set h [::webapp::form-yesno $var $val $fmt] 620 } 621 hidden { 622 set h [::webapp::form-hidden $var $val] 623 } 624 default { 625 set h "ERREUR" 626 } 627 } 628 return $h 629} 630 631# 632# Génération du code HTML pour réaliser un item oui/non 633# 634# Entrée : 635# - paramètres : 636# - var : variable du formulaire pour ce menu 637# - defval : valeur par défaut 638# - fmt : format pour la sortie de l'HTML 639# Sortie : 640# - code HTML généré 641# 642# Historique : 643# 2001/06/18 : pda : conception 644# 645 646proc ::webapp::form-yesno {var defval fmt} { 647 set oui "<INPUT TYPE=radio NAME=$var VALUE=1" 648 set non "<INPUT TYPE=radio NAME=$var VALUE=0" 649 if {! [string equal $defval ""] && $defval} then { 650 append oui " CHECKED" 651 } else { 652 append non " CHECKED" 653 } 654 append oui ">" 655 append non ">" 656 set html [format $fmt $oui $non] 657 return $html 658} 659 660# 661# Génération du code HTML pour réaliser un item booléen (case cochée ou non) 662# 663# Entrée : 664# - paramètres : 665# - var : variable du formulaire pour ce menu 666# - defval : valeur par défaut (=0 ou !=0) 667# Sortie : 668# - code HTML généré 669# 670# Historique : 671# 2004/02/12 : pda/jean : conception 672# 673 674proc ::webapp::form-bool {var defval} { 675 set checked "" 676 if {[regexp {^[0-9]+$} $defval] && $defval} then { 677 set checked " CHECKED" 678 } 679 set html "<INPUT TYPE=CHECKBOX NAME=$var VALUE=1$checked>" 680 return $html 681} 682 683# 684# Génération du code HTML pour réaliser un menu déroulant ou une 685# liste à choix multiples 686# 687# Entrée : 688# - paramètres : 689# - var : variable du formulaire pour ce menu 690# - taille : taille de la liste (1 si menu déroulant) 691# - multiple : 1 si choix multiple autorisé, 0 sinon 692# - liste : liste de couples { <valeur renvoyée> <item affiché> } 693# - lsel : liste des indices des items sélectionnés 694# Sortie : 695# - code HTML généré 696# 697# Historique : 698# 2001/04/27 : pda : conception 699# 2004/01/16 : pda/jean : correction d'un bug si lsel non trié 700# 701 702proc ::webapp::form-menu {var taille multiple liste lsel} { 703 set indice 0 704 705 set lsel [lsort -integer $lsel] 706 707 set optsel [lindex $lsel 0] 708 set lsel [lreplace $lsel 0 0] 709 710 set m "" 711 if {$multiple} then { set m "MULTIPLE" } 712 713 set html "<SELECT SIZE=\"$taille\" NAME=\"$var\" $m>\n" 714 715 foreach item $liste { 716 set valeur [::webapp::html-string [lindex $item 0]] 717 set libelle [::webapp::html-string [lindex $item 1]] 718 719 append html "<OPTION" 720 721 if {! [string equal $valeur ""]} then { 722 append html " VALUE=\"$valeur\"" 723 } 724 725 if {[string equal $indice $optsel]} then { 726 append html " SELECTED" 727 set optsel [lindex $lsel 0] 728 set lsel [lreplace $lsel 0 0] 729 } 730 731 append html ">$libelle\n" 732 733 incr indice 734 } 735 append html "</SELECT>\n" 736 737 return $html 738} 739 740# 741# Génération du code HTML pour réaliser un bouton (submit ou reset) 742# 743# Entrée : 744# - paramètres : 745# - var : variable du formulaire pour ce menu ou {} 746# - val : valeur par défaut (=0 ou !=0) 747# Sortie : 748# - code HTML généré 749# 750# Historique : 751# 2010/12/09 : pda : conception 752# 753 754proc ::webapp::form-submit {var val} { 755 set name "" 756 if {$var ne ""} then { 757 set name "name=\"$var\"" 758 } 759 set val [::webapp::html-string $val] 760 set html "<input type=submit $name value=\"$val\">" 761 return $html 762} 763 764proc ::webapp::form-reset {var val} { 765 set name "" 766 if {$var ne ""} then { 767 set name "name=\"$var\"" 768 } 769 set val [::webapp::html-string $val] 770 set html "<input type=reset $name value=\"$val\">" 771 return $html 772} 773 774# 775# Génération du code HTML pour réaliser une ligne de texte 776# 777# Entrée : 778# - paramètres : 779# - var : variable du formulaire pour cette ligne 780# - hauteur : hauteur de l'entrée, ou 0 pour la hauteur par défaut 781# - largeur : taille de l'entrée, ou 0 pour la taille par défaut 782# - max : nb maximum de caractères autorisés, 0 pour la valeur par défaut 783# - valeur : valeur initiale 784# Sortie : 785# - code HTML généré 786# 787# Historique : 788# 2001/04/27 : pda : conception 789# 2005/04/13 : pda : manquait ">" si input sans valeur par défaut 790# 791 792proc ::webapp::form-text {var hauteur largeur max valeur} { 793 set v [::webapp::html-string $valeur] 794 if {$hauteur <= 1} then { 795 # 796 # Simple ligne 797 # 798 set html "<INPUT TYPE=text NAME=\"$var\"" 799 800 if {$largeur > 0} then { 801 append html " SIZE=\"$largeur\"" 802 } 803 804 if {$max > 0} then { 805 append html " MAXLENGTH=\"$max\"" 806 } 807 808 if {! [string equal $valeur ""]} then { 809 append html " VALUE=\"$v\"" 810 } 811 812 append html ">" 813 } else { 814 # 815 # Zone de texte multi-ligne 816 # 817 set html "<TEXTAREA NAME=\"$var\" ROWS=\"$hauteur\"" 818 819 if {$largeur > 0} then { 820 append html " COLS=\"$largeur\"" 821 } 822 append html ">$v</TEXTAREA>" 823 } 824 825 return $html 826} 827 828# 829# Génération du code HTML pour réaliser un champ hidden 830# 831# Entrée : 832# - paramètres : 833# - var : variable du formulaire pour ce menu 834# - defval : valeur par défaut 835# Sortie : 836# - code HTML généré 837# 838# Historique : 839# 2003/08/03 : pda : conception 840# 841 842proc ::webapp::form-hidden {var defval} { 843 set v [::webapp::html-string $defval] 844 return "<INPUT TYPE=HIDDEN NAME=\"$var\" VALUE=\"$v\">" 845} 846 847# 848# Génération d'un arbre interactif (avec Javascript) 849# 850# Entrée : 851# - paramètres : 852# - id : id de l'élément racine (tag html "ul") de l'arbre généré 853# - tree : arbre, au format : 854# {<code-html> <arbre-fils> <arbre-fils> ... <arbre-fils>} 855# chaque <arbre-fils> pouvant être lui-même un arbre. 856# Si un arbre n'a pas de racine unique, le <code-html> de la 857# racine est vide, et chaque fils constitue une racine. 858# - expcoll : liste de deux textes à afficher (pour tout dérouler 859# et tout enrouler, dans l'ordre) 860# Sortie : 861# - valeur de retour : liste contenant les éléments suivants : 862# {head1 head2 onload html} 863# où : 864# - head1 : code HTML prêt à être inséré dans l'en-tête HTML 865# de la page. Ce code est toujours le même quel que 866# soit l'arbre (fonctions Javascript) 867# - head2 : code HTML prêt à être inséré dans l'en-tête HTML 868# de la page. Ce code est spécifique à l'arbre 869# (spécifications CSS dépendant de l'id) 870# - onload : code Javascript pour l'état initial (enroulé ou déroulé) 871# initial de l'arbre 872# - html : code HTML pour l'arbre lui-même 873# 874# Exemple d'arbre : 875# {/ 876# {/bin 877# ls sh rm mkdir rmdir } 878# {/etc passwd 879# {/etc/mail sendmail.cf submit.cf}} 880# {/usr 881# {/usr/include 882# {/usr/include/sys types.h} 883# stdio.h} 884# {/usr/bin ...} 885# } 886# } 887# 888# Historique : 889# 2008/06/12 : pda/jean : conception 890# 2008/08/14 : pda : ajout expcoll 891# 892 893proc ::webapp::interactive-tree {id tree expcoll} { 894 set root [lindex $tree 0] 895 set children [lreplace $tree 0 0] 896 set nchildren [llength $children] 897 898 # 899 # Générer le code HTML non spécifique de l'en-tête 900 # 901 902 set head1 $::webapp::treejs 903 regsub -all "%TREEIMAGES%" $head1 $::webapp::treeimages head1 904 905 # 906 # Générer le code HTML de l'en-tête spécifique à cet arbre 907 # 908 909 set head2 $::webapp::treecss 910 regsub -all "%ID%" $head2 $id head2 911 regsub -all "%TREEIMAGES%" $head2 $::webapp::treeimages head2 912 913 # 914 # Générer le code Javascript du "body onload" 915 # 916 917 set onload "javascript:multide('$id','none');" 918 919 # 920 # Générer le code HTML de l'arbre 921 # 922 923 if {$root eq ""} then { 924 set li "" 925 for {set i 0} {$i < $nchildren} {incr i} { 926 set lastnext [expr {$i == $nchildren-1}] 927 append li [::webapp::interactive-tree-rec 1 \ 928 [lindex $children $i] \ 929 $lastnext \ 930 ] 931 append li "\n" 932 } 933 } else { 934 set li [::webapp::interactive-tree-rec 1 $tree 1] 935 } 936 set ul [helem ul $li "id" $id] 937 938 # 939 # Afficher les boutons "tout enrouler" et "tout dérouler" 940 # 941 942 if {[llength $expcoll] > 0} then { 943 set de [lindex $expcoll 0] 944 set en [lindex $expcoll 1] 945 946 set i1 [helem "img" "" \ 947 "src" "$::webapp::treeimages/tree-plus-only.png" \ 948 "alt" "+" \ 949 "onclick" "multide('$id', 'block')" \ 950 "class" "click" \ 951 ] 952 set i2 [helem "img" "" \ 953 "src" "$::webapp::treeimages/tree-minus-only.png" \ 954 "alt" "+" \ 955 "onclick" "multide('$id', 'none')" \ 956 "class" "click" \ 957 ] 958 set ul "$i1 $de $i2 $en\n$ul" 959 } 960 961 # 962 # Résultat final : assemblage des quatre éléments 963 # 964 965 return [list $head1 $head2 $onload $ul] 966} 967 968# level : profondeur (1 .. n) de l'arbre en cours 969# tree : arbre en cours 970# last : 1 si l'arbre est le dernier des fils de l'arbre père 971proc ::webapp::interactive-tree-rec {level tree last} { 972 set root [lindex $tree 0] 973 set children [lreplace $tree 0 0] 974 set nchildren [llength $children] 975 976 if {$nchildren == 0} then { 977 if {$last} then { 978 set file "$::webapp::treeimages/tree-joinbottom.gif" 979 } else { 980 set file "$::webapp::treeimages/tree-join.gif" 981 } 982 set img [helem "img" "" src $file] 983 set li [helem "li" "$img\n$root\n"] 984 } else { 985 set img [helem "img" "" \ 986 "src" "$::webapp::treeimages/tree-plus.gif" \ 987 "alt" "+" \ 988 "onclick" "de(this)" \ 989 "class" "click" \ 990 ] 991 992 set li "" 993 for {set i 0} {$i < $nchildren} {incr i} { 994 set lastnext [expr {$i == $nchildren-1}] 995 append li [::webapp::interactive-tree-rec [expr $level+1] \ 996 [lindex $children $i] \ 997 $lastnext \ 998 ] 999 append li "\n" 1000 } 1001 set class "niv$level" 1002 if {$last} then { 1003 append class " last" 1004 } 1005 set ul [helem "ul" $li "class" $class] 1006 1007 set li [helem "li" "$img\n$root\n$ul\n"] 1008 } 1009 1010 return $li 1011} 1012 1013 1014############################################################################## 1015# Cacher des paramètres dans une liste de champs INPUT HIDDEN 1016############################################################################## 1017 1018# 1019# Cache des paramètres dans une liste de champs INPUT HIDDEN 1020# 1021# Entrée : 1022# - paramètres : 1023# - champs : liste de champs à chercher dans le tableau 1024# - formtab : tableau de champs tels qu'issu de get-data 1025# Sortie : 1026# - valeur de retour : une suite de balises INPUT 1027# 1028# Historique 1029# 1999/11/01 : pda : conception et codage 1030# 2000/07/25 : pda : ajout de \n entre deux HIDDEN 1031# 2006/11/02 : pda : re-ajout de \n entre deux HIDDEN 1032# 1033 1034proc ::webapp::cacher-parametres {champs formtab} { 1035 upvar $formtab ftab 1036 1037 return [::webapp::hide-parameters $champs ftab] 1038} 1039 1040proc ::webapp::hide-parameters {champs formtab} { 1041 upvar $formtab ftab 1042 1043 set html {} 1044 foreach regexp $champs { 1045 foreach c [array names ftab] { 1046 if {! [info exists dejavu($c)] && [regexp "^$regexp\$" $c]} then { 1047 set dejavu($c) 1 1048 foreach v $ftab($c) { 1049 lappend html [::webapp::form-hidden $c $v] 1050 } 1051 } 1052 } 1053 } 1054 return [join $html "\n"] 1055} 1056 1057############################################################################## 1058# Appel d'un autre script cgi 1059############################################################################## 1060 1061# 1062# Appelle un script CGI en respectant le protocole. 1063# 1064# Entrée : 1065# - paramètres : 1066# - formtab : tableau, passé par référence, contenant les champs 1067# de formulaire, tels que get-data les extrait 1068# 1069# Sortie : 1070# - valeur de retour : aucune 1071# - sortie standard : les données du script appelées sont envoyées sur stdout 1072# 1073# Notes : les variables d'environnement suivantes sont modifiées 1074# - REQUEST_METHOD : mis à GET 1075# - PATH_INFO : remis à "" 1076# - QUERY_STRING : la partie après le "?" dans l'URL 1077# - HTTP_COOKIE : les cookies enregistrés via set-cookie 1078# Les autres variables ne sont pas changées. 1079# 1080# Historique : 1081# 2003/06/07 : pda : conception et codage 1082# 2014/05/09 : pda/jean : add cookies 1083# 1084 1085proc ::webapp::call-cgi {script formtab} { 1086 global env 1087 1088 upvar $formtab ftab 1089 1090 # 1091 # On utilise la méthode "GET" 1092 # 1093 1094 set env(REQUEST_METHOD) "GET" 1095 1096 # 1097 # Positionner la "query string" en fonction des paramètres 1098 # 1099 1100 set query {} 1101 foreach key [array names ftab] { 1102 set qkey [::webapp::post-string $key] 1103 foreach val $ftab($key) { 1104 set qval [::webapp::post-string $val] 1105 lappend query "$qkey=$qval" 1106 } 1107 } 1108 set env(QUERY_STRING) [join $query "&"] 1109 1110 # 1111 # Détruit PATH_INFO 1112 # 1113 1114 catch {unset env(PATH_INFO)} 1115 1116 # 1117 # Passer les cookies 1118 # 1119 1120 set hc {} 1121 global wcooktab 1122 foreach name [array names wcooktab] { 1123 lappend hc $wcooktab($name) 1124 } 1125 set env(HTTP_COOKIE) [join $hc ";"] 1126 1127 # 1128 # Appeler le script 1129 # 1130 1131 return [exec $script] 1132} 1133 1134############################################################################## 1135# Traitement des formulaires 1136############################################################################## 1137 1138# 1139# Récupère le contenu d'une FORM, ou de QUERY_STRING ou de PATH_INFO 1140# et place dans le tableau fourni en paramètre les champs trouvés. 1141# 1142# Entrée : 1143# - paramètres : 1144# - formtab : tableau, passé par référence 1145# - param : liste des paramètres des champs, sous la forme 1146# d'une liste {champ nbmin nbmax def}, avec : 1147# champ : nom du champ (regexp) 1148# nbmin/mbmax : nb d'occurrences du champ (si checkbox) 1149# def : valeur par défaut 1150# - variables d'environnement : 1151# - CONTENT_TYPE : doit être "application/x-www-form-urlencoded" 1152# - REQUEST_METHOD : doit être POST 1153# - CONTENT_LENGTH : longueur des données du formulaire 1154# - PATH_INFO : la partie d'URL après le nom du script CGI 1155# - QUERY_STRING : la partie après le "?" dans l'URL 1156# Sortie : 1157# - paramètre formtab : chaque champ du formulaire est placé 1158# dans le tableau, avec comme index l'intitulé du champ 1159# - valeur de retour : {} si erreur, liste des champs lus si pas d'erreur 1160# 1161# Historique : 1162# 1994/08/xx : pda : conception et codage 1163# 1999/02/25 : pda : documentation 1164# 1999/02/26 : pda : changement du test de CONTENT_TYPE (peut être vide) 1165# 1999/04/05 : pda : possibilité d'avoir plusieurs fois le même champ 1166# 1999/04/05 : pda : ajout de la vérification des champs 1167# 1999/10/02 : pda : gestion de plusieurs sources (pathinfo et querystring) 1168# 1999/10/29 : pda : traitement des noms de champs comme des regexp 1169# 1999/11/01 : pda : possibilité de multiples appels et chgt valeur de retour 1170# 1171 1172set ::webapp::gotform 0 1173 1174proc ::webapp::get-data {_ftab param} { 1175 global ::webapp::gotform ::webapp::formtab 1176 upvar $_ftab ftab 1177 1178 if {! $::webapp::gotform} then { 1179 # 1180 # On n'essayera plus de relire les paramètres (ça serait bloquant 1181 # si on essayait de relire sur stdin) lors des appels ultérieurs. 1182 # 1183 1184 set ::webapp::gotform 1 1185 1186 # 1187 # Récupérer les informations de : 1188 # - PATH_INFO 1189 # - QUERY_STRING 1190 # - les champs du formulaire 1191 # 1192 1193 set lus 0 1194 incr lus [::webapp::recuperer-pathinfo ftab $param] 1195 incr lus [::webapp::recuperer-querystring ftab $param] 1196 incr lus [::webapp::recuperer-form ftab $param] 1197 1198 # 1199 # Si on n'a rien lu, il n'y a rien à vérifier 1200 # 1201 1202 if {$lus == 0} then { 1203 return {} 1204 } 1205 } 1206 1207 # 1208 # Boucle de vérification : analyser tous les champs 1209 # listés en paramètre. 1210 # En passant, on met specfield(champ) à 1 pour chaque champ 1211 # trouvé dans le formulaire. 1212 # 1213 1214 foreach p $param { 1215 set nom [lindex $p 0] 1216 set nbmin [lindex $p 1] 1217 set nbmax [lindex $p 2] 1218 set def [lindex $p 3] 1219 if {[info exists ftab($nom)]} then { 1220 if {[::webapp::trouve-form ftab $nom $nbmin $nbmax] == 0} then { 1221 return {} 1222 } 1223 set specfield($nom) 1 1224 } else { 1225 set trouve 0 1226 foreach p [array names ftab] { 1227 if {[regexp "^$nom\$" $p]} then { 1228 if {[::webapp::trouve-form ftab $p $nbmin $nbmax] == 0} then { 1229 return {} 1230 } 1231 set specfield($p) 1 1232 set trouve 1 1233 } 1234 } 1235 1236 if {! $trouve} then { 1237 if {$nbmin > 0} then { 1238 set ftab(_error) "mandatory field '$nom' not found" 1239 return {} 1240 } else { 1241 set ftab($nom) $def 1242 set specfield($nom) 1 1243 } 1244 } 1245 } 1246 } 1247 1248 # 1249 # On renvoie maintenant la liste des éléments trouvés 1250 # 1251 1252 return [array names specfield] 1253} 1254 1255proc ::webapp::trouve-form {formtab nom nbmin nbmax} { 1256 upvar $formtab tab 1257 set n [llength $tab($nom)] 1258 if {$n < $nbmin || $n > $nbmax} then { 1259 set tab(_error) "invalid number of fields ($n) for parameter '$nom'" 1260 return 0 1261 } 1262} 1263 1264proc ::webapp::get-keyval {formtab l} { 1265 upvar $formtab tab 1266 1267 foreach arg $l { 1268 if {[regexp {^([^=]+)=(.*)$} $arg bidon key val]} then { 1269 set key [::webapp::unpost-string $key] 1270 set val [::webapp::unpost-string $val] 1271 lappend tab($key) $val 1272 } 1273 } 1274} 1275 1276proc ::webapp::recuperer-pathinfo {formtab param} { 1277 upvar $formtab tab 1278 1279 set lcomposants [::webapp::pathinfo] 1280 1281 if {[llength $lcomposants] == 0} then { 1282 return 0 1283 } 1284 1285 ::webapp::get-keyval tab $lcomposants 1286 1287 return 1 1288} 1289 1290proc ::webapp::recuperer-querystring {formtab param} { 1291 global env 1292 upvar $formtab tab 1293 1294 if {! [info exists env(QUERY_STRING)]} then { 1295 return 0 1296 } 1297 1298 ::webapp::get-keyval tab [split $env(QUERY_STRING) "&"] 1299 1300 return 1 1301} 1302 1303# 1304# Décode les éléments d'un formulaire en format "x-www-form-urlencoded" 1305# 1306# Entrée : 1307# - paramètres : 1308# - formtab : tableau de champs, cf get-data 1309# Sortie : 1310# - valeur de retour : 1 si ok, 0 si erreur 1311# 1312# Historique 1313# 2003/06/01 : pda : séparation de recuperer-form 1314# 1315 1316proc ::webapp::x-www-form-urlencoded {formtab} { 1317 global env 1318 upvar $formtab tab 1319 1320 # 1321 # Méthode classique pour récupérer les champs 1322 # des formulaires 1323 # 1324 1325 if {! [info exists env(CONTENT_LENGTH)]} then { 1326 lappend tab(_error) "non existant CONTENT_LENGTH" 1327 return 0 1328 } 1329 set line [read stdin $env(CONTENT_LENGTH)] 1330 1331 ::webapp::get-keyval tab [split $line "&"] 1332 1333 return 1 1334} 1335 1336# 1337# Décode une sous-partie MIME d'un formulaire en format "form-data" 1338# 1339# Entrée : 1340# - paramètres : 1341# - formtab : tableau de champs, cf get-data 1342# - entete : l'en-tête de la sous-partie 1343# - corps : le corps de la sous-partie 1344# Sortie : 1345# - valeur de retour : 1 si ok, 0 si erreur 1346# 1347# Notes : 1348# - le format de l'en-tête de la sous-partie est : 1349# Content-Disposition: form-data; name="<champ formulaire>"; filename="..." 1350# Content-Type: image/gif 1351# - le corps est le contenu du fichier. 1352# - si c'est une variable classique de formulaire, il n'y a pas de filename= 1353# 1354# Historique 1355# 2003/06/01 : pda : commentaires 1356# 1357 1358proc ::webapp::get-mime-part {formtab entete corps} { 1359 upvar $formtab tab 1360 1361 set hdrre {^([^: \t]+):[ \t]*(.*)} 1362 set subhdrre {^([^= \t]+)[ \t]*=[ \t]*(.*)} 1363 set unquotere {^"([^"]*)"$} 1364 1365 # 1366 # Traitement de l'en-tête 1367 # 1368 1369 regsub -all -- "\r\n" $entete "\n" entete 1370 foreach ligne [split $entete "\n"] { 1371 # 1372 # Première partie : séparer "nom: valeur" (ex: Content-Disposition: ...) 1373 # 1374 if {! [regexp $hdrre $ligne bidon hname hval]} then { 1375 return 0 1376 } 1377 # nom du champ d'en-tête 1378 set hname [string tolower $hname] 1379 1380 # la valeur peut elle-même être de la forme "val;clef=val;clef=val..." 1381 set hval [split $hval ";"] 1382 1383 set subhdrlist {} 1384 lappend subhdrlist VALEUR 1385 lappend subhdrlist [lindex $hval 0] 1386 1387 # 1388 # Parcourir toutes les sous-valeurs de la ligne d'en-tête 1389 # 1390 1391 foreach hv [lrange $hval 1 end] { 1392 if {! [regexp $subhdrre [string trim $hv] bidon clef val]} then { 1393 lappend tab(_error) "Invalid form-data sub-header name '$hname'" 1394 return 0 1395 } 1396 if {[regexp $unquotere $val bidon v]} then { 1397 set val $v 1398 } 1399 lappend subhdrlist [string tolower $clef] 1400 lappend subhdrlist $val 1401 } 1402 array set sh $subhdrlist 1403 1404 # 1405 # Une fois la ligne d'en-tête complètement parcourue, regarder 1406 # quels sont les associations "clef/valeur" obtenues. 1407 # Ces associations sont dans le tableau sh() 1408 # sh(VALEUR) : valeur du champ d'en-tête 1409 # sh(name) : nom de la variable du formulaire 1410 # sh(filename) : nom du fichier fourni par le client 1411 # 1412 1413 switch -- $hname { 1414 content-disposition { 1415 if {! [string equal -nocase $sh(VALEUR) "form-data"]} then { 1416 lappend tab(_error) "Invalid Content-Disposition header" 1417 return 0 1418 } 1419 if {! [info exists sh(name)]} then { 1420 lappend tab(_error) "No 'name' attribute in form" 1421 return 0 1422 } 1423 set h(name) $sh(name) 1424 if {[info exists sh(filename)]} then { 1425 set h(filename) $sh(filename) 1426 } 1427 } 1428 content-type { 1429 set h(contenttype) $sh(VALEUR) 1430 } 1431 default { 1432 lappend tab(_error) "Invalid form-data sub-header name '$hname'" 1433 return 0 1434 } 1435 } 1436 unset sh 1437 } 1438 1439 # 1440 # Traitement du corps 1441 # 1442 1443 if {! [info exists h(name)]} then { 1444 lappend tab(_error) "No 'name' attribute in form" 1445 return 0 1446 } 1447 set name $h(name) 1448 1449 # 1450 # Si c'est un fichier, le placer dans une liste de la forme 1451 # {file <type> <filename> <content>} 1452 # Sinon, nettoyer les \r\n 1453 # 1454 1455 if {[info exists h(filename)]} then { 1456 if {! [info exists h(contenttype)]} then { 1457 set h(contenttype) application/octet-stream 1458 } 1459 1460 lappend tab($name) [list "file" $sh(filename) $h(contentype) $corps] 1461 1462 } else { 1463 # 1464 # Variable classique (i.e. pas un fichier) 1465 # 1466 regsub -all -- "\r\n" $corps "\n" corps 1467 lappend tab($name) $corps 1468 } 1469 1470 return 1 1471} 1472 1473proc ::webapp::form-data {formtab contenttype} { 1474 global env 1475 upvar $formtab tab 1476 1477 # 1478 # Méthode pour récupérer les champs des formulaires 1479 # spécifiée dans la RFC 1867, notamment pour gérer 1480 # les fichiers. 1481 # 1482 1483 if {! [info exists env(CONTENT_LENGTH)]} then { 1484 lappend tab(_error) "non existant CONTENT_LENGTH" 1485 return 0 1486 } 1487 1488 # 1489 # Extraire le délimiteur 1490 # 1491 1492 set boundary "" 1493 foreach element [split $contenttype ";"] { 1494 if {[regexp {boundary=(.*)} $element bidon boundary]} then { 1495 break 1496 } 1497 } 1498 if {[string equal $boundary ""]} then { 1499 lappend tab(_error) "boundary not found in CONTENT_TYPE" 1500 return 0 1501 } 1502 set boundary "--$boundary" 1503 1504 # 1505 # Lire les données du formulaire et les mettre en mémoire 1506 # 1507 1508 fconfigure stdin -translation binary 1509 set line [read stdin $env(CONTENT_LENGTH)] 1510 1511 set fd [open /tmp/g.log w] 1512 fconfigure $fd -translation binary 1513 puts $fd $line 1514 close $fd 1515 1516 # 1517 # Rechercher le premier délimiteur 1518 # 1519 1520 set offset [string first $boundary $line 0] 1521 if {$offset == -1} then { 1522 lappend tab(_error) "Invalid form-data encoding (no first boundary)" 1523 return 0 1524 } 1525 set blen [string length $boundary] 1526 1527 incr offset $blen 1528 1529 # 1530 # Invariants de boucle 1531 # - offset = indice juste après le délimiteur (qui correspond soit à 1532 # "\r\n", soit à "--\r\n" si c'est le dernier) 1533 # - retval = 1 si aucune erreur ne s'est produite 1534 # 1535 # 1536 1537 set retval 1 1538 while {[set next [string first $boundary $line $offset]] != -1} { 1539 # - next = indice du délimiteur suivant 1540 1541 # 1542 # Arrêt si le premier délimiteur correspond à une fin 1543 # d'arguments. Ce cas ne devrait jamais arriver, mais 1544 # il vaut mieux prévoir l'impossible... 1545 # 1546 1547 if {[string equal [string range $line $offset [expr $offset+1]] "--"]} then { 1548 break 1549 } 1550 1551 # on saute le \r\n 1552 incr offset 2 1553 1554 # 1555 # Séparation de l'en-tête et du corps 1556 # 1557 1558 set sephdr [string first "\r\n\r\n" $line $offset] 1559 set entete [string range $line $offset [expr $sephdr-1]] 1560 1561 set r [::webapp::get-mime-part tab \ 1562 [string range $line $offset [expr $sephdr-1]] \ 1563 [string range $line [expr $sephdr+4] [expr $next-3]] \ 1564 ] 1565 if {$r == 0} then { 1566 lappend tab(_error) "Invalid form-data encoding of subpart" 1567 set retval 0 1568 } 1569 1570 set offset [expr $next + $blen] 1571 } 1572 1573 return $retval 1574} 1575 1576proc ::webapp::recuperer-form {formtab param} { 1577 global env 1578 upvar $formtab tab 1579 1580 if {! [info exists env(REQUEST_METHOD)]} then { 1581 lappend tab(_error) "non existant REQUEST_METHOD" 1582 return 0 1583 } 1584 if {! [string equal $env(REQUEST_METHOD) "POST"]} then { 1585 lappend tab(_error) "invalid method '$env(REQUEST_METHOD)'" 1586 return 0 1587 } 1588 1589 # 1590 # Traitement de content-type 1591 # 1592 1593 if {[info exists env(CONTENT_TYPE)]} then { 1594 set type $env(CONTENT_TYPE) 1595 } else { 1596 # 1597 # Cas particulier du browser de KDE 1 : si 1598 # CONTENT_TYPE est vide, c'est implicitement 1599 # "application/x-www-form-urlencoded". 1600 # 1601 set type application/x-www-form-urlencoded 1602 } 1603 1604 switch -glob -- $type { 1605 application/x-www-form-urlencoded { 1606 set r [::webapp::x-www-form-urlencoded tab] 1607 } 1608 multipart/form-data* { 1609 set r [::webapp::form-data tab $type] 1610 } 1611 default { 1612 lappend tab(_error) "invalid CONTENT_TYPE '$env(CONTENT_TYPE)'" 1613 set r 0 1614 } 1615 } 1616 1617 # 1618 # On a lu quelque chose 1619 # 1620 1621 return $r 1622} 1623 1624# 1625# Convertit une chaîne (données d'un formulaire) en caractères 1626# "normaux" 1627# 1628# Entrée : 1629# - paramètres : 1630# - str : la chaîne à convertir 1631# Sortie : 1632# - valeur de retour : la chaîne convertie 1633# 1634# Historique 1635# 1994/08/xx : pda : conception et codage 1636# 1999/02/25 : pda : documentation 1637# 2001/02/28 : pda : remplacement des \r\n par \n 1638# 2010/10/28 : pda : simplification du décodage, merci Tcl 1639# 1640 1641proc ::webapp::unpost-string {str} { 1642 # 1643 # Remplace tous les espaces 1644 # 1645 regsub -all "\\+" $str " " str 1646 1647 # 1648 # Remplace tous les %xx par le caractère correspondant 1649 # 1650 1651 set pos 0 1652 while {[set pos [string first "%" $str $pos]] > -1} { 1653 set code [scan [string range $str $pos+1 $pos+2] "%x"] 1654 set str [string replace $str $pos $pos+2 [format "%c" $code]] 1655 incr pos 1656 } 1657 1658 set new [encoding convertfrom utf-8 $str] 1659 1660 # 1661 # Nettoyage des mauvais caractères de fin de ligne 1662 # 1663 regsub -all -- "\r\n" $new "\n" new 1664 regsub -all -- "\r" $new "\n" new 1665 1666 return $new 1667} 1668 1669# 1670# Convertit une chaîne contenant éventuellement des caractères spéciaux 1671# HTML en chaîne dans laquelle les caractères spéciaux sont remplacés 1672# par des caractères "%.." 1673# 1674# Entrée : 1675# - paramètres : 1676# - str : la chaîne à convertir 1677# Sortie : 1678# - valeur de retour : la chaîne convertie 1679# 1680# Historique 1681# 1999/11/01 : pda : conception 1682# 1683 1684proc ::webapp::post-string {str} { 1685 # 1686 # Remplace tous les caractères spéciaux 1687 # 1688 regsub -all {%} $str "%25" str 1689 regsub -all {\+} $str "%2B" str 1690 regsub -all {\&} $str "%26" str 1691 regsub -all "\n" $str "%0A" str 1692 regsub -all "\r" $str "%0D" str 1693 regsub -all {\<} $str "%3C" str 1694 regsub -all {=} $str "%3D" str 1695 regsub -all {\>} $str "%3E" str 1696 regsub -all {\?} $str "%3F" str 1697 regsub -all {"} $str "%22" str 1698 regsub -all {"} $str "%22" str 1699 regsub -all { } $str "%20" str 1700 1701 return $str 1702} 1703 1704# 1705# Convertit une chaîne contenant éventuellement des caractères spéciaux 1706# HTML en chaîne dans laquelle les caractères spéciaux sont remplacés 1707# par des caractères "&...;" 1708# 1709# Entrée : 1710# - paramètres : 1711# - str : la chaîne à convertir 1712# Sortie : 1713# - valeur de retour : la chaîne convertie 1714# 1715# Historique 1716# 1999/11/02 : pda : conception 1717# 1718 1719proc ::webapp::html-string {str} { 1720 # 1721 # Remplace tous les caractères spéciaux 1722 # 1723 regsub -all {\&} $str {\&} str 1724 regsub -all {\<} $str {\<} str 1725 regsub -all {\>} $str {\>} str 1726 regsub -all {"} $str {\"} str 1727 1728 return $str 1729} 1730 1731# 1732# Import form variables in individual Tcl variables 1733# 1734# Entrée : 1735# - paramètres : 1736# - _ftab : tableau, passé par référence, contenant les valeurs 1737# des paramètres fournis au formulaire (see get-data) 1738# - fspec : optional form specification (see get-data) 1739# Sortie : 1740# - variables nommées par formtab : initialisées 1741# - valeur de retour : none 1742# 1743# Historique : 1744# 2006/08/29 : pda : conception et codage 1745# 2010/12/16 : pda : add fspec 1746# 1747 1748proc ::webapp::import-vars {_ftab {fspec {}}} { 1749 upvar $_ftab ftab 1750 1751 if {[llength $fspec] == 0} then { 1752 foreach varname [array names ftab] { 1753 upvar $varname var 1754 set var $ftab($varname) 1755 } 1756 } else { 1757 # keep max number of occurrence to make a single value or a list 1758 # while at here, keep a log of found specifiers 1759 foreach s $fspec { 1760 lassign $s re min max def 1761 set m($re) $max 1762 set found($re) 0 1763 } 1764 foreach varname [array names ftab] { 1765 foreach s $fspec { 1766 lassign $s re min max def 1767 if {[regexp "^$re\$" $varname]} then { 1768 set found($re) 1 1769 upvar $varname var 1770 set val $ftab($varname) 1771 if {$m($re) <= 1} then { 1772 set val [string trim [lindex $val 0]] 1773 } 1774 set var $val 1775 break 1776 } 1777 } 1778 } 1779 foreach s $fspec { 1780 lassign $s re min max def 1781 if {! $found($re)} then { 1782 upvar $re var 1783 set var {} 1784 } 1785 } 1786 } 1787} 1788 1789############################################################################## 1790# Mail et adresses électroniques 1791############################################################################## 1792 1793# 1794# Vérifie si une adresse électronique est valide, 1795# c'est à dire si elle vérifie les conditions suivantes : 1796# - présence de "@" 1797# - absence d'espace et de tabulations 1798# 1799# Entrée : 1800# - paramètres : 1801# - email : adresse électronique telle que saisie par l'utilisateur 1802# Sortie : 1803# - valeur de retour : 0 (adresse incorrecte) ou 1 (adresse correcte) 1804# 1805# Historique 1806# 1994/08/xx : pda : conception et codage 1807# 1999/02/25 : pda : documentation 1808# 1809 1810proc ::webapp::valid-email {email} { 1811 set email [string trim $email] 1812 1813 if {[string first "@" $email] == -1} then { return 0 } 1814 if {[string first " " $email] != -1} then { return 0 } 1815 if {[string first "\t" $email] != -1} then { return 0 } 1816 return 1 1817} 1818 1819# 1820# Envoi d'un mail 1821# 1822# Entrée : 1823# - paramètres : 1824# - from : l'émetteur 1825# - replyto : le destinataire des réponses 1826# - to : le ou les destinataires 1827# - cc : le ou les destinataires, si besoin est 1828# - bcc : destinataire caché, si besoin est 1829# - subject : le sujet 1830# - texte : le texte 1831# - type : le type du mail, par défaut 'text/plain; charset="utf-8"' 1832# 1833# Sortie : 1834# - valeur de retour : aucune 1835# 1836# Historique : 1837# 2003/09/29 : pda : conception et codage 1838# 2009/02/23 : pda : ajout paramètre optionnel type 1839# 1840 1841proc ::webapp::mail {from replyto to cc bcc subject texte {type {}}} { 1842 set fd [open "|$::webapp::sendmail" "w"] 1843 1844 set to [join $to ", "] 1845 puts $fd "From: $from" 1846 puts $fd "To: $to" 1847 1848 if {! [string equal $cc ""]} then { 1849 puts $fd "Cc: $cc" 1850 } 1851 if {! [string equal $bcc ""]} then { 1852 puts $fd "Bcc: $bcc" 1853 } 1854 if {! [string equal $replyto ""]} then { 1855 puts $fd "Reply-to: $replyto" 1856 } 1857 if {[string equal $type ""]} then { 1858 set type {text/plain; charset="utf-8"} 1859 } 1860 if {! [string is ascii $subject]} then { 1861 set subject [::mime::word_encode "utf-8" "quoted-printable" $subject] 1862 } 1863 puts $fd "Subject: $subject" 1864 puts $fd "Mime-Version: 1.0" 1865 puts $fd "Content-Type: $type" 1866 puts $fd "Content-Transfer-Encoding: 8bit" 1867 puts $fd "" 1868 puts $fd $texte 1869 close $fd 1870} 1871 1872############################################################################## 1873# Génération d'une page HTML par substitution dans une page existante 1874############################################################################## 1875 1876# 1877# Substitue, dans un fichier, des motifs par des valeurs calculées 1878# par le script CGI. 1879# 1880# Entrée : 1881# - paramètres : 1882# - fichier : le nom du fichier servant de base pour la substitution 1883# - subst : liste de susbtitutions, de la forme 1884# {{motif valeur} {motif valeur} ...} 1885# - encoding: name of encoding 1886# Sortie : 1887# - valeur de retour : le fichier susbtitué 1888# 1889# Historique 1890# 1999/03/25 : pda : conception et codage 1891# 1999/11/02 : pda : suppression de & comme caractère spécial 1892# 2002/05/12 : pda : suppression de \ comme caractère spécial 1893# 2010/10/16 : pda : add encoding parameter 1894# 2010/12/22 : pda/jean : encoding defaults to utf-8 1895# 1896 1897proc ::webapp::file-subst {fichier subst {encoding utf-8}} { 1898 set fd [open $fichier r] 1899 if {$encoding ne ""} then { 1900 fconfigure $fd -encoding $encoding 1901 } 1902 set string [read $fd] 1903 close $fd 1904 1905 foreach l $subst { 1906 set motif [lindex $l 0] 1907 set valeur [lindex $l 1] 1908 1909 regsub -all {\\} $valeur {\\&} valeur 1910 regsub -all {\&} $valeur {\\&} valeur 1911 1912 regsub -all -- $motif $string $valeur string 1913 } 1914 return $string 1915} 1916 1917############################################################################## 1918# Gestion des sessions 1919############################################################################## 1920 1921# 1922# Récupère une chaîne aléatoire (ou pseudo-aléatoire) 1923# 1924# Entrée : 1925# - paramètres : - 1926# Sortie : 1927# - valeur de retour : une chaîne de 20 chiffres 1928# 1929# Historique 1930# 1999/07/14 : pda : conception 1931# 1932 1933proc ::webapp::random {} { 1934 set rand "" 1935 1936 append rand [format "%03d" [expr [clock clicks] % 1000]] 1937 # rand contains now 3 digits 1938 1939 append rand [format "%05d" [pid]] 1940 # rand contains now 8 digits 1941 1942 # %d = day of month 01..31 1943 # %H = hour 00..23 1944 # %j = day of the year 001..366 1945 # %M = minute 00..59 1946 # %S = second 00..59 1947 # %w = weekday 0..6 1948 append rand [clock format [clock seconds] -format "%d%H%j%M%S%w"] 1949 # rand contains now 20 digits 1950 1951 return $rand 1952} 1953 1954############################################################################## 1955# Sortie d'une page Web ou autre 1956############################################################################## 1957 1958# 1959# Sort une page Web ou autre 1960# 1961# Entrée : 1962# - paramètres : 1963# - type : le type de sortie, html ou pdf 1964# - page : la page (en html si html, en latex si pdf) 1965# - fichier : nom de fichier à renvoyer 1966# Sortie : 1967# - envoi direct sur la sortie standard 1968# 1969# Historique 1970# 2002/05/20 : pda : conception 1971# 2002/06/21 : pda : ajout de types 1972# 2002/10/24 : pda : ajout de la sortie csv 1973# 2008/02/27 : jean/zamboni : gestion des extensions de nom de fichiers 1974# 1975 1976proc ::webapp::send {type page {fichier "output"}} { 1977 1978 # 1979 # Détermine l'extension du fichier 1980 # 1981 switch -- $type { 1982 rawpdf { set ext "pdf" } 1983 jpeg { set ext "jpg" } 1984 default { set ext $type } 1985 } 1986 1987 # 1988 # on rajoute une extension au nom de fichier si necessaire 1989 # 1990 if {! [regexp "\.$ext\$" $fichier] } then { 1991 append fichier "." $ext 1992 } 1993 1994 switch -- $type { 1995 html { ::webapp::sortie-html $page } 1996 csv { ::webapp::sortie-csv $page $fichier } 1997 png { ::webapp::sortie-bin image/png $page $fichier } 1998 gif { ::webapp::sortie-bin image/gif $page $fichier } 1999 jpeg { ::webapp::sortie-bin image/jpeg $page $fichier } 2000 rawpdf { ::webapp::sortie-bin application/pdf $page $fichier } 2001 pdf { ::webapp::sortie-latex $page $fichier } 2002 } 2003} 2004 2005# 2006# Sort une page Web ou autre 2007# 2008# Entrée : 2009# - paramètres : 2010# - page : la page HTML, sans le content-type 2011# Sortie : 2012# - envoi direct sur la sortie standard 2013# 2014# Historique 2015# 2001/10/20 : pda : conception et codage 2016# 2014/03/28 : pda/jean : add cookies 2017# 2018 2019proc ::webapp::sortie-html {page} { 2020 fconfigure stdout -encoding utf-8 2021 puts stdout "Content-type: text/html; charset=utf-8" 2022 http-send-cookies 2023 puts stdout "" 2024 puts stdout $page 2025} 2026 2027# 2028# Sort un fichier CSV 2029# 2030# Entrée : 2031# - paramètres : 2032# - page : le fichier CSV, sans le content-type 2033# - fichier : nom de fichier à renvoyer 2034# Sortie : 2035# - envoi direct sur la sortie standard 2036# 2037# Historique 2038# 2002/10/24 : pda : conception et codage 2039# 2008/02/27 : jean/zamboni : Content-type et filename 2040# 2014/03/28 : pda/jean : add cookies 2041# 2042 2043proc ::webapp::sortie-csv {page fichier} { 2044 fconfigure stdout -encoding utf-8 2045 puts stdout "Content-type: text/csv; charset=utf-8" 2046 puts stdout "Content-Disposition: attachment; filename=$fichier" 2047 http-send-cookies 2048 puts stdout "" 2049 puts stdout $page 2050} 2051 2052# 2053# Sort un document binaire 2054# 2055# Entrée : 2056# - paramètres : 2057# - type : type MIME 2058# - page : le fichier 2059# - fichier : nom de fichier à renvoyer 2060# Sortie : 2061# - envoi direct sur la sortie standard 2062# 2063# Historique 2064# 2002/05/21 : pda : conception et codage 2065# 2008/02/27 : jean/zamboni : ajout filename 2066# 2014/03/28 : pda/jean : add cookies 2067# 2068 2069proc ::webapp::sortie-bin {type page fichier} { 2070 puts stdout "Content-type: $type" 2071 puts stdout "Content-Disposition: attachment; filename=$fichier" 2072 http-send-cookies 2073 puts stdout "" 2074 flush stdout 2075 fconfigure stdout -translation binary 2076 puts -nonewline stdout $page 2077} 2078 2079 2080# 2081# Sort un document latex compilé en pdf 2082# 2083# Entrée : 2084# - paramètres : 2085# - page : le source latex, prêt à être compilé 2086# - fichier : nom de fichier à renvoyer 2087# - variable globale debuginfos : valeur latexfiles 2088# Sortie : 2089# - envoi direct sur la sortie standard 2090# 2091# Historique 2092# 2002/05/11 : pda : conception et codage 2093# 2002/05/12 : pda : ajout de debuginfos 2094# 2008/02/27 : jean/zamboni : ajout filename 2095# 2012/01/09 : pda : encoding to utf8 2096# 2097 2098proc ::webapp::sortie-latex {page fichier} { 2099 global errorInfo 2100 2101 if {[lsearch $::webapp::debuginfos latexsource] != -1} then { 2102 ::webapp::sortie-html \ 2103 "<PRE>$page</PRE>" 2104 return 2105 } 2106 2107 # 2108 # Le changement de répertoire est nécessaire car latex dépose 2109 # des fichiers .aux, .log et .pdf dans le répertoire courant. 2110 # 2111 2112 cd $::webapp::tmpdir 2113 2114 # 2115 # Nommage des fichiers utilisés. Le répertoire est absolu, 2116 # c'est plus clair dans les messages d'erreur. 2117 # 2118 2119 set prefix $::webapp::tmpdir/arrgen[pid] 2120 set texfile "${prefix}.tex" 2121 set pdffile "${prefix}.pdf" 2122 set auxfile "${prefix}.aux" 2123 set logfile "${prefix}.log" 2124 2125 # 2126 # Envoi du source latex dans le fichier 2127 # 2128 2129 if {[catch {set fd [open $texfile "w"]} m]} then { 2130 ::webapp::sortie-html \ 2131 "Impossible de créer '$texfile': <PRE>$errorInfo</PRE>" 2132 return 2133 } 2134 fconfigure $fd -encoding utf-8 2135 puts $fd $page 2136 close $fd 2137 2138 # 2139 # Génération du fichier pdf 2140 # 2141 2142 if {[catch {set log [exec $::webapp::pdflatex $texfile]} msg]} then { 2143 ::webapp::sortie-html \ 2144 "Impossible de générer '$pdffile': <PRE>$errorInfo</PRE>" 2145 return 2146 } 2147 2148 # 2149 # Sortie du résultat 2150 # 2151 2152 if {[catch {set fd [open $pdffile "r"]} m]} then { 2153 ::webapp::sortie-html \ 2154 "Impossible de lire '$pdffile': <PRE>$errorInfo</PRE>" 2155 return 2156 } 2157 fconfigure $fd -translation binary 2158 set pdf [read $fd] 2159 close $fd 2160 2161 puts stdout "Content-Type: application/pdf" 2162 puts stdout "Content-Disposition: attachment; filename=$fichier" 2163 http-send-cookies 2164 puts stdout "" 2165 flush stdout 2166 fconfigure stdout -translation binary 2167 puts -nonewline stdout $pdf 2168 2169 # 2170 # Effacement des fichiers temporaires 2171 # 2172 2173 if {[lsearch $::webapp::debuginfos latexfiles] == -1} then { 2174 file delete -force -- $texfile $pdffile $auxfile $logfile 2175 } 2176} 2177 2178# 2179# Sort une redirection 2180# 2181# Entrée : 2182# - paramètres : 2183# - url : redirect url (or relative path) 2184# Sortie : 2185# - envoi direct sur la sortie standard 2186# 2187# Historique 2188# 2015/02/18 : pda/jean : creation 2189# 2190 2191proc ::webapp::redirect {url} { 2192 fconfigure stdout -encoding utf-8 2193 puts stdout "Location: $url" 2194 http-send-cookies 2195 puts stdout "" 2196} 2197 2198############################################################################## 2199# Sortie des erreurs dans une belle page Web 2200############################################################################## 2201 2202# 2203# Sortie des erreurs dans une belle page Web 2204# 2205# Entrée : 2206# - paramètres : 2207# - page : fichier contenant la page HTML à trous 2208# - msg : le message d'erreur 2209# Sortie : pas de sortie, la procédure fait un exit. 2210# 2211# Historique 2212# 2000/07/26 : pda : conception 2213# 2000/07/27 : pda : documentation 2214# 2001/10/20 : pda : utilisation de la procédure de sortie 2215# 2002/12/26 : pda : mise en package 2216# 2003/12/11 : pda : ajout du traitement de \n 2217# 2218 2219proc ::webapp::error-exit {page msg} { 2220 set msg [::webapp::html-string $msg] 2221 regsub -all "\n" $msg "<br>" msg 2222 ::webapp::send html [::webapp::file-subst $page \ 2223 [list [list %MESSAGE% $msg] \ 2224 ] \ 2225 ] 2226 exit 0 2227} 2228 2229############################################################################## 2230# Des fois, il faut bien avoir recours aux dernières extrémités... 2231############################################################################## 2232 2233# 2234# Affiche tous les paramètres fournis au script CGI. 2235# 2236# Entrée : tout l'environnement d'un script CGI 2237# Sortie : 2238# - envoi direct 2239# 2240# Historique 2241# 1999/03/25 : pda : conception et codage 2242# 2243 2244proc ::webapp::cgidebug {} { 2245 global env argv 2246 2247 puts "Content-type: text/html" 2248 http-send-cookies 2249 puts "" 2250 2251 puts "<TITLE>Debug information</TITLE>" 2252 puts "<H1>Debug information</H1>" 2253 2254 set pwd [exec pwd] 2255 puts "Working directory = $pwd <P>" 2256 2257 puts "Parameters : <P>" 2258 set n 0 2259 puts "<UL>" 2260 foreach i $argv { 2261 incr n 2262 puts "<LI> arg $n = /$i/" 2263 } 2264 puts "</UL>" 2265 2266 puts "Environment : <P>" 2267 puts "<UL>" 2268 foreach i [lsort [array names env]] { 2269 puts "<LI> $i=$env($i)" 2270 } 2271 puts "</UL>" 2272 2273 if {[info exists env(CONTENT_LENGTH)]} then { 2274 puts "Standard input : <P>" 2275 puts "<CODE>" 2276 puts [read stdin $env(CONTENT_LENGTH)] 2277 puts "</CODE>" 2278 } 2279} 2280 2281############################################################################## 2282# Protéger l'accès à des applications 2283############################################################################## 2284 2285# 2286# Teste l'existence d'un fichier et interdit l'accès à 2287# l'application si le fichier existe. 2288# 2289# Entrée : 2290# - paramètres : 2291# - ftest : fichier à tester, contenant le message d'interdiction 2292# - lusers : liste d'utilisateurs autorisés à accéder quand même 2293# - ferr : fichier HTML à trou (%MESSAGE% = message d'interdiction) 2294# - variables d'environnement : 2295# - REMOTE_USER : une chaîne de la forme "login"" 2296# Sortie : 2297# - envoi direct, ou rien du tout 2298# 2299# Historique 2300# 1999/03/25 : pda : conception et codage 2301# 1999/06/21 : pda : fin de la conception 2302# 2303 2304proc ::webapp::nologin {ftest lusers ferr} { 2305 set user [::webapp::user] 2306 if {[file exists $ftest]} then { 2307 if {[string equal $user ""] || [lsearch -exact $lusers $user] == -1} then { 2308 set fd [open $ftest r] 2309 set message [read $fd] 2310 close $fd 2311 2312 ::webapp::send html [::webapp::file-subst $ferr \ 2313 [list \ 2314 [list %MESSAGE% $message] \ 2315 ] \ 2316 ] 2317 exit 0 2318 } 2319 } 2320} 2321 2322############################################################################## 2323# Une interface agréable pour la programmation des scripts CGI 2324############################################################################## 2325 2326proc ::webapp::cgi-env {} { 2327} 2328 2329proc ::webapp::cgi-get {} { 2330} 2331 2332proc ::webapp::cgi-err {msg debug} { 2333 global argv 2334 2335 set script [::webapp::script-name] 2336 set date [clock format [clock seconds]] 2337 2338 set page "" 2339 append page "<HTML>\n" 2340 append page "<HEAD><TITLE>Error !</TITLE></HEAD>\n" 2341 append page "<BODY TEXT=#000000 BGCOLOR=#FFFFFF>\n" 2342 append page "<FONT FACE=\"Arial,Helvetica\">\n" 2343 append page "<H1>Internal error!</H1>\n" 2344 2345 if {$debug} then { 2346 set pwd [exec pwd] 2347 2348 append page "Error detected in script '$script'\n" 2349 append page "on '$date' :\n" 2350 append page "<HR>\n" 2351 append page "<PRE>[::webapp::html-string $msg]</PRE>\n" 2352 append page "<HR>\n" 2353 2354 append page "<H2>Context</H2>\n" 2355 append page "Directory = $pwd<P>\n" 2356 2357 append page "Parameters :<BR>\n" 2358 set n 0 2359 append page "<UL>\n" 2360 foreach i $argv { 2361 incr n 2362 append page "<LI> arg $n = /[::webapp::html-string $i]/\n" 2363 } 2364 append page "</UL>\n" 2365 2366 append page "Environment :<BR>\n" 2367 append page "<UL>\n" 2368 foreach i [lsort [array names env]] { 2369 append page "<LI> $i=[::webapp::html-string $env($i)]\n" 2370 } 2371 append page "</UL>\n" 2372 2373 if {[info exists env(CONTENT_LENGTH)]} then { 2374 append page "Standard input : <P>\n" 2375 append page "<CODE>\n" 2376 append page [::webapp::html-string [read stdin $env(CONTENT_LENGTH)]] 2377 append page "</CODE>\n" 2378 } 2379 } else { 2380 append page "Error detected in script :\n" 2381 append page "<UL>\n" 2382 append page "<LI> on '$date'\n" 2383 append page "<LI> in '$script'\n" 2384 append page "</UL>\n" 2385 append page "Please contact your Netmagis administrator\n" 2386 append page "and send her/him a copy of this message.\n" 2387 2388 puts stderr "\[$date\] webapp/$script: $msg" 2389 } 2390 append page "</BODY></HTML>\n" 2391 2392 ::webapp::send html $page 2393} 2394 2395# 2396# Lance l'exécution d'un script CGI 2397# 2398# Entrée : 2399# - tout l'environnement d'un script CGI 2400# - paramètres : 2401# - script : nom du script à exécuter, avec paramètres éventuels 2402# - debug : 1 s'il faut sortir l'environnement, ou 0 pour un simple message 2403# Sortie : 2404# - envoi direct 2405# 2406# Historique 2407# 2001/06/20 : pda : conception 2408# 2409 2410proc ::webapp::cgi-exec {script {debug 0}} { 2411 global errorInfo 2412 2413 ::webapp::cgi-env 2414 if [catch $script msg] then { 2415 # on n'utilise pas msg, car errorInfo le contient déjà 2416 ::webapp::cgi-err $errorInfo $debug 2417 } 2418 exit 0 2419} 2420 2421# 2422# Classe "utilisateur dans la base d'authentification" 2423# 2424# Représente les attributs d'un utilisateur tel qu'il est stocké 2425# dans la base d'authentification (PostgreSQL ou LDAP) sous une 2426# forme unifiée. 2427# 2428# Options : 2429# aucune 2430# 2431# Méthodes 2432# get : récupère la valeur (unique) d'un attribut 2433# set : modifie la valeur d'un attribut (en mémoire uniquement). 2434# C'est une méthode utilisée uniquement par la classe authbase 2435# exists : indique si l'utilisateur a été trouvé dans la base. 2436# 2437# Historique 2438# 2007/10/05 : pda/jean : intégration et documentation 2439# 2440 2441snit::type ::webapp::authuser { 2442 variable exists 0 2443 variable attrvals -array {} 2444 2445 method exists {{value {}}} { 2446 if {$value ne ""} then { 2447 set exists $value 2448 } 2449 return $exists 2450 } 2451 2452 method get {attr} { 2453 if {[info exists attrvals($attr)]} then { 2454 set v $attrvals($attr) 2455 } else { 2456 set v "" 2457 } 2458 return $v 2459 } 2460 2461 method set {attr val} { 2462 set attrvals($attr) $val 2463 } 2464} 2465 2466# 2467# Classe "base d'authentification" 2468# 2469# Représente une base d'authentification et donne les moyens 2470# de récupérer les attributs d'un utilisateur 2471# 2472# Options : 2473# method : "ldap" ou "postgresql" 2474# db : paramètres d'accès à la base d'authentification (cf. ci-dessous) 2475# attrmap : traduction d'attribut 2476# 2477# Méthodes 2478# getuser : recherche l'utilisateur par son login et récupère ses attributs 2479# 2480# Historique 2481# 2007/10/05 : pda/jean : intégration et documentation 2482# 2483 2484snit::type ::webapp::authbase { 2485 2486 # Option method: ldap, postgresql, opened-postgresql 2487 option -method -default "none" 2488 2489 # Option db : 2490 # pour ldap: 2491 # url ... 2492 # [ binddn ... ] 2493 # [ bindpw ... ] 2494 # base ... 2495 # searchuid ... (filtre avec un %s pour le login) 2496 # pour postgresql: 2497 # host=... 2498 # dbname=... 2499 # user=... 2500 # password=... 2501 # pour opened-postgresql: 2502 # handle 2503 option -db -default {} 2504 2505 # Option attrmap : 2506 # liste de couples 2507 # <nom dans ce module> <nom dans la base> 2508 option -attrmap -default { 2509 login login 2510 password password 2511 lastname lastname 2512 firstname firstname 2513 mail mail 2514 phone phone 2515 mobile mobile 2516 fax fax 2517 addr addr 2518 } 2519 2520 variable connected "no" 2521 variable handle 2522 2523 destructor { 2524 if {$connected} then { 2525 Disconnect $selfns 2526 } 2527 } 2528 2529 method getuser {login u} { 2530 if {! $connected} then { 2531 Connect $selfns 2532 } 2533 2534 $u exists 0 2535 set n 0 2536 2537 switch $options(-method) { 2538 opened-postgresql - 2539 postgresql { 2540 set qlogin [::pgsql::quote $login] 2541 set sql "SELECT * FROM pgauth.user WHERE login = '$qlogin'" 2542 set av {} 2543 pg_select $handle $sql tab { 2544 set av [array get tab] 2545 incr n 2546 } 2547 } 2548 ldap { 2549 array set dbopt $options(-db) 2550 set base $dbopt(base) 2551 set search $dbopt(searchuid) 2552 2553 # XXXXXXXXX Il faut quoter le login 2554 set filter [format $search $login] 2555 2556 set e [::ldapx::entry create %AUTO%] 2557 set n [$handle read $base $filter $e] 2558 2559 set av {} 2560 if {$n == 1} then { 2561 # 2562 # On ne garde que la première valeur des champs multivalués 2563 # 2564 2565 array set x [$e getall] 2566 foreach i [array names x] { 2567 set x($i) [lindex $x($i) 0] 2568 } 2569 set av [array get x] 2570 } 2571 2572 $e destroy 2573 } 2574 default { 2575 error "Auth method '$options(-method)' not supported" 2576 } 2577 } 2578 2579 if {$av ne ""} then { 2580 $u exists 1 2581 array set t $av 2582 foreach {cmod cbase} [string tolower $options(-attrmap)] { 2583 set v {} 2584 foreach c $cbase { 2585 if {[info exists t($c)]} then { 2586 lappend v $t($c) 2587 } 2588 $u set $cmod [join $v ", "] 2589 } 2590 } 2591 } 2592 2593 return $n 2594 } 2595 2596 proc Connect {selfns} { 2597 set db $options(-db) 2598 switch $options(-method) { 2599 opened-postgresql { 2600 set handle $db 2601 } 2602 postgresql { 2603 if {[catch {set handle [pg_connect -conninfo $db]} msg]} then { 2604 error $msg 2605 } 2606 } 2607 ldap { 2608 array set dbopt $db 2609 2610 if {! [info exists dbopt(url)]} then { 2611 error "url not configured for LDAP method" 2612 } else { 2613 set url $dbopt(url) 2614 } 2615 if {[info exists dbopt(binddn)] && [info exists dbopt(bindpw)]} then { 2616 set binddn $dbopt(binddn) 2617 set bindpw $dbopt(bindpw) 2618 } else { 2619 set binddn "" 2620 set bindpw "" 2621 } 2622 2623 set handle [::ldapx::ldap create %AUTO%] 2624 if {! [$handle connect $url $binddn $bindpw]} then { 2625 error [$handle error] 2626 } 2627 } 2628 none { 2629 error "Auth method not configured" 2630 } 2631 default { 2632 error "Auth method '$options(-method)' not supported" 2633 } 2634 } 2635 set connected 1 2636 } 2637 2638 proc Disconnect {selfns} { 2639 switch $options(-method) { 2640 opened-postgresql { 2641 # nothing 2642 } 2643 postgresql { 2644 if {[catch {pg_disconnect $handle} msg]} then { 2645 error $msg 2646 } 2647 } 2648 ldap { 2649 if {! [$handle disconnect]} then { 2650 error [$handle error] 2651 } 2652 $handle destroy 2653 } 2654 default { 2655 error "Auth method '$options(-method)' not supported" 2656 } 2657 } 2658 set connected 0 2659 } 2660} 2661 2662############################################################################## 2663# Cookie management 2664############################################################################## 2665 2666# Input: 2667# - name: cookie name (printable ascii chars, excluding [,; =]) 2668# - val: cookie value (printable ascii chars, excluding [,; ]) 2669# - expire: unix timestamp, or 0 if no expiration date 2670# - path: 2671# - domain: 2672# - secure: 2673# - httponly: 2674# Output: none 2675# 2676# History: 2677# 2014/03/28 : pda/jean : design 2678 2679proc ::webapp::set-cookie {name val expire path domain secure httponly} { 2680 global wcooktab 2681 2682 set l {} 2683 2684 lappend l "$name=$val" 2685 if {$expire > 0} then { 2686 # Wdy, DD Mon YYYY HH:MM:SS GMT 2687 set max [clock format $expire -gmt yes -format "%a, %d %b %Y %T GMT"] 2688 lappend "Expires=$max" 2689 } 2690 if {$path ne ""} then { 2691 lappend "Path=$path" 2692 } 2693 if {$domain ne ""} then { 2694 lappend "Domain=$domain" 2695 } 2696 if {$secure} then { 2697 lappend "Secure" 2698 } 2699 if {$httponly} then { 2700 lappend "HttpOnly" 2701 } 2702 2703 set wcooktab($name) [join $l "; "] 2704} 2705 2706# 2707# Send cookies to the browser as part of HTTP protocol 2708# 2709# Input: 2710# - global parameter wcooktab(): all cookies to return 2711# Output: none 2712# 2713# History: 2714# 2014/03/28 : pda/jean : design 2715# 2716 2717proc ::webapp::http-send-cookies {} { 2718 global wcooktab 2719 2720 foreach name [array names wcooktab] { 2721 puts stdout "Set-Cookie: $wcooktab($name)" 2722 } 2723} 2724 2725# 2726# Get a cookie (as returned by the browser) by its name 2727# 2728# Input: 2729# - name: name of the cookie to get 2730# Output: 2731# - return value: value of cookie or "" 2732# 2733# History: 2734# 2014/04/11 : pda/jean : design 2735# 2736 2737set ::webapp::gotcookies 0 2738 2739proc ::webapp::get-cookie {name} { 2740 global ::webapp::gotcookies 2741 global rcooktab 2742 global env 2743 2744 if {! $::webapp::gotcookies} then { 2745 if {[info exists env(HTTP_COOKIE)]} then { 2746 foreach nv [split $env(HTTP_COOKIE) ";"] { 2747 if {[regexp {^\s*([^=]+)=(.*)} $nv bidon n v]} then { 2748 set rcooktab($n) $v 2749 } 2750 } 2751 } 2752 set ::webapp::gotcookies 1 2753 } 2754 2755 if {[info exists rcooktab($name)]} then { 2756 set v $rcooktab($name) 2757 } else { 2758 set v "" 2759 } 2760 2761 return $v 2762} 2763 2764 2765############################################################################## 2766# Log management 2767############################################################################## 2768 2769# 2770# Classe "systeme de log" 2771# 2772# Représente l'acces a un support de journaux 2773# 2774# Options : 2775# method : "postgresql", "file", "syslog" 2776# medium : paramètres 2777# subsys : nom générique de l'application 2778# 2779# Méthodes 2780# log : écrit un événement dans le journal 2781# 2782# Historique 2783# 2007/10/23 : pda/jean : intégration et documentation 2784# 2785 2786snit::type ::webapp::log { 2787 2788 # method: postgresql, file, syslog 2789 option -method -default "none" 2790 2791 # medium for postgresql : 2792 # host ... 2793 # dbname ... 2794 # table ... 2795 # user ... 2796 # password ... 2797 # (table must contain the columns : date, subsys, event, login, ip, msg) 2798 # medium for opened-postgresql 2799 # dbfd ... 2800 # table ... 2801 # medium for file : 2802 # file ... 2803 # medium for syslog : 2804 # host ... 2805 # facility ... 2806 # priority ... 2807 option -medium -default {} 2808 2809 # subsystem 2810 option -subsys -default "none" 2811 2812 variable handle "" 2813 variable table "log" 2814 2815 constructor {args} { 2816 $self configurelist $args 2817 2818 switch $options(-method) { 2819 none { 2820 error "Wrong # args: should be -method ... -medium ..." 2821 } 2822 postgresql { 2823 array set x $options(-medium) 2824 set db {} 2825 foreach c {host dbname user password} { 2826 if {[info exists x($c)]} then { 2827 lappend db "$c=$x($c)" 2828 } 2829 } 2830 set db [join $db " "] 2831 if {[catch {set handle [pg_connect -conninfo $db]} msg]} then { 2832 error "Cannot connect: $msg" 2833 } 2834 if {[info exists x(table)]} then { 2835 set table $x(table) 2836 } 2837 } 2838 opened-postgresql { 2839 array set x $options(-medium) 2840 if {! [info exists x(db)]} then { 2841 error "db is a mandatory parameter" 2842 } 2843 set handle $x(db) 2844 if {[info exists x(table)]} then { 2845 set table $x(table) 2846 } 2847 } 2848 file { 2849 # XXX 2850 } 2851 syslog { 2852 # XXX 2853 } 2854 default { 2855 error "Unknown method '$options(-method)'" 2856 } 2857 } 2858 } 2859 2860 destructor { 2861 switch $options(-method) { 2862 opened-postgresql - 2863 postgresql { 2864 pg_disconnect $handle 2865 } 2866 file { 2867 } 2868 syslog { 2869 } 2870 default { 2871 error "Unknown method '$options(-method)'" 2872 } 2873 } 2874 } 2875 2876 method log {date event login ip msg} { 2877 2878 switch $options(-method) { 2879 opened-postgresql - 2880 postgresql { 2881 foreach c {event login ip msg} { 2882 if {[string equal [set $c] ""]} then { 2883 set t($c) NULL 2884 } else { 2885 set t($c) "'[::pgsql::quote [set $c]]'" 2886 } 2887 } 2888 if {[string equal $date ""]} then { 2889 set datecol "" 2890 set dateval "" 2891 } else { 2892 set datecol "date," 2893 if {[regexp {^\d+$} $date]} then { 2894 set dateval "to_timestamp($date)," 2895 } else { 2896 set dateval "'[::pgsql::quote $date]'," 2897 } 2898 } 2899 set t(subsys) "'[::pgsql::quote $options(-subsys)]'" 2900 set sql "INSERT INTO $table 2901 ($datecol subsys, event, login, ip, msg) 2902 VALUES ( 2903 $dateval $t(subsys), $t(event), $t(login), 2904 $t(ip), $t(msg))" 2905 if {! [::pgsql::execsql $handle $sql m]} then { 2906 error "Cannot write log ($m)" 2907 } 2908 } 2909 file { 2910 } 2911 syslog { 2912 } 2913 default { 2914 error "Unknown method '$options(-method)'" 2915 } 2916 } 2917 } 2918} 2919