2015-12-24 15:31:55 +04:00
#!/usr/bin/env python
# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
u """ Ce script est prévu pour être utilisé dans un script CGI.
2016-02-07 23:03:38 +04:00
Il permet de lister les paramètres du formulaire et d ' y accéder depuis un script
bash . Le tableau QVARS est initialisé avec la liste des variables correspondant
aux paramètres pour lesquels une valeur est définie .
Les arguments de ce script doivent être de la forme NAME [ = DEFAULT ] . Si le
paramètres était fourni dans la requête , il est affiché , sous forme de scalaire
ou de tableau . S ' il n ' était pas fourni , la valeur par défaut est affichée .
2015-12-24 15:31:55 +04:00
"""
2016-04-24 18:06:26 +04:00
import sys , os , re , cgi , urllib , types , tempfile , csv
from os import path
2015-12-24 15:31:55 +04:00
2016-04-24 18:06:26 +04:00
cgitb = None # marqueur pour savoir si le module cgitb a été importé
#import cgitb; cgitb.enable()
2015-12-24 15:31:55 +04:00
def quote ( value , q = False ) :
2016-04-24 18:06:26 +04:00
value = str ( value )
2015-12-24 15:31:55 +04:00
if value or q :
value = " ' %s ' " % value . replace ( " ' " , " ' \\ ' ' " )
2016-04-24 18:06:26 +04:00
if value != " ' ' " : value = re . sub ( r " (?<! \\ ) ' ' " , " " , value )
return value
2015-12-24 15:31:55 +04:00
def print_scalar ( name , value ) :
print " %s = %s " % ( name , quote ( value ) )
def print_array ( name , values ) :
2016-04-24 18:06:26 +04:00
parts = [ " %s =( " % name ]
first = True
2015-12-24 15:31:55 +04:00
for value in values :
2016-04-24 18:06:26 +04:00
if first : first = False
else : parts . append ( " " )
parts . append ( quote ( value , True ) )
parts . append ( " ) " )
print " " . join ( parts )
class File ( object ) :
# classe wrapper permettant de savoir si nous avons déjà traité un paramètre
# de type file
_f = None
def __init__ ( self , f ) : self . __dict__ [ ' _f ' ] = f
def __getattr__ ( self , name ) : return getattr ( self . _f , name )
def __setattr__ ( self , name , value ) : setattr ( self . _f , name , value )
class FileInfo ( object ) :
FIELDS = ( ' name ' , ' value ' , ' file ' , ' type ' , ' invalid ' )
_param = None
_items = None
def __init__ ( self , param , * items ) :
nitems = len ( items )
nfields = len ( self . FIELDS )
if nitems < nfields : items = items + ( ' ' , ) * ( nfields - nitems )
items = items [ : nfields ]
self . _param = param
self . _items = list ( items )
def __len__ ( self ) : return len ( self . FIELDS )
def __getitem__ ( self , key ) : return self . _items [ key ]
param = property ( lambda self : self . _param )
name = property ( lambda self : self . _items [ 0 ] )
value = property ( lambda self : self . _items [ 1 ] )
file = property ( lambda self : self . _items [ 2 ] )
type = property ( lambda self : self . _items [ 3 ] )
def __set_invalid ( self , value ) : self . _items [ 4 ] = value
invalid = property ( lambda self : self . _items [ 4 ] , __set_invalid )
class FieldStorage ( cgi . FieldStorage ) :
fm = None
def make_file ( self , binary = None ) :
outf = self . fm . _new_file ( self )
if outf is not None : return outf
return cgi . FieldStorage . make_file ( self , binary )
class FileManager ( object ) :
fimap = None
filist = None
tries = None
count = None
destdir = None
spec = None
type = None
exitcode = None
form = None
def __init__ ( self , destdir = None , spec = None , type = None , parse_env = True ) :
FieldStorage . fm = self
if destdir is None : destdir = ' /tmp '
self . fimap = { }
self . filist = [ ]
self . tries = 0
self . count = 0
self . destdir = destdir
if spec is not None : spec = re . compile ( spec )
self . spec = spec
self . type = type
if parse_env : self . parse_env ( )
def _new_file ( self , param ) :
fname = param . name
if self . spec is not None and self . spec . match ( param . filename ) is None :
invalid = ' name '
elif self . type is not None and self . type != param . type :
invalid = ' type '
else :
invalid = ' '
self . count + = 1
self . tries + = 1
fp = path . join ( self . destdir , ' upload %i ' % self . tries )
outf = File ( open ( fp , ' w+b ' ) )
fileinfo = FileInfo ( param , fname , param . filename , fp , param . type , invalid )
if not self . fimap . has_key ( fname ) :
self . fimap [ fname ] = [ ]
self . fimap [ fname ] . append ( fileinfo )
self . filist . append ( fileinfo )
return outf
def parse_env ( self ) :
self . form = FieldStorage ( keep_blank_values = True )
for param in self . form . list :
if param . filename and not isinstance ( param . file , File ) :
outf = self . _new_file ( param )
outf . write ( param . value )
outf . close ( )
for fi in self . filist :
param = fi . param
if param is None : continue
if param . done == - 1 :
fi . invalid = ' incomplete '
def get_params ( self , fname ) :
return [ param for param in self . form . list if param . name == fname ]
def print_param ( self , fname , vname = None , defvalue = None , skip_invalids = None ) :
if vname is None : vname = fname . replace ( ' - ' , ' _ ' )
if self . fimap . has_key ( fname ) :
filenames = [ ' %s _filename ' % vname ]
files = [ ' %s _file ' % vname ]
types = [ ' %s _type ' % vname ]
invalids = [ ' %s _invalid ' % vname ]
count = 0
for fi in self . fimap [ fname ] :
if fi . invalid and skip_invalids : continue
filenames . append ( fi . value )
files . append ( fi . file )
types . append ( fi . type )
invalids . append ( fi . invalid )
count + = 1
if count == 0 : return False
print_scalar ( vname , " FILE-UPLOAD " )
print_scalar ( ' %s _count ' % vname , count )
for values in filenames , files , types , invalids :
if len ( values ) == 2 : print_scalar ( values [ 0 ] , values [ 1 ] )
else : print_array ( values [ 0 ] , values [ 1 : ] )
return True
values = self . form . getlist ( fname )
if defvalue is not None :
if len ( values ) == 0 : print_scalar ( vname , defvalue )
elif len ( values ) == 1 : print_scalar ( vname , values [ 0 ] )
else : print_array ( vname , values )
else :
if len ( values ) == 1 : print_scalar ( vname , values [ 0 ] )
else : print_array ( vname , values )
return True
def write_csv ( self , outf , nvs = None , skip_invalids = None ) :
close = False
if type ( outf ) is types . StringType :
outf = open ( outf , ' w+b ' )
close = True
w = csv . writer ( outf )
try :
w . writerow ( FileInfo . FIELDS )
if nvs is None :
nvs = self . form . keys ( )
# s'assurer que les noms sont dans l'ordre de filist
for fi in self . filist :
fname = fi . name
if fname in nvs :
nvs . remove ( fname )
nvs . append ( fname )
for nv in nvs :
mo = RE_NAME_VALUE . match ( nv )
if mo is None : continue
fname , defvalue = mo . group ( 1 ) , mo . group ( 2 )
if self . fimap . has_key ( fname ) :
for fi in self . fimap [ fname ] :
if not fi . invalid or not skip_invalids :
w . writerow ( fi )
else :
if defvalue is None : defvalue = ' '
params = self . get_params ( fname )
if params :
param = params [ 0 ]
values = [ param . value for param in params ]
else :
param = None
values = [ defvalue ]
w . writerow ( FileInfo ( param , fname , " ; " . join ( values ) ) )
finally :
if close : outf . close ( )
def exitcode ( self ) :
if self . count == 0 : return 2
elif self . count != self . tries : return 1
else : return 0
2015-12-24 15:31:55 +04:00
2016-02-07 23:03:38 +04:00
RE_COMMA = re . compile ( ' , ' )
def lfix ( values ) :
if values is None : return None
fvalues = [ ]
for parts in values :
parts = RE_COMMA . split ( parts )
fvalues . extend ( parts )
return fvalues
def build_query_string ( form , includes = None , excludes = None , prefix = None ) :
includes = lfix ( includes )
if includes is None : names = form . keys ( )
else : names = includes [ : ]
excludes = lfix ( excludes )
if excludes is not None :
for name in excludes :
if name in names : names . remove ( name )
params = [ ]
for name in names :
values = form . getlist ( name )
for value in values :
params . append ( ( name , value ) )
query_string = urllib . urlencode ( params )
if prefix :
if query_string != " " : query_string = " %s & %s " % ( prefix , query_string )
elif prefix != " " : query_string = prefix
if query_string :
query_string = " ? %s " % query_string
return query_string
2016-04-24 18:06:26 +04:00
RE_NAME_VALUE = re . compile ( r ' ([a-zA-Z0-9_-]+)(?:=(.*))?$ ' )
SHELL_ACTION = ' shell '
PRINTCSV_ACTION = ' printcsv '
CMD_ACTION = ' cmd '
INFOS_ARGTYPE = ' infos '
FILES_ARGTYPE = ' files '
CSV_ARGTYPE = ' csv '
2015-12-24 15:31:55 +04:00
def run_cgiparams ( ) :
2016-04-24 18:06:26 +04:00
from optparse import OptionParser , OptionGroup
2015-12-24 15:31:55 +04:00
OP = OptionParser ( usage = u " \n \t % prog [options] params... " , description = __doc__ )
2016-04-24 18:06:26 +04:00
OP . set_defaults ( action = SHELL_ACTION )
OP . set_defaults ( argtype = INFOS_ARGTYPE )
OP . set_defaults ( clear = True )
OP . add_option ( ' --devel ' , action = ' store_true ' , dest = ' devel ' ,
help = u " Activer le mode développement: le module cgitb est chargé et activé. " )
OG = OptionGroup ( OP , " ACTIONS " , u " Ces options permettent de choisir le type d ' action effectuée " )
OG . add_option ( ' -a ' , ' --shell ' , action = ' store_const ' , const = SHELL_ACTION , dest = ' action ' ,
help = u " Afficher les valeurs pour traitement par le shell. C ' est la valeur par défaut. " )
OG . add_option ( ' -b ' , ' --printcsv ' , action = ' store_const ' , const = PRINTCSV_ACTION , dest = ' action ' ,
help = u " Afficher la liste des paramètres et des fichiers reçus au format CSV. Chaque ligne est de la forme ' name,value,file,type,invalid ' . S ' il s ' agit d ' un paramètre normal, name est le nom du paramètre, value la liste des valeurs du paramètres séparées par ' ; ' , file, type et invalid sont vides. S ' il s ' agit d ' un fichier reçu, name est le nom du paramètre, value le nom du fichier tel qu ' il a été fourni par le client, file le chemin complet vers le fichier reçu, type le type mime du contenu, et invalid a une valeur non vide si le fichier ne correspond pas à certains critères. En ce qui concernent le paramètre invalid, il vaut ' name ' si le fichier reçu ne correspond pas au paramètre --filespec, ' type ' si le fichier ne correspond pas au paramètre --type, ou ' incomplete ' si le fichier n ' a pas été transféré complètement. " )
OG . add_option ( ' -c ' , ' --cmd ' , action = ' store_const ' , const = CMD_ACTION , dest = ' action ' ,
help = u " Lancer une commande en lui fournissant les informations nécessaires. La commande ne reçoit QUE les informations concernant les fichiers reçus. " )
OP . add_option_group ( OG )
OG = OptionGroup ( OP , " OPTIONS COMMUNES " , u " Les options suivantes sont communes à toutes les actions " )
OG . add_option ( ' -f ' , ' --filespec ' , dest = ' filespec ' ,
help = u " N ' accepter que des fichiers dont le nom correspond à l ' expression régulière spécifiée. Les autres fichiers sont ignorés, comme s ' ils n ' avaient même pas été spécifiés. " )
OG . add_option ( ' -t ' , ' --type ' , dest = ' filetype ' ,
help = u " N ' accepter que des fichiers dont le type mime correspond à la valeur spécifiée. Les autres fichiers sont ignorés, comme s ' ils n ' avaient même pas été spécifiés. " )
OG . add_option ( ' -d ' , ' --destdir ' , dest = ' destdir ' ,
help = u " Spécifier le répertoire dans lequel enregistrer les fichiers reçus. Ce répertoire est créé si nécessaire. " )
OG . add_option ( ' -R ' , ' --remove-files ' , action = ' store_true ' , dest = ' clear ' ,
help = u " Supprimer les fichiers reçus après avoir lancé la commande. C ' est l ' option par défaut. Pour information, même si ça n ' a à priori aucun intérêt, cette option est honorée avec les actions --shell et --printcsv " )
OG . add_option ( ' -K ' , ' --keep-files ' , action = ' store_false ' , dest = ' clear ' ,
help = u " Ne pas supprimer les fichiers reçus après avoir lancé la commande. Avec les actions --shell et --printcsv, si on prévoit de recevoir des fichiers, cette option devrait être spécifiée. " )
OG . add_option ( ' -V ' , ' --skip-invalids ' , action = ' store_true ' , dest = ' skip_invalids ' ,
help = u " Ne pas mentionner les fichiers reçus invalides " )
OP . add_option_group ( OG )
OG = OptionGroup ( OP , " PARAMETRES " , u " Les options suivantes concernent le traitement et l ' affichage des paramètres normaux (action --shell) " )
OG . add_option ( ' --qvars ' , dest = " qvars " , default = " QVARS " ,
help = u " Spécifier le nom du tableau qui contiendra la liste des paramètres pour lesquels une valeur est définie. Par défaut, utiliser QVARS " )
OG . add_option ( ' -q ' , ' --query-string ' , action = ' store_true ' , dest = ' print_qs ' ,
2016-02-07 23:03:38 +04:00
help = u " Reconstruire et afficher la valeur de QUERY_STRING en fonction des paramètres fournis dans la requête. Les options -i et -x permettent de sélectionner les paramètres qui y sont inclus. " )
2016-04-24 18:06:26 +04:00
OG . add_option ( ' -p ' , ' --prefix ' , dest = " prefix " ,
help = u " Avec l ' option --query-string, ajouter les paramètres supplémentaires spécifiés à QUERY_STRING. " )
OG . add_option ( ' -i ' , ' --include ' , dest = ' includes ' , action = ' append ' ,
help = u " Spécifier les paramètres utilisés pour construire QUERY_STRING. Spécifier plusieurs paramètres en les séparant par des virgules. " )
OG . add_option ( ' -x ' , ' --exclude ' , dest = ' excludes ' , action = ' append ' ,
2016-02-07 23:03:38 +04:00
help = u " Spécifier un paramètre à exclure pour construire QUERY_STRING. Il est possible de spécifier plusieurs paramètres en les séparant par des virgules. " )
2016-04-24 18:06:26 +04:00
OP . add_option_group ( OG )
OG = OptionGroup ( OP , " FICHIERS " , u " Les options suivantes concernent l ' accès aux fichiers reçus (action --cmd) " )
OG . add_option ( ' -G ' , ' --infos ' , action = ' store_const ' , const = INFOS_ARGTYPE , dest = ' argtype ' ,
help = u " La commande est lancée avec N*4 arguments, N étant le nombre de fichiers reçus. Pour chaque fichier, les arguments sont fournis dans l ' ordre donné par l ' option --printcsv. C ' est l ' option par défaut. " )
OG . add_option ( ' -F ' , ' --files ' , action = ' store_const ' , const = FILES_ARGTYPE , dest = ' argtype ' ,
help = u " La commande est lancée avec N arguments, N étant le nombre de fichiers reçus. Chaque argument est le chemin vers le fichier reçu, correspondant à la colonne file de l ' option --printcsv " )
OG . add_option ( ' -C ' , ' --csv ' , action = ' store_const ' , const = CSV_ARGTYPE , dest = ' argtype ' ,
help = u " La commande est lancée avec en argument un fichier temporaire au format CSV qui liste les paramètres ET les fichiers reçus, contrairement à -G et -F. Cf l ' option --printcsv pour le format de ce fichier. " )
OP . add_option_group ( OG )
2015-12-24 15:31:55 +04:00
o , args = OP . parse_args ( )
2016-04-24 18:06:26 +04:00
if o . devel and cgitb is None :
import cgitb ; cgitb . enable ( )
fm = FileManager ( o . destdir , o . filespec , o . filetype )
if o . action == SHELL_ACTION :
qvars = [ ]
if args :
for nv in args :
mo = RE_NAME_VALUE . match ( nv )
if mo is None : continue
fname , defvalue = mo . group ( 1 ) , mo . group ( 2 )
if defvalue is None : defvalue = ' '
vname = fname . replace ( ' - ' , ' _ ' )
if fm . print_param ( fname , vname , defvalue , skip_invalids = o . skip_invalids ) :
if vname not in qvars : qvars . append ( vname )
else :
for fname in fm . form . keys ( ) :
vname = fname . replace ( ' - ' , ' _ ' )
if fm . print_param ( fname , vname , skip_invalids = o . skip_invalids ) :
qvars . append ( vname )
print_array ( o . qvars , qvars )
if o . print_qs :
qs = build_query_string ( fm . form , o . includes , o . excludes , o . prefix )
print_scalar ( ' QUERY_STRING ' , qs )
elif o . action == PRINTCSV_ACTION :
fm . write_csv ( sys . stdout , args or None , skip_invalids = o . skip_invalids )
elif o . action == CMD_ACTION :
tmpfile = None
if not args : args = [ ' echo ' ]
if o . argtype == INFOS_ARGTYPE :
for fi in fm . filist :
if fi . invalid and o . skip_invalids : continue
args . extend ( fi )
elif o . argtype == FILES_ARGTYPE :
for fi in fm . filist :
if fi . invalid and o . skip_invalids : continue
args . extend ( fi . file )
elif o . argtype == CSV_ARGTYPE :
fd , tmpfile = tempfile . mkstemp ( )
os . close ( fd )
fm . write_csv ( tmpfile , skip_invalids = o . skip_invalids )
args . append ( tmpfile )
else :
raise AssertionError ( " Unknown argtype " )
os . spawnvp ( os . P_WAIT , args [ 0 ] , args )
if tmpfile is not None :
try : os . remove ( tmpfile )
except : pass
if o . clear :
for fi in fm . filist :
try : os . remove ( fi . file )
except : pass
2015-12-24 15:31:55 +04:00
2016-04-24 18:09:59 +04:00
return fm . exitcode ( )
2015-12-24 15:31:55 +04:00
if __name__ == ' __main__ ' :
2016-04-24 18:09:59 +04:00
ec = run_cgiparams ( )
sys . exit ( ec )