615 lines
31 KiB
Python
Executable File
615 lines
31 KiB
Python
Executable File
#!/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.
|
|
|
|
Il permet de lister le contenu d'un répertoire au format XML, et de télécharger les fichiers trouvés.
|
|
"""
|
|
|
|
import os, sys, re, shutil, mimetypes, urlparse, urllib, fnmatch, glob
|
|
from os import path
|
|
from time import time, localtime
|
|
from types import UnicodeType, StringTypes
|
|
import xml.etree.ElementTree as ET
|
|
|
|
# _u() et datef() sont des fonctions de pyulib, recopiées ici pour ne pas devoir
|
|
# dépendre d'une librairie extérieure
|
|
def _u(u):
|
|
if u is None: return None
|
|
elif type(u) not in StringTypes: u = unicode(str(u), 'utf-8', 'strict')
|
|
elif type(u) is not UnicodeType: u = unicode(u, 'utf-8', 'strict')
|
|
return u
|
|
DATEF_MAP = {'%Y': '%(y)04i', '%m': '%(m)02i', '%d': '%(d)02i', '%H': '%(H)02i', '%M': '%(M)02i', '%S': '%(S)02i'}
|
|
DATE_DATEF = '%d/%m/%Y'
|
|
TIME_DATEF = '%H:%M:%S'
|
|
def datef(format=None, t=None):
|
|
if format is None: format = DATE_DATEF
|
|
if t is None: t = time()
|
|
y, m, d, H, M, S, W, J, dst = localtime(t)
|
|
for fr, to in DATEF_MAP.items(): format = format.replace(fr, to)
|
|
return format % locals()
|
|
|
|
RE_COMMA = re.compile(r'\s*,\s*')
|
|
RE_VAR_NAME_EXPR = re.compile(r'([_a-zA-Z][-_a-zA-Z0-9]*)=(.*)')
|
|
RE_SORT_FIELD_TYPE = re.compile(r'([_a-zA-Z][-_a-zA-Z0-9]*)(?::([aAdDcCnN]+))?')
|
|
|
|
class Filter:
|
|
re_spec = None
|
|
glob = None
|
|
caret_anchor = None
|
|
deep_scan = None
|
|
re_allows = None
|
|
ex_vars = None
|
|
ex_group = None
|
|
ex_break_on = None
|
|
force_disposition = None
|
|
force_type = None
|
|
force_charset = None
|
|
|
|
def __init__(self, spec=None, glob=None):
|
|
if spec is not None:
|
|
self.re_spec = re.compile(spec)
|
|
self.caret_anchor = spec.startswith('^')
|
|
self.deep_scan = '/' in spec
|
|
elif glob is not None:
|
|
self.glob = glob
|
|
self.deep_scan = '/' in glob
|
|
else: raise ValueError("either spec or glob must be specified")
|
|
self.re_allows = []
|
|
self.ex_vars = []
|
|
self.ex_group = None
|
|
self.ex_break_on = None
|
|
|
|
def allow_spec(self, re_allow):
|
|
self.re_allows.append(re.compile(re_allow))
|
|
|
|
def add_var(self, ex_var):
|
|
mo = RE_VAR_NAME_EXPR.match(ex_var)
|
|
if mo is None: raise ValueError('Invalid VAR_EXPR: %s' % ex_var)
|
|
name, expr = mo.groups()
|
|
self.ex_vars.append((name, expr))
|
|
|
|
def set_group(self, ex_group):
|
|
self.ex_group = ex_group
|
|
|
|
def set_break_on(self, ex_break_on):
|
|
self.ex_break_on = ex_break_on
|
|
|
|
def __repr__(self):
|
|
return 'Filter<%s||%s,%s,%s,%s,%s>' % \
|
|
(self.re_spec, self.glob, repr(self.re_allows),
|
|
repr(self.ex_vars),
|
|
self.ex_group, self.ex_break_on,
|
|
)
|
|
|
|
def match_fill(self, file, basedir=None):
|
|
if self.glob:
|
|
if self.deep_scan:
|
|
# la correspondance a déjà été faite avant avec glob.glob(). on
|
|
# se contente d'initialiser l'objet
|
|
pass
|
|
elif not fnmatch.fnmatch(file.name, self.glob):
|
|
return None
|
|
file.group = None
|
|
file.break_on = None
|
|
if self.force_disposition: file.content_disposition = self.force_disposition
|
|
if self.force_type: file.content_type = self.force_type
|
|
if self.force_charset: file.content_charset = self.force_charset
|
|
return True
|
|
|
|
if self.deep_scan:
|
|
pf = file.pf
|
|
if not self.caret_anchor:
|
|
blen = len(basedir)
|
|
if pf[:blen] != basedir: return None
|
|
pf = pf[blen:]
|
|
if not pf.startswith('/'): return None
|
|
pf = pf[1:]
|
|
mo = self.re_spec.match(pf)
|
|
else: mo = self.re_spec.match(file.name)
|
|
if mo is None: return None
|
|
for name, expr in self.ex_vars:
|
|
value = mo.expand(expr)
|
|
if '%' in value: value = value % file.vars
|
|
file[name] = value
|
|
if self.ex_group is not None:
|
|
group = mo.expand(self.ex_group)
|
|
if '%' in group: group = group % file.vars
|
|
else:
|
|
group = None
|
|
file.group = group
|
|
if self.ex_break_on is not None:
|
|
break_on = mo.expand(self.ex_break_on)
|
|
if '%' in break_on: break_on = break_on % file.vars
|
|
else:
|
|
break_on = None
|
|
file.break_on = break_on
|
|
if self.force_disposition: file.content_disposition = self.force_disposition
|
|
if self.force_type: file.content_type = self.force_type
|
|
if self.force_charset: file.content_charset = self.force_charset
|
|
return True
|
|
|
|
def match_allow(self, file):
|
|
for re_allow in self.re_allows:
|
|
if re_allow.match(file.name) is not None:
|
|
return True
|
|
return False
|
|
|
|
class File:
|
|
pf = None
|
|
group = None
|
|
vars = None
|
|
dontlist = None
|
|
|
|
def __init__(self, dir=None, name=None, pf=None, dontlist=False):
|
|
if pf is None:
|
|
if dir is None or name is None: raise ValueError("either dir/name or pf must be specified")
|
|
pf = path.join(dir, name)
|
|
dir, name = path.split(pf) # il est possible que name soit de la forme subdir/name
|
|
dir = path.abspath(dir)
|
|
pf = path.join(dir, name)
|
|
self.__dict__['pf'] = pf
|
|
self.__dict__['group'] = None
|
|
self.__dict__['vars'] = {}
|
|
self.__dict__['dontlist'] = dontlist
|
|
|
|
stat = os.stat(pf)
|
|
mtime = int(stat.st_mtime)
|
|
|
|
self.dir = dir
|
|
self.name = name
|
|
self.dlname = name
|
|
self.size = stat.st_size
|
|
self.mtime = mtime
|
|
self.date = datef(DATE_DATEF, mtime)
|
|
self.time = datef(TIME_DATEF, mtime)
|
|
self.Y, self.m, self.d, self.H, self.M, self.S = datef('%Y %m %d %H %M %S', mtime).split(' ')
|
|
|
|
def isfile(self):
|
|
return path.isfile(self.pf)
|
|
|
|
def __getitem__(self, name): return self.vars[name]
|
|
def __setitem__(self, name, value): self.vars[name] = value
|
|
def has_key(self, name): return self.vars.has_key(name)
|
|
def __getattr__(self, name):
|
|
try: return self.vars[name]
|
|
except KeyError: raise AttributeError(name)
|
|
def __setattr__(self, name, value):
|
|
if self.__dict__.has_key(name): self.__dict__[name] = value
|
|
else: self.vars[name] = value
|
|
def __repr__(self): return 'File<%s>' % (self.pf)
|
|
|
|
def find_files(basedir, filters):
|
|
basedir = path.abspath(basedir)
|
|
files = []
|
|
deep_filters = [filter for filter in filters if filter.re_spec is not None and filter.deep_scan]
|
|
if deep_filters:
|
|
for dirpath, dirnames, filenames in os.walk(basedir):
|
|
for filename in filenames:
|
|
file = File(dirpath, filename)
|
|
matched = False
|
|
allowed = False
|
|
for filter in deep_filters:
|
|
matched = filter.match_fill(file, basedir) or matched
|
|
allowed = filter.match_allow(file) or allowed
|
|
if matched: pass
|
|
elif allowed: file.dontlist = True
|
|
else: continue
|
|
files.append(file)
|
|
simple_filters = [filter for filter in filters if filter.re_spec is not None and not filter.deep_scan]
|
|
if simple_filters:
|
|
for name in os.listdir(basedir):
|
|
file = File(basedir, name)
|
|
if not file.isfile(): continue
|
|
matched = False
|
|
allowed = False
|
|
for filter in simple_filters:
|
|
matched = filter.match_fill(file) or matched
|
|
allowed = filter.match_allow(file) or allowed
|
|
if matched: pass
|
|
elif allowed: file.dontlist = True
|
|
else: continue
|
|
files.append(file)
|
|
glob_filters = [filter for filter in filters if filter.glob is not None]
|
|
if glob_filters:
|
|
for filter in glob_filters:
|
|
if filter.deep_scan:
|
|
candidates = [File(pf=pf) for pf in glob.glob(path.join(basedir, filter.glob))]
|
|
else:
|
|
candidates = [File(basedir, name) for name in os.listdir(basedir)]
|
|
for file in candidates:
|
|
if not file.isfile(): continue
|
|
elif filter.match_fill(file): pass
|
|
elif filter.match_allow(file): file.dontlist = True
|
|
else: continue
|
|
files.append(file)
|
|
return files
|
|
|
|
def build_sortfunc(sortby):
|
|
SORTS = []
|
|
for term in RE_COMMA.split(sortby.strip()):
|
|
mo = RE_SORT_FIELD_TYPE.match(term)
|
|
if mo is None: raise ValueError('Invalid SORT_EXPR: %s' % term)
|
|
field, type = mo.groups()
|
|
if type is None: type = ''
|
|
type = type.upper()
|
|
if 'A' in type: order = 'A'
|
|
elif 'D' in type: order = 'D'
|
|
else: order = 'A'
|
|
if 'C' in type: method = 'C'
|
|
elif 'N' in type: method = 'N'
|
|
else: method = 'C'
|
|
SORTS.append((field, method, order))
|
|
def sortfunc(a, b):
|
|
for field, method, order in SORTS:
|
|
av = getattr(a, field, None)
|
|
bv = getattr(b, field, None)
|
|
if av is None:
|
|
if bv is None: outcome = 0
|
|
else: outcome = 1
|
|
elif bv is None:
|
|
outcome = -1
|
|
else:
|
|
if method == 'C':
|
|
av = str(av)
|
|
bv = str(bv)
|
|
elif method == 'N':
|
|
av = int(av)
|
|
bv = int(bv)
|
|
if av < bv: outcome = -1
|
|
elif av > bv: outcome = 1
|
|
else: outcome = 0
|
|
if order == 'A': pass
|
|
elif order == 'D': outcome = -outcome
|
|
if outcome != 0: return outcome
|
|
return 0
|
|
return sortfunc
|
|
|
|
def sort_files(files, sortfunc):
|
|
files.sort(sortfunc)
|
|
return files
|
|
|
|
def build_fgroups(files):
|
|
fgroups = set()
|
|
for file in files:
|
|
if file.group is not None:
|
|
fgroups.add(file.group)
|
|
fgroups = list(fgroups)
|
|
fgroups.sort()
|
|
return fgroups
|
|
|
|
def filter_files(files, group):
|
|
if group: func = lambda file: file.dontlist or file.group == group
|
|
else: func = lambda file: file.dontlist or not file.group
|
|
return filter(func, files)
|
|
|
|
def select_file(files, name):
|
|
matches = filter(lambda file: file.name == name, files)
|
|
if matches: return matches[0]
|
|
else: return None
|
|
|
|
def cgi_nocache():
|
|
print "Cache-Control: private, no-cache, no-store, must-revalidate, max-age=0"
|
|
print "Pragma: no-cache"
|
|
print "Expires: Thu, 01 Jan 1970 00:00:00 GMT"
|
|
|
|
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 filter_query_string(query_string, includes=None, excludes=None, prefix=None):
|
|
params = urlparse.parse_qsl(query_string, keep_blank_values=True)
|
|
includes = lfix(includes)
|
|
if includes is None: names = dict(params).keys()
|
|
else: names = includes[:]
|
|
excludes = lfix(excludes)
|
|
if excludes is not None:
|
|
for name in excludes:
|
|
if name in names: names.remove(name)
|
|
params = [(name, value) for (name, value) in params if name in names]
|
|
query_string = urllib.urlencode(params)
|
|
if prefix:
|
|
if query_string != "": query_string = "%s&%s" % (prefix, query_string)
|
|
elif prefix != "": query_string = prefix
|
|
return query_string
|
|
|
|
def print_files(files, fgroups=None, select_group=None, script_name=None, xslt=None,
|
|
query_string=None, includes=None, excludes=None, prefix=None):
|
|
xresult = ET.Element("result")
|
|
xenv = ET.SubElement(xresult, "env")
|
|
if script_name is not None:
|
|
ET.SubElement(xenv, "script_name").text = _u(script_name)
|
|
ET.SubElement(xenv, "script_base").text = _u(re.sub(r'[^/]+$', '', script_name))
|
|
|
|
orig_query_string = query_string
|
|
if orig_query_string is not None:
|
|
query_string = orig_query_string
|
|
params = []
|
|
if query_string is not None:
|
|
params = urlparse.parse_qsl(query_string, keep_blank_values=True)
|
|
query_string = _u(query_string)
|
|
fquery_string = orig_query_string
|
|
fparams = []
|
|
if fquery_string is not None:
|
|
fquery_string = filter_query_string(fquery_string, includes, excludes, prefix)
|
|
fparams = urlparse.parse_qsl(fquery_string, keep_blank_values=True)
|
|
fquery_string = _u(fquery_string)
|
|
|
|
if includes or excludes or prefix:
|
|
xorig = ET.SubElement(xenv, "orig")
|
|
if query_string:
|
|
ET.SubElement(xorig, "query_string").text = u'?%s' % query_string
|
|
ET.SubElement(xorig, "squery_string").text = u'&%s' % query_string
|
|
xvars = ET.SubElement(xorig, "query_vars")
|
|
for name, value in params:
|
|
ET.SubElement(xvars, name).text = value
|
|
query_string = fquery_string
|
|
params = fparams
|
|
|
|
if query_string:
|
|
ET.SubElement(xenv, "query_string").text = u'?%s' % query_string
|
|
ET.SubElement(xenv, "squery_string").text = u'&%s' % query_string
|
|
|
|
xvars = ET.SubElement(xenv, "query_vars")
|
|
for name, value in params:
|
|
ET.SubElement(xvars, name).text = value
|
|
|
|
xfgroups = ET.SubElement(xresult, "fgroups")
|
|
if fgroups is not None:
|
|
for fgroup in fgroups:
|
|
xfgroup = ET.SubElement(xfgroups, 'fgroup')
|
|
xfgroup.text = _u(fgroup)
|
|
if fgroup == select_group:
|
|
xfgroup.set('selected', 'selected')
|
|
|
|
xfiles = ET.SubElement(xresult, "files")
|
|
first = True
|
|
last_classifier = (None, None)
|
|
for file in files:
|
|
if file.dontlist: continue
|
|
current_classifier = (file.group, file.break_on)
|
|
if first or current_classifier != last_classifier:
|
|
file.break_here = None
|
|
last_classifier = current_classifier
|
|
first = False
|
|
xfile = ET.SubElement(xfiles, "file")
|
|
if file.group is not None: xfile.set('group', file.group)
|
|
for name, value in file.vars.items():
|
|
ET.SubElement(xfile, name).text = _u(value)
|
|
|
|
sys.stdout.write('<?xml version="1.0" encoding="utf-8"?>\n')
|
|
if xslt is not None:
|
|
sys.stdout.write('<?xml-stylesheet version="1.0" type="text/xsl" href="%s"?>\n' % xslt)
|
|
ET.ElementTree(xresult).write(sys.stdout, "utf-8")
|
|
|
|
def run_cgilsxml():
|
|
class ContentInfos:
|
|
disposition = 'attachment'
|
|
type = None
|
|
charset = 'utf-8'
|
|
default_content = ContentInfos()
|
|
default_filter = Filter(r'(.*)')
|
|
def add_spec(option, opt, value, parser, *args, **kw):
|
|
if env['filter'] is not None: env['filters'].append(env['filter'])
|
|
env['filter'] = Filter(spec=value)
|
|
def add_glob(option, opt, value, parser, *args, **kw):
|
|
if env['filter'] is not None: env['filters'].append(env['filter'])
|
|
env['filter'] = Filter(glob=value)
|
|
def allow_spec(option, opt, value, parser, *args, **kw):
|
|
if env['filter'] is None: env['filter'] = default_filter
|
|
env['filter'].allow_spec(value)
|
|
def add_var(option, opt_str, value, parser, *args, **kw):
|
|
if env['filter'] is None: env['filter'] = default_filter
|
|
env['filter'].add_var(value)
|
|
def set_group(option, opt_str, value, parser, *args, **kw):
|
|
if env['filter'] is None: env['filter'] = default_filter
|
|
env['filter'].set_group(value)
|
|
def set_break_on(option, opt_str, value, parser, *args, **kw):
|
|
if env['filter'] is None: env['filter'] = default_filter
|
|
env['filter'].set_break_on(value)
|
|
def set_content_disposition(option, opt_str, value, parser, *args, **kw):
|
|
if value in ('attachment', 'inline'): pass
|
|
elif value == 'none': value = None
|
|
else:
|
|
if cgi_mode:
|
|
if not cgi_cache: cgi_nocache()
|
|
print "Content-Type: text/plain; charset=utf-8"
|
|
print
|
|
print "%s: Disposition de contenu invalide. Ce soit être attachment, inline ou none" % cgi_content_disposition
|
|
sys.exit(1)
|
|
if env['filter'] is None: default_content.disposition = value
|
|
else: env['filter'].force_disposition = value
|
|
def set_content_type(option, opt_str, value, parser, *args, **kw):
|
|
if env['filter'] is None: default_content.type = value
|
|
else: env['filter'].force_type = value
|
|
def set_content_charset(option, opt_str, value, parser, *args, **kw):
|
|
if env['filter'] is None: default_content.charset = value
|
|
else: env['filter'].force_charset = value
|
|
|
|
from optparse import OptionParser
|
|
OP = OptionParser(usage=u"\n\t%prog [options] /path/to/dir", description=__doc__)
|
|
OP.add_option('-e', '--spec', dest='spec', action='callback', callback=add_spec, type='string',
|
|
help=u"Spécifier l'expression régulière permettant de sélectionner les fichiers à lister. "
|
|
+ u"La correspondance est tentée sur le nom du fichier avec la fonction match(). "
|
|
+ u"Penser à inclure l'ancre $ pour matcher le nom entier du fichier. "
|
|
+ u"Si l'expression régulière contient un caractère '/', la correspondance peut se faire sur le chemin complet du fichier. "
|
|
+ u"Pour simplifier, par défaut seule la partie située après le chemin de base est matchée, sauf si l'expression commence par '^', auquel cas la correspondance se fait sur le chemin complet du fichier. "
|
|
+ u"L'expression régulière peut définir des groupes qui sont utilisées pour l'extraction des variables."
|
|
+ u"\n Il est possible de spécifier cette option plusieurs fois."
|
|
+ u"\n Note: TOUTES les expressions régulières sont testées par rapport au nom du fichier, et pour celles qui correspondent, les variables correspondantes sont définies. "
|
|
+ u"Il faut donc ordonner les expressions régulières de la plus générale à la plus spécifique, contrairement à ce qui se fait d'habitude.")
|
|
OP.add_option('--glob', dest='pattern', action='callback', callback=add_glob, type='string',
|
|
help=u"Comme --spec, mais en utilisant les patterns du shell *, ?, [seq], [!seq]. "
|
|
+ u"Si l'expression contient un caractère '/', la correspondance se fait sur le chemin complet du fichier avec le module glob. "
|
|
+ u"Cette précision est importante notamment pour la recherche de fichiers dont le nom commence par un point. "
|
|
+ u"Si l'expression ne contient pas le caractère '/', la correspondance est faite uniquement sur le nom de fichier avec le module fnmatch, et le point n'a plus de signification particulière. "
|
|
+ u"Cette méthode de correspondance ne permet pas de définir des groupes et ne permet donc pas d'extraire des variables.")
|
|
OP.add_option('-E', '--allow-spec', dest='spec', action='callback', callback=allow_spec, type='string',
|
|
help=u"Ajouter une spécification de fichier qui peut être demandé avec --cgi-path-info. "
|
|
+ u"Ces fichiers ne sont pas inclus dans la liste.")
|
|
OP.add_option('-v', '--var', dest='var', action='callback', callback=add_var, type='string',
|
|
help=u"Définir la variable NAME à la valeur de l'expression VAR_EXPR. "
|
|
+ u"Dans cette expression, il est possible d'utiliser des expressions de la forme %(var)s pour inclure des variables déjà définies, ou \\N et \\g<NAME> pour inclure respectivement le groupe numéro N et le groupe nommé NAME de l'expression régulière --spec."
|
|
+ u"\n Cette option peut être spécifiée plusieurs fois. "
|
|
+ u"Elle s'applique à l'expression régulière définie par la dernière option --spec")
|
|
OP.add_option('-g', '--group', dest='group', action='callback', callback=set_group, type='string',
|
|
help=u"Spécifier l'expression qui permet de construire des ensembles de fichiers sur la base des groupes définis dans l'expression régulière de l'option --spec. "
|
|
+ u"Dans cette expression, il est possible d'utiliser des expressions de la forme %(var)s pour inclure des variables déjà définies, ou \\N ou \\g<NAME> pour inclure respectivement le groupe numéro N et le groupe nommé NAME de l'expression régulière --spec."
|
|
+ u"\n Cette option ne peut être spécifiée qu'une seule fois par option --spec")
|
|
OP.add_option('-b', '--break-on', dest='break_on', action='callback', callback=set_break_on, type='string',
|
|
help=u"Spécifier une expression qui permet de partitionner la list des fichiers au sein d'un même groupe. "
|
|
+ u"Dans cette expression, il est possible d'utiliser des expressions de la forme %(var)s pour inclure des variables déjà définies, ou \\N ou \\g<NAME> pour inclure respectivement le groupe numéro N et le groupe nommé NAME de l'expression régulière --spec."
|
|
+ u"\n Bien que ce ne soit pas une obligation, il est logique de trier la liste sur cette expression pour que les groupes de fichiers soient ensembles dans la liste."
|
|
+ u"\n Cette option ne peut être spécifiée qu'une seule fois par option --spec")
|
|
OP.add_option('-s', '--sort', dest='sortby',
|
|
help=u"Spécifier le champ sur lequel trier ainsi que le type de tri à utiliser. "
|
|
+ u"SORT_EXPR est de la forme FIELD:TYPE où FIELD est le nom du champ et TYPE est le type de tri: A, D, C et/ou N pour (A)scendant, (D)escendant, (C)haine, (N)numérique. "
|
|
+ u"Si un champ est spécifié mais que le type de tri ne l'est pas, la valeur par défaut est AC. "
|
|
+ u"Si cette option n'est pas spécifiée, le tri par défaut est 'mtime:DN'."
|
|
+ u"\n Il est possible de spécifier plusieurs champs pour le tri en les séparant par des virgules.")
|
|
OP.add_option('--cgi', dest='cgi_mode', action='store_true',
|
|
help=u"Activer le mode CGI. "
|
|
+ u"Ce mode est automatiquement activé si la variable d'environnement REQUEST_METHOD existe.")
|
|
OP.add_option('--cgi-allow-cache', dest='cgi_allow_cache', action='store_true',
|
|
help=u"En mode CGI, ne pas rajouter les en-tête désactivant la mise en cache du résultat.")
|
|
OP.add_option('--cgi-content-disposition', '--cgicd', dest='content-disposition', action='callback', callback=set_content_disposition, type='string',
|
|
help=u"En mode CGI, forcer la disposition de contenu avec lequel servir le fichier. "
|
|
+ u"Les valeurs valides sont attachment (par défaut), inline, none. "
|
|
+ u"Si cette option est spécifiée après une option --spec ou --glob, elle ne s'applique qu'aux fichiers qui correspondent à ce modèle")
|
|
OP.add_option('--cgi-content-type', '--cgict', dest='content-type', action='callback', callback=set_content_type, type='string',
|
|
help=u"En mode CGI, forcer le type de contenu avec lequel servir le fichier."
|
|
+ u"Si cette option est spécifiée après une option --spec ou --glob, elle ne s'applique qu'aux fichiers qui correspondent à ce modèle")
|
|
OP.add_option('--cgi-content-charset', '--cgicc', dest='content-charset', action='callback', callback=set_content_charset, type='string',
|
|
help=u"En mode CGI, forcer l'encoding de contenu avec lequel servir le fichier."
|
|
+ u"Si cette option est spécifiée après une option --spec ou --glob, elle ne s'applique qu'aux fichiers qui correspondent à ce modèle")
|
|
OP.add_option('-P', '--cgi-path-info', dest='path_info',
|
|
help=u"Spécifier un chemin d'un fichier à télécharger. "
|
|
+ u"En mode CGI, cette valeur est prise dans la variable d'environnement PATH_INFO")
|
|
OP.add_option('-Q', '--cgi-query-string', dest='query_string',
|
|
help=u"Spécifier la valeur de QUERY_STRING pour provisionner l'environnement du fichier résultat. "
|
|
+ u"En mode CGI, cette valeur est prise dans la variable d'environnement QUERY_STRING.")
|
|
OP.add_option('-N', '--cgi-script-name', dest='script_name',
|
|
help=u"Spécifier la valeur de SCRIPT_NAME pour provisionner l'environnement du fichier résultat. "
|
|
+ u"En mode CGI, cette option est automatiquement activée si QUERY_STRING contient le paramètre script_name=SCRIPT_NAME, la valeur par défaut étant la valeur de la variable d'environnement SCRIPT_NAME."
|
|
+ u"\n L'ordre de priorité pour le calcul de cette valeur est: d'abord le paramètre script_name dans QUERY_STRING, puis l'option de la ligne de commande, enfin la valeur de la variable d'environnement")
|
|
OP.add_option('-G', '--cgi-param-group', dest='select_group',
|
|
help=u"Sélectionner le groupe spécifié. "
|
|
+ u"Seuls les fichiers du groupe sont affichés. "
|
|
+ u"En mode CGI, cette option est automatiquement activée si QUERY_STRING contient le paramètre group=GROUP."
|
|
+ u"\n S'il n'y a qu'un seul groupe, il est automatiquement sélectionné."
|
|
+ u"\n L'ordre de priorité pour le calcul de cette valeur est: d'abord le paramètre group dans QUERY_STRING, puis l'option de la ligne de commande")
|
|
OP.add_option('-t', '--cgi-param-xslt', dest='xslt',
|
|
help=u"Ajouter le chemin vers la feuille de style XSLT dans le flux XML généré. "
|
|
+ u"En mode CGI, cette option est automatiquement activée si QUERY_STRING contient le paramètre xslt=XSLT."
|
|
+ u"\n L'ordre de priorité pour le calcul de cette valeur est: d'abord le paramètre xslt dans QUERY_STRING, puis l'option de la ligne de commande")
|
|
OP.add_option('-i', '--include', dest='includes', action='append',
|
|
help=u"Spécifier un paramètre à inclure pour construire la valeur du chemin xpath /result/env/query_string dans le résultat. "
|
|
+ u"Il est possible de spécifier plusieurs paramètres en les séparant par des virgules. "
|
|
+ u"Par défaut, prendre tous les paramètres de la requête.")
|
|
OP.add_option('-x', '--exclude', dest='excludes', action='append',
|
|
help=u"Spécifier un paramètre à exclure pour construire la valeur du chemin xpath /result/env/query_string dans le résultat. "
|
|
+ u"Il est possible de spécifier plusieurs paramètres en les séparant par des virgules.")
|
|
OP.add_option('-p', '--prefix', dest="prefix",
|
|
help=u"Ajouter les paramètres supplémentaires spécifiés à /result/env/query_string.")
|
|
env = dict(filters=[], filter=None)
|
|
o, args = OP.parse_args()
|
|
filters = env['filters']
|
|
filter = env['filter']
|
|
sortby = o.sortby
|
|
|
|
environ = os.environ
|
|
cgi_mode = o.cgi_mode
|
|
cgi_cache = o.cgi_allow_cache and True or False
|
|
cgi_query_string = o.query_string
|
|
if cgi_query_string is not None: environ['QUERY_STRING'] = cgi_query_string
|
|
if cgi_mode is None:
|
|
cgi_mode = 'REQUEST_METHOD' in environ
|
|
if cgi_mode:
|
|
import cgi; form = cgi.FieldStorage()
|
|
cgi_path_info = o.path_info
|
|
if cgi_path_info is None and 'PATH_INFO' in environ : cgi_path_info = environ.get('PATH_INFO')
|
|
if cgi_query_string is None and 'QUERY_STRING' in environ: cgi_query_string = environ.get('QUERY_STRING')
|
|
cgi_script_name = None
|
|
if 'script_name' in form and cgi_script_name is None: cgi_script_name = form.getfirst("script_name")
|
|
if cgi_script_name is None: cgi_script_name = o.script_name
|
|
if cgi_script_name is None and 'SCRIPT_NAME' in environ : cgi_script_name = environ.get('SCRIPT_NAME')
|
|
cgi_select_group = None
|
|
if 'group' in form and cgi_select_group is None: cgi_select_group = form.getfirst("group")
|
|
if cgi_select_group is None: cgi_select_group = o.select_group
|
|
cgi_xslt = None
|
|
if 'xslt' in form and cgi_xslt is None: cgi_xslt = form.getfirst("xslt")
|
|
if cgi_xslt is None: cgi_xslt = o.xslt
|
|
else:
|
|
cgi_path_info = o.path_info
|
|
cgi_query_string = o.query_string
|
|
cgi_script_name = o.script_name
|
|
cgi_select_group = o.select_group
|
|
cgi_xslt = o.xslt
|
|
|
|
if cgi_path_info is not None: cgi_path_info = path.split(cgi_path_info)[1]
|
|
|
|
if filter is not None: filters.append(filter)
|
|
if filter is None and not filters: filters.append(default_filter)
|
|
if len(args) == 0: args = ['.']
|
|
if sortby is None: sortby = 'mtime:DN'
|
|
|
|
files = []
|
|
for basedir in args:
|
|
files.extend(find_files(basedir, filters))
|
|
sortfunc = build_sortfunc(sortby)
|
|
files = sort_files(files, sortfunc)
|
|
fgroups = build_fgroups(files)
|
|
if cgi_mode and fgroups and cgi_select_group is None:
|
|
# En mode CGI, s'il y a plusieurs groupes, ne pas afficher la liste
|
|
# complète, mais requérir la sélection d'un groupe, sauf s'il n'y a
|
|
# qu'une seul groupe défini
|
|
if len(fgroups) == 1: cgi_select_group = fgroups[0]
|
|
else: cgi_select_group = ''
|
|
if cgi_select_group is not None:
|
|
files = filter_files(files, cgi_select_group)
|
|
if cgi_path_info:
|
|
file = select_file(files, cgi_path_info)
|
|
if file is None:
|
|
if cgi_mode:
|
|
if not cgi_cache: cgi_nocache()
|
|
print "Content-Type: text/plain; charset=utf-8"
|
|
print
|
|
print "Impossible de trouver le fichier %s" % cgi_path_info
|
|
sys.exit(1)
|
|
else:
|
|
if cgi_mode:
|
|
if not cgi_cache: cgi_nocache()
|
|
dlname = file.dlname or file.name
|
|
if file.has_key('content_disposition'): content_disposition = file.content_disposition
|
|
else: content_disposition = default_content.disposition
|
|
if file.has_key('content_type'): content_type = file.content_type
|
|
else: content_type = default_content.type
|
|
if content_type is None: content_type, encoding = mimetypes.guess_type(dlname, False)
|
|
if content_type is None: content_type = "application/octet-stream"
|
|
if file.has_key('content_charset'): content_charset = file.content_charset
|
|
else: content_charset = default_content.charset
|
|
|
|
print "Content-Type: %s; charset=%s" % (content_type, content_charset)
|
|
if content_disposition is not None:
|
|
print "Content-Disposition: %s; filename=\"%s\"" % (content_disposition, dlname)
|
|
print
|
|
inf = open(file.pf, 'rb')
|
|
try: shutil.copyfileobj(inf, sys.stdout)
|
|
finally: inf.close()
|
|
sys.exit(0)
|
|
|
|
if cgi_mode:
|
|
if not cgi_cache: cgi_nocache()
|
|
print "Content-Type: text/xml; charset=utf-8"
|
|
print
|
|
print_files(files, fgroups, cgi_select_group, cgi_script_name, cgi_xslt,
|
|
cgi_query_string, o.includes, o.excludes, o.prefix)
|
|
|
|
if __name__ == '__main__':
|
|
run_cgilsxml()
|