1 #! /usr/local/bin/python
3 # NOTE: the above "/usr/local/bin/python" is NOT a mistake. It is
4 # intentionally NOT "/usr/bin/env python". On many systems
5 # (e.g. Solaris), /usr/local/bin is not in $PATH as passed to CGI
6 # scripts, and /usr/local/bin is the default directory where Python is
7 # installed, so /usr/bin/env would be unable to find python. Granted,
8 # binary installations by Linux vendors often install Python in
9 # /usr/bin. So let those vendors patch cgi.py to match their choice
12 """Support module for CGI (Common Gateway Interface) scripts.
14 This module defines a number of utilities for use by CGI scripts
18 # XXX Perhaps there should be a slimmed version that doesn't contain
19 # all those backwards compatible and debugging classes and functions?
24 # Michael McLay started this module. Steve Majewski changed the
25 # interface to SvFormContentDict and FormContentDict. The multipart
26 # parsing was inspired by code submitted by Andreas Paepcke. Guido van
27 # Rossum rewrote, reformatted and documented the module and is currently
28 # responsible for its maintenance.
37 from operator
import attrgetter
44 from warnings
import filterwarnings
, catch_warnings
, warn
45 with
catch_warnings():
47 filterwarnings("ignore", ".*mimetools has been removed",
51 filterwarnings("ignore", ".*rfc822 has been removed", DeprecationWarning)
55 from cStringIO
import StringIO
57 from StringIO
import StringIO
59 __all__
= ["MiniFieldStorage", "FieldStorage", "FormContentDict",
60 "SvFormContentDict", "InterpFormContentDict", "FormContent",
61 "parse", "parse_qs", "parse_qsl", "parse_multipart",
62 "parse_header", "print_exception", "print_environ",
63 "print_form", "print_directory", "print_arguments",
64 "print_environ_usage", "escape"]
69 logfile
= "" # Filename to log to, if not empty
70 logfp
= None # File object to log to, if not None
72 def initlog(*allargs
):
73 """Write a log message, if there is a log file.
75 Even though this function is called initlog(), you should always
76 use log(); log is a variable that is set either to initlog
77 (initially), to dolog (once the log file has been opened), or to
78 nolog (when logging is disabled).
80 The first argument is a format string; the remaining arguments (if
81 any) are arguments to the % operator, so e.g.
82 log("%s: %s", "a", "b")
83 will write "a: b" to the log file, followed by a newline.
85 If the global logfp is not None, it should be a file object to
86 which log data is written.
88 If the global logfp is None, the global logfile may be a string
89 giving a filename to open, in append mode. This file should be
90 world writable!!! If the file can't be opened, logging is
91 silently disabled (since there is no safe place where we could
92 send an error message).
96 if logfile
and not logfp
:
98 logfp
= open(logfile
, "a")
107 def dolog(fmt
, *args
):
108 """Write a log message to the log file. See initlog() for docs."""
109 logfp
.write(fmt
%args
+ "\n")
112 """Dummy function, assigned to log when logging is disabled."""
115 log
= initlog
# The current logging function
121 # Maximum input we will accept when REQUEST_METHOD is POST
122 # 0 ==> unlimited input
125 def parse(fp
=None, environ
=os
.environ
, keep_blank_values
=0, strict_parsing
=0):
126 """Parse a query in the environment or from a file (default stdin)
128 Arguments, all optional:
130 fp : file pointer; default: sys.stdin
132 environ : environment dictionary; default: os.environ
134 keep_blank_values: flag indicating whether blank values in
135 URL encoded forms should be treated as blank strings.
136 A true value indicates that blanks should be retained as
137 blank strings. The default false value indicates that
138 blank values are to be ignored and treated as if they were
141 strict_parsing: flag indicating what to do with parsing errors.
142 If false (the default), errors are silently ignored.
143 If true, errors raise a ValueError exception.
147 if not 'REQUEST_METHOD' in environ
:
148 environ
['REQUEST_METHOD'] = 'GET' # For testing stand-alone
149 if environ
['REQUEST_METHOD'] == 'POST':
150 ctype
, pdict
= parse_header(environ
['CONTENT_TYPE'])
151 if ctype
== 'multipart/form-data':
152 return parse_multipart(fp
, pdict
)
153 elif ctype
== 'application/x-www-form-urlencoded':
154 clength
= int(environ
['CONTENT_LENGTH'])
155 if maxlen
and clength
> maxlen
:
156 raise ValueError, 'Maximum content length exceeded'
157 qs
= fp
.read(clength
)
159 qs
= '' # Unknown content-type
160 if 'QUERY_STRING' in environ
:
162 qs
= qs
+ environ
['QUERY_STRING']
165 qs
= qs
+ sys
.argv
[1]
166 environ
['QUERY_STRING'] = qs
# XXX Shouldn't, really
167 elif 'QUERY_STRING' in environ
:
168 qs
= environ
['QUERY_STRING']
174 environ
['QUERY_STRING'] = qs
# XXX Shouldn't, really
175 return parse_qs(qs
, keep_blank_values
, strict_parsing
)
178 # parse query string function called from urlparse,
179 # this is done in order to maintain backward compatiblity.
181 def parse_qs(qs
, keep_blank_values
=0, strict_parsing
=0):
182 """Parse a query given as a string argument."""
183 warn("cgi.parse_qs is deprecated, use urlparse.parse_qs \
184 instead", PendingDeprecationWarning
, 2)
185 return urlparse
.parse_qs(qs
, keep_blank_values
, strict_parsing
)
188 def parse_qsl(qs
, keep_blank_values
=0, strict_parsing
=0):
189 """Parse a query given as a string argument."""
190 warn("cgi.parse_qsl is deprecated, use urlparse.parse_qsl instead",
191 PendingDeprecationWarning
, 2)
192 return urlparse
.parse_qsl(qs
, keep_blank_values
, strict_parsing
)
194 def parse_multipart(fp
, pdict
):
195 """Parse multipart input.
199 pdict: dictionary containing other parameters of content-type header
201 Returns a dictionary just like parse_qs(): keys are the field names, each
202 value is a list of values for that field. This is easy to use but not
203 much good if you are expecting megabytes to be uploaded -- in that case,
204 use the FieldStorage class instead which is much more flexible. Note
205 that content-type is the raw, unparsed contents of the content-type
208 XXX This does not parse nested multipart parts -- use FieldStorage for
211 XXX This should really be subsumed by FieldStorage altogether -- no
212 point in having two implementations of the same parsing algorithm.
213 Also, FieldStorage protects itself better against certain DoS attacks
214 by limiting the size of the data read in one chunk. The API here
215 does not support that kind of protection. This also affects parse()
216 since it can call parse_multipart().
220 if 'boundary' in pdict
:
221 boundary
= pdict
['boundary']
222 if not valid_boundary(boundary
):
223 raise ValueError, ('Invalid boundary in multipart form: %r'
226 nextpart
= "--" + boundary
227 lastpart
= "--" + boundary
+ "--"
231 while terminator
!= lastpart
:
235 # At start of next part. Read headers first.
236 headers
= mimetools
.Message(fp
)
237 clength
= headers
.getheader('content-length')
244 if maxlen
and bytes
> maxlen
:
245 raise ValueError, 'Maximum content length exceeded'
246 data
= fp
.read(bytes
)
249 # Read lines until end of part.
254 terminator
= lastpart
# End outer loop
257 terminator
= line
.strip()
258 if terminator
in (nextpart
, lastpart
):
266 # Strip final line terminator
268 if line
[-2:] == "\r\n":
270 elif line
[-1:] == "\n":
273 data
= "".join(lines
)
274 line
= headers
['content-disposition']
277 key
, params
= parse_header(line
)
278 if key
!= 'form-data':
281 name
= params
['name']
285 partdict
[name
].append(data
)
287 partdict
[name
] = [data
]
296 while end
> 0 and s
.count('"', 0, end
) % 2:
297 end
= s
.find(';', end
+ 1)
304 def parse_header(line
):
305 """Parse a Content-type like header.
307 Return the main content-type and a dictionary of options.
310 parts
= _parseparam(';' + line
)
316 name
= p
[:i
].strip().lower()
317 value
= p
[i
+1:].strip()
318 if len(value
) >= 2 and value
[0] == value
[-1] == '"':
320 value
= value
.replace('\\\\', '\\').replace('\\"', '"')
325 # Classes for field storage
326 # =========================
328 class MiniFieldStorage
:
330 """Like FieldStorage, for use when no file uploads are possible."""
339 disposition_options
= {}
342 def __init__(self
, name
, value
):
343 """Constructor from field name and value."""
346 # self.file = StringIO(value)
349 """Return printable representation."""
350 return "MiniFieldStorage(%r, %r)" % (self
.name
, self
.value
)
355 """Store a sequence of fields, reading multipart/form-data.
357 This class provides naming, typing, files stored on disk, and
358 more. At the top level, it is accessible like a dictionary, whose
359 keys are the field names. (Note: None can occur as a field name.)
360 The items are either a Python list (if there's multiple values) or
361 another FieldStorage or MiniFieldStorage object. If it's a single
362 object, it has the following attributes:
364 name: the field name, if specified; otherwise None
366 filename: the filename, if specified; otherwise None; this is the
367 client side filename, *not* the file name on which it is
368 stored (that's a temporary file you don't deal with)
370 value: the value as a *string*; for file uploads, this
371 transparently reads the file every time you request the value
373 file: the file(-like) object from which you can read the data;
374 None if the data is stored a simple string
376 type: the content-type, or None if not specified
378 type_options: dictionary of options specified on the content-type
381 disposition: content-disposition, or None if not specified
383 disposition_options: dictionary of corresponding options
385 headers: a dictionary(-like) object (sometimes rfc822.Message or a
386 subclass thereof) containing *all* headers
388 The class is subclassable, mostly for the purpose of overriding
389 the make_file() method, which is called internally to come up with
390 a file open for reading and writing. This makes it possible to
391 override the default choice of storing all files in a temporary
392 directory and unlinking them as soon as they have been opened.
396 def __init__(self
, fp
=None, headers
=None, outerboundary
="",
397 environ
=os
.environ
, keep_blank_values
=0, strict_parsing
=0):
398 """Constructor. Read multipart/* until last part.
400 Arguments, all optional:
402 fp : file pointer; default: sys.stdin
403 (not used when the request method is GET)
405 headers : header dictionary-like object; default:
406 taken from environ as per CGI spec
408 outerboundary : terminating multipart boundary
409 (for internal use only)
411 environ : environment dictionary; default: os.environ
413 keep_blank_values: flag indicating whether blank values in
414 URL encoded forms should be treated as blank strings.
415 A true value indicates that blanks should be retained as
416 blank strings. The default false value indicates that
417 blank values are to be ignored and treated as if they were
420 strict_parsing: flag indicating what to do with parsing errors.
421 If false (the default), errors are silently ignored.
422 If true, errors raise a ValueError exception.
426 self
.keep_blank_values
= keep_blank_values
427 self
.strict_parsing
= strict_parsing
428 if 'REQUEST_METHOD' in environ
:
429 method
= environ
['REQUEST_METHOD'].upper()
430 self
.qs_on_post
= None
431 if method
== 'GET' or method
== 'HEAD':
432 if 'QUERY_STRING' in environ
:
433 qs
= environ
['QUERY_STRING']
440 headers
= {'content-type':
441 "application/x-www-form-urlencoded"}
445 # Set default content-type for POST to what's traditional
446 headers
['content-type'] = "application/x-www-form-urlencoded"
447 if 'CONTENT_TYPE' in environ
:
448 headers
['content-type'] = environ
['CONTENT_TYPE']
449 if 'QUERY_STRING' in environ
:
450 self
.qs_on_post
= environ
['QUERY_STRING']
451 if 'CONTENT_LENGTH' in environ
:
452 headers
['content-length'] = environ
['CONTENT_LENGTH']
453 self
.fp
= fp
or sys
.stdin
454 self
.headers
= headers
455 self
.outerboundary
= outerboundary
457 # Process content-disposition header
458 cdisp
, pdict
= "", {}
459 if 'content-disposition' in self
.headers
:
460 cdisp
, pdict
= parse_header(self
.headers
['content-disposition'])
461 self
.disposition
= cdisp
462 self
.disposition_options
= pdict
465 self
.name
= pdict
['name']
467 if 'filename' in pdict
:
468 self
.filename
= pdict
['filename']
470 # Process content-type header
472 # Honor any existing content-type header. But if there is no
473 # content-type header, use some sensible defaults. Assume
474 # outerboundary is "" at the outer level, but something non-false
475 # inside a multi-part. The default for an inner part is text/plain,
476 # but for an outer part it should be urlencoded. This should catch
477 # bogus clients which erroneously forget to include a content-type
480 # See below for what we do if there does exist a content-type header,
481 # but it happens to be something we don't understand.
482 if 'content-type' in self
.headers
:
483 ctype
, pdict
= parse_header(self
.headers
['content-type'])
484 elif self
.outerboundary
or method
!= 'POST':
485 ctype
, pdict
= "text/plain", {}
487 ctype
, pdict
= 'application/x-www-form-urlencoded', {}
489 self
.type_options
= pdict
490 self
.innerboundary
= ""
491 if 'boundary' in pdict
:
492 self
.innerboundary
= pdict
['boundary']
494 if 'content-length' in self
.headers
:
496 clen
= int(self
.headers
['content-length'])
499 if maxlen
and clen
> maxlen
:
500 raise ValueError, 'Maximum content length exceeded'
503 self
.list = self
.file = None
505 if ctype
== 'application/x-www-form-urlencoded':
506 self
.read_urlencoded()
507 elif ctype
[:10] == 'multipart/':
508 self
.read_multi(environ
, keep_blank_values
, strict_parsing
)
513 """Return a printable representation."""
514 return "FieldStorage(%r, %r, %r)" % (
515 self
.name
, self
.filename
, self
.value
)
518 return iter(self
.keys())
520 def __getattr__(self
, name
):
522 raise AttributeError, name
525 value
= self
.file.read()
527 elif self
.list is not None:
533 def __getitem__(self
, key
):
534 """Dictionary style indexing."""
535 if self
.list is None:
536 raise TypeError, "not indexable"
538 for item
in self
.list:
539 if item
.name
== key
: found
.append(item
)
547 def getvalue(self
, key
, default
=None):
548 """Dictionary style get() method, including 'value' lookup."""
551 if type(value
) is type([]):
552 return map(attrgetter('value'), value
)
558 def getfirst(self
, key
, default
=None):
559 """ Return the first value received."""
562 if type(value
) is type([]):
563 return value
[0].value
569 def getlist(self
, key
):
570 """ Return list of received values."""
573 if type(value
) is type([]):
574 return map(attrgetter('value'), value
)
581 """Dictionary style keys() method."""
582 if self
.list is None:
583 raise TypeError, "not indexable"
584 return list(set(item
.name
for item
in self
.list))
586 def has_key(self
, key
):
587 """Dictionary style has_key() method."""
588 if self
.list is None:
589 raise TypeError, "not indexable"
590 return any(item
.name
== key
for item
in self
.list)
592 def __contains__(self
, key
):
593 """Dictionary style __contains__ method."""
594 if self
.list is None:
595 raise TypeError, "not indexable"
596 return any(item
.name
== key
for item
in self
.list)
599 """Dictionary style len(x) support."""
600 return len(self
.keys())
602 def __nonzero__(self
):
603 return bool(self
.list)
605 def read_urlencoded(self
):
606 """Internal: read data in query string format."""
607 qs
= self
.fp
.read(self
.length
)
609 qs
+= '&' + self
.qs_on_post
610 self
.list = list = []
611 for key
, value
in urlparse
.parse_qsl(qs
, self
.keep_blank_values
,
612 self
.strict_parsing
):
613 list.append(MiniFieldStorage(key
, value
))
616 FieldStorageClass
= None
618 def read_multi(self
, environ
, keep_blank_values
, strict_parsing
):
619 """Internal: read a part that is itself multipart."""
620 ib
= self
.innerboundary
621 if not valid_boundary(ib
):
622 raise ValueError, 'Invalid boundary in multipart form: %r' % (ib
,)
625 for key
, value
in urlparse
.parse_qsl(self
.qs_on_post
,
626 self
.keep_blank_values
, self
.strict_parsing
):
627 self
.list.append(MiniFieldStorage(key
, value
))
628 FieldStorageClass
= None
630 klass
= self
.FieldStorageClass
or self
.__class
__
631 part
= klass(self
.fp
, {}, ib
,
632 environ
, keep_blank_values
, strict_parsing
)
633 # Throw first part away
635 headers
= rfc822
.Message(self
.fp
)
636 part
= klass(self
.fp
, headers
, ib
,
637 environ
, keep_blank_values
, strict_parsing
)
638 self
.list.append(part
)
641 def read_single(self
):
642 """Internal: read an atomic part."""
650 bufsize
= 8*1024 # I/O buffering size for copy to file
652 def read_binary(self
):
653 """Internal: read binary data."""
654 self
.file = self
.make_file('b')
658 data
= self
.fp
.read(min(todo
, self
.bufsize
))
662 self
.file.write(data
)
663 todo
= todo
- len(data
)
665 def read_lines(self
):
666 """Internal: read lines until EOF or outerboundary."""
667 self
.file = self
.__file
= StringIO()
668 if self
.outerboundary
:
669 self
.read_lines_to_outerboundary()
671 self
.read_lines_to_eof()
673 def __write(self
, line
):
674 if self
.__file
is not None:
675 if self
.__file
.tell() + len(line
) > 1000:
676 self
.file = self
.make_file('')
677 self
.file.write(self
.__file
.getvalue())
679 self
.file.write(line
)
681 def read_lines_to_eof(self
):
682 """Internal: read lines until EOF."""
684 line
= self
.fp
.readline(1<<16)
690 def read_lines_to_outerboundary(self
):
691 """Internal: read lines until outerboundary."""
692 next
= "--" + self
.outerboundary
695 last_line_lfend
= True
697 line
= self
.fp
.readline(1<<16)
701 if line
[:2] == "--" and last_line_lfend
:
702 strippedline
= line
.strip()
703 if strippedline
== next
:
705 if strippedline
== last
:
709 if line
[-2:] == "\r\n":
712 last_line_lfend
= True
713 elif line
[-1] == "\n":
716 last_line_lfend
= True
719 last_line_lfend
= False
720 self
.__write
(odelim
+ line
)
722 def skip_lines(self
):
723 """Internal: skip lines until outer boundary if defined."""
724 if not self
.outerboundary
or self
.done
:
726 next
= "--" + self
.outerboundary
728 last_line_lfend
= True
730 line
= self
.fp
.readline(1<<16)
734 if line
[:2] == "--" and last_line_lfend
:
735 strippedline
= line
.strip()
736 if strippedline
== next
:
738 if strippedline
== last
:
741 last_line_lfend
= line
.endswith('\n')
743 def make_file(self
, binary
=None):
744 """Overridable: return a readable & writable file.
746 The file will be used as follows:
747 - data is written to it
749 - data is read from it
751 The 'binary' argument is unused -- the file is always opened
754 This version opens a temporary file for reading and writing,
755 and immediately deletes (unlinks) it. The trick (on Unix!) is
756 that the file can still be used, but it can't be opened by
757 another process, and it will automatically be deleted when it
758 is closed or when the current process terminates.
760 If you want a more permanent file, you derive a class which
761 overrides this method. If you want a visible temporary file
762 that is nevertheless automatically deleted when the script
763 terminates, try defining a __del__ method in a derived class
764 which unlinks the temporary files you have created.
768 return tempfile
.TemporaryFile("w+b")
772 # Backwards Compatibility Classes
773 # ===============================
775 class FormContentDict(UserDict
.UserDict
):
776 """Form content as dictionary with a list of values per field.
778 form = FormContentDict()
780 form[key] -> [value, value, ...]
781 key in form -> Boolean
782 form.keys() -> [key, key, ...]
783 form.values() -> [[val, val, ...], [val, val, ...], ...]
784 form.items() -> [(key, [val, val, ...]), (key, [val, val, ...]), ...]
785 form.dict == {key: [val, val, ...], ...}
788 def __init__(self
, environ
=os
.environ
, keep_blank_values
=0, strict_parsing
=0):
789 self
.dict = self
.data
= parse(environ
=environ
,
790 keep_blank_values
=keep_blank_values
,
791 strict_parsing
=strict_parsing
)
792 self
.query_string
= environ
['QUERY_STRING']
795 class SvFormContentDict(FormContentDict
):
796 """Form content as dictionary expecting a single value per field.
798 If you only expect a single value for each field, then form[key]
799 will return that single value. It will raise an IndexError if
800 that expectation is not true. If you expect a field to have
801 possible multiple values, than you can use form.getlist(key) to
802 get all of the values. values() and items() are a compromise:
803 they return single strings where there is a single value, and
804 lists of strings otherwise.
807 def __getitem__(self
, key
):
808 if len(self
.dict[key
]) > 1:
809 raise IndexError, 'expecting a single value'
810 return self
.dict[key
][0]
811 def getlist(self
, key
):
812 return self
.dict[key
]
815 for value
in self
.dict.values():
817 result
.append(value
[0])
818 else: result
.append(value
)
822 for key
, value
in self
.dict.items():
824 result
.append((key
, value
[0]))
825 else: result
.append((key
, value
))
829 class InterpFormContentDict(SvFormContentDict
):
830 """This class is present for backwards compatibility only."""
831 def __getitem__(self
, key
):
832 v
= SvFormContentDict
.__getitem
__(self
, key
)
833 if v
[0] in '0123456789+-.':
837 except ValueError: pass
841 for key
in self
.keys():
843 result
.append(self
[key
])
845 result
.append(self
.dict[key
])
849 for key
in self
.keys():
851 result
.append((key
, self
[key
]))
853 result
.append((key
, self
.dict[key
]))
857 class FormContent(FormContentDict
):
858 """This class is present for backwards compatibility only."""
859 def values(self
, key
):
860 if key
in self
.dict :return self
.dict[key
]
862 def indexed_value(self
, key
, location
):
864 if len(self
.dict[key
]) > location
:
865 return self
.dict[key
][location
]
868 def value(self
, key
):
869 if key
in self
.dict: return self
.dict[key
][0]
871 def length(self
, key
):
872 return len(self
.dict[key
])
873 def stripped(self
, key
):
874 if key
in self
.dict: return self
.dict[key
][0].strip()
883 def test(environ
=os
.environ
):
884 """Robust test CGI script, usable as main program.
886 Write minimal HTTP headers and dump all information provided to
887 the script in HTML form.
890 print "Content-type: text/html"
892 sys
.stderr
= sys
.stdout
894 form
= FieldStorage() # Replace with other classes to test those
898 print_environ(environ
)
899 print_environ_usage()
901 exec "testing print_exception() -- <I>italics?</I>"
904 print "<H3>What follows is a test, not an actual exception:</H3>"
909 print "<H1>Second try with a small maxlen...</H1>"
914 form
= FieldStorage() # Replace with other classes to test those
918 print_environ(environ
)
922 def print_exception(type=None, value
=None, tb
=None, limit
=None):
924 type, value
, tb
= sys
.exc_info()
927 print "<H3>Traceback (most recent call last):</H3>"
928 list = traceback
.format_tb(tb
, limit
) + \
929 traceback
.format_exception_only(type, value
)
930 print "<PRE>%s<B>%s</B></PRE>" % (
931 escape("".join(list[:-1])),
936 def print_environ(environ
=os
.environ
):
937 """Dump the shell environment as HTML."""
938 keys
= environ
.keys()
941 print "<H3>Shell Environment:</H3>"
944 print "<DT>", escape(key
), "<DD>", escape(environ
[key
])
948 def print_form(form
):
949 """Dump the contents of a form as HTML."""
953 print "<H3>Form Contents:</H3>"
955 print "<P>No form fields."
958 print "<DT>" + escape(key
) + ":",
960 print "<i>" + escape(repr(type(value
))) + "</i>"
961 print "<DD>" + escape(repr(value
))
965 def print_directory():
966 """Dump the current directory as HTML."""
968 print "<H3>Current Working Directory:</H3>"
971 except os
.error
, msg
:
972 print "os.error:", escape(str(msg
))
977 def print_arguments():
979 print "<H3>Command Line Arguments:</H3>"
984 def print_environ_usage():
985 """Dump a list of environment variables used by CGI as HTML."""
987 <H3>These environment variables could have been set:</H3>
997 <LI>GATEWAY_INTERFACE
1015 In addition, HTTP headers sent by the server may be passed in the
1016 environment as well. Here are some common variable names:
1031 def escape(s
, quote
=None):
1032 '''Replace special characters "&", "<" and ">" to HTML-safe sequences.
1033 If the optional flag quote is true, the quotation mark character (")
1034 is also translated.'''
1035 s
= s
.replace("&", "&") # Must be done first!
1036 s
= s
.replace("<", "<")
1037 s
= s
.replace(">", ">")
1039 s
= s
.replace('"', """)
1042 def valid_boundary(s
, _vb_pattern
="^[ -~]{0,200}[!-~]$"):
1044 return re
.match(_vb_pattern
, s
)
1049 # Call test() when this file is run as a script (not imported as a module)
1050 if __name__
== '__main__':