16 |
17 |
20 |
21 |
22 |
23 | {errorparas}
24 |
25 |
26 |
27 | {footer}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/lib/accepted.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
17 |
20 |
21 |
22 |
23 |
24 | The machine comes to life (figuratively) with a dazzling display
25 | of colored lights and bizarre noises. After a few moments, the
26 | excitement abates.
27 |
28 |
29 |
30 |
Thank you for your upload. Please check the following filenames and
31 | file sizes to make sure the file was uploaded correctly.
32 |
33 | {filenames}
34 |
35 |
Note that your files will not appear in the
36 | unprocessed
37 | folder immediately. A human moderator will usually move your files
38 | to the "unprocessed" folder within a few days, and then move
39 | them to their final destination some time after that.
40 | For more information on the upload process,
41 | see this post.
42 |
43 |
Thanks again for your upload. You may
44 | return to the Archive if you wish.
45 |
46 |
47 |
48 | {footer}
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/lib/main.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
16 |
17 |
20 |
21 |
22 |
23 |
24 | This is a large, cold room whose sole exit is to the north.
25 | In one corner there is a machine reminiscent of a clothes dryer.
26 | On its face is a switch which is labelled “START.”
27 |
28 |
29 |
30 |
31 | The Archive's mission is to preserve the history and practice of interactive fiction and make it freely available to the public. Use this form to donate IF-related files. Games, development tools, solutions, articles, and documentation are all gratefully accepted.
32 |
33 |
34 |
35 | Please read our Terms of Use before you use this form. By uploading files, you give the Archive permission to store those files forever and distribute them freely to the public. For more information on the upload process, see this post.
36 |
37 |
38 |
39 | You will need to tell us what you've uploaded. Please include as much information as possible: title, author, version number, copyright license, what platform it runs on. If we can't make heads or tails of an anonymous upload, we'll probably just delete it, so be specific. There is a size limit for uploads.
40 |
41 |
42 |
43 | If the upload is successful, you will see a "Thank you for your upload" response.
44 | If you have any problems, please contact us: submit@ifarchive.org.
45 |
46 |
47 |
75 |
76 |
77 |
78 | {footer}
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/ifdbize.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # This is part of the IFDB-Archive integration. Here's how it works:
4 | #
5 | # When someone uses the "upload to IF archive" flow on IFDB, it creates
6 | # a "pending" upload link on IFDB. It also hands the upload to us with
7 | # a one-off ID key. (This is not the TUID, by the way.)
8 | #
9 | # Later, we run this script, which passes the final Archive path back
10 | # to IFDB, along with the ID. IFDB can then update the pending link.
11 | # The IFDB form URL used for this purpose is:
12 | #
13 | # http://ifdb.org/ifarchive-commit?ifdbid=XXX&path=PPP&key=SECRET
14 | #
15 | # The data shared between upload.py and this script is stored in a
16 | # Python "shelve" file (see ifdbIdFile). The format is a dict mapping
17 | # filemd5: { 'time':timestamp, 'id':ifdbid }
18 | # For historical reasons we are still on shelve/pickle protocol 2.
19 |
20 | import os, os.path, shelve, hashlib
21 | import urllib.request
22 | import configparser
23 |
24 | configpath = '/var/ifarchive/lib/ifarch.config'
25 | config = configparser.ConfigParser()
26 | config.read(configpath)
27 |
28 | ifdbIdFile = config['DEFAULT']['IFDBIdMapFile']
29 | ifdbKey = config['DEFAULT']['IFDBCommitKey']
30 |
31 | def submitID(fns, askForID = False):
32 | ifdbUrl = "https://ifdb.org/ifarchive-commit?ifdbid={ifdbid}&path={path}&key={key}"
33 | dirPrefix = '/var/ifarchive/htdocs' # Prefix to remove from a file's abspath
34 |
35 | for fn in fns:
36 | # Get the directory and base name of the file
37 | absfn = os.path.realpath(os.path.abspath(fn))
38 | if not os.path.isfile(absfn):
39 | continue
40 | (pathfn, basefn) = os.path.split(absfn)
41 |
42 | # See if an IFDB ID exists for the file (based on its md5 hash)
43 | o = open(fn, "rb")
44 | hashval = hashlib.md5(o.read()).hexdigest()
45 | o.close()
46 |
47 | # We gotta play with the umask to open shelve
48 | ifdbID = None
49 | oldmask = os.umask(0)
50 | ids = shelve.open(ifdbIdFile, protocol=2)
51 | if hashval in ids:
52 | ifdbID = ids[hashval]['id']
53 | ids.close()
54 | # Reset umask
55 | os.umask(oldmask)
56 |
57 | # If not, query for the IFDB ID interactively (sometimes)
58 | if ifdbID is None:
59 | if askForID:
60 | ifdbID = input("IFDB ID for %s: "% basefn)
61 | # If no ID is passed, stop
62 | if not ifdbID:
63 | return
64 | else:
65 | print("No IFID found for "+fn)
66 | return
67 |
68 | # Massage the directory to fit what IFDB needs
69 | ifdbPath = absfn
70 | if ifdbPath.startswith(dirPrefix):
71 | ifdbPath = ifdbPath[len(dirPrefix):]
72 |
73 | # Submit the directory to IFDB
74 | urlToFetch = ifdbUrl.format(ifdbid=ifdbID, path=ifdbPath, key=ifdbKey)
75 | try:
76 | ifdbPage = urllib.request.urlopen(urlToFetch)
77 | resultStr = ifdbPage.readline()
78 | ifdbPage.close()
79 | resultStr = resultStr.decode()
80 | except Exception as ex:
81 | resultStr = str(ex)
82 |
83 | # The ifdb update page returns plain text from the following list:
84 | # OK
85 | # Error: invalid API key
86 | # Error: no link found to this pending URL
87 | # Error: database update failed:
88 | if resultStr == 'OK':
89 | print("IFDB updated for %s (ID %s)\n" % (fn, ifdbID))
90 | elif resultStr.startswith('Error: '):
91 | print("IFDB update for %s failed. %s" % (fn, resultStr))
92 | else:
93 | print("IFDB update for %s failed unexpectedly: %s" % (fn, resultStr))
94 |
95 |
96 | if __name__ == "__main__":
97 | import optparse
98 |
99 | p = optparse.OptionParser(usage="%prog [file(s) to submit to IFDB]")
100 | p.add_option("-n", "--non-interactive", action="store_false",
101 | dest="askForID", help="don't ask the user to enter an ID")
102 | p.add_option("-i", "--interactive", action="store_true",
103 | dest="askForID",
104 | help="ask the user to enter an ID if no stored one is found [default]")
105 | p.set_defaults(askForID = True)
106 | (options, args) = p.parse_args()
107 |
108 | if len(args) == 0:
109 | p.error("No filenames to submit to IFDB. Type --help for more information.")
110 |
111 | submitID(args, options.askForID)
112 |
--------------------------------------------------------------------------------
/upload.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | """
4 | A simple CGI script to accept uploaded files.
5 |
6 | Written by Andrew Plotkin and many contributors.
7 | Originally adapted from a script by Tim Middleton.
8 |
9 | This script must run in a UTF-8 locale, or Unicode input will cause horrible
10 | errors. The Apache config contains the line "SetEnv LANG en_US.UTF-8", which
11 | takes care of this.
12 | """
13 |
14 | # Andrew Plotkin (1 Sep 2025):
15 | # - Include uploader name/email and filename with "incoming full" error.
16 | # Andrew Plotkin (28 Apr 2025):
17 | # - Minor template fixes to support testframe.
18 | # Andrew Plotkin (20 Feb 2024):
19 | # - Drop code that wrote to the ifids.db file.
20 | # Andrew Plotkin (17 Nov 2023):
21 | # - Loosen the definition of safe filename characters.
22 | # Andrew Plotkin (18 Oct 2023):
23 | # - Write to an SQL database as well as the ifids.db file.
24 | # Andrew Plotkin (6 Oct 2023):
25 | # - Read a config file for configurable (and nonpublic) info.
26 | # Andrew Plotkin (30 Sep 2023):
27 | # - Improved the filename-cleaning code.
28 | # - Bumped upload-dir limit.
29 | # Andrew Plotkin (27 May 2018):
30 | # - Added an "accept the terms of service" checkbox.
31 | # Andrew Plotkin (23 November 2017):
32 | # - Update to use subprocess instead of os.popen.
33 | # Andrew Plotkin (25 July 2017):
34 | # - Rewrote the whole script in Python 3.
35 | # - Put all the HTML templates in /var/ifarchive/lib/uploader.
36 | # - Added the "right to use" checkboxes.
37 | # - Cleaned up lots of grotty old code.
38 | # Andrew Plotkin (18 June 2017):
39 | # - Uploaded file details are logged to web-upload.log as well as being
40 | # emailed to webuploader@ifarchive.org.
41 | # Doug Orleans (22 Feb 2017):
42 | # - Added my name to the footer, to match the footer everywhere else.
43 | # Stephen Granade (3 May 2010):
44 | # - Added support for storing IDs from IFDB
45 | # - Filename-mangling code now allows spaces
46 | # Goob (10 July 2008):
47 | # - repointed mail to webuploader@ifarchive.org
48 | # Stephen Granade (27 March 2006):
49 | # - Added "Suggested Directory" to form
50 | # Stephen Granade (13 September 2004):
51 | # - Filename-mangling code had an error that only showed up when
52 | # given a filename from Internet Explorer
53 | # Stephen Granade (22 June 2004):
54 | # - Email now lists filename in the subject line
55 | # Further Stephen Granade edits (18 April 2004):
56 | # - Filename-mangling code now much more paranoid
57 | # Stephen Granade edits (28 Feb 2004):
58 | # - Form now includes uploader's name, email address, and file description
59 | # - Notification email has Reply-To: set to uploader's email
60 | # - Notification email includes uploader's name, email, and file desc
61 | # - Removed any mention of exact upload limits
62 | # - Overall look & feel closer to IF Archive standard
63 | # - Logs all uploads
64 | # - New uploads don't clobber old ones. Instead, a timestamp is
65 | # appended to the new upload's filename
66 | # - Upload errors are caught and logged
67 | # Hacked a bit for the ifarchive server by Goob, 2/25/04
68 |
69 | import sys
70 | import os
71 | import io
72 | import subprocess
73 | import cgi
74 | import configparser
75 | import string
76 | import logging
77 | import time
78 | import traceback
79 | import re
80 | import hashlib
81 | import sqlite3
82 |
83 | # First, some constants. Some of these are taken from a config file.
84 |
85 | configpath = '/var/ifarchive/lib/ifarch.config'
86 | config = configparser.ConfigParser()
87 | config.read(configpath)
88 |
89 | # Directory in which to find template files.
90 | dirLibFiles = "/var/ifarchive/lib/uploader"
91 |
92 | # Directory for upload; will be created if doesn't exist.
93 | dirUpload = "/var/ifarchive/incoming"
94 |
95 | # Logs will be written here. The file must be chown www-data.
96 | logfile = "/var/ifarchive/logs/web-upload.log"
97 |
98 | # Links to the main site use this domain.
99 | archivedomain = config['DEFAULT'].get('ArchiveDomain', '')
100 |
101 | # SQL database for upload information.
102 | dbFile = config['DEFAULT']['DBFile']
103 |
104 | # Maximum size of upload directory (in bytes) before no more files
105 | # are accepted.
106 | maxdirsize = config['DEFAULT'].getint('MaxIncomingDirSize')
107 |
108 | # Current size of upload directory (in bytes). Will compute before
109 | # running the form.
110 | totaldirsize = None
111 |
112 | # Where to email upload reports. (If None, don't.)
113 | email = config['Upload'].get('ReportEmail', None)
114 |
115 | # Mail-sending tool.
116 | sendmail = "/usr/sbin/sendmail"
117 |
118 | # Utility functions...
119 |
120 | def write_template(filename, map):
121 | """Read a template file from the lib directory, perform the
122 | substitutions in the map file, and print the result.
123 |
124 | This is a very simple substitution engine. Most of the Archive
125 | systems use Jinja, but this is a CGI script and I want to keep
126 | it simple.
127 | """
128 | text = get_template(filename)
129 |
130 | for (key, val) in map.items():
131 | key = '{'+key+'}'
132 | text = text.replace(key, val)
133 |
134 | print(text)
135 |
136 | def get_template(filename):
137 | """Read a template file from the lib directory and return its contents.
138 | """
139 | fl = open(os.path.join(dirLibFiles, filename), encoding='utf-8')
140 | text = fl.read()
141 | fl.close()
142 | # HACK: This would make more sense in Jinja.
143 | text = text.replace('{homedomain}', archivedomain)
144 | return text
145 |
146 | def plural(s,num):
147 | """
148 | Make plural words as nicely as possible.
149 | """
150 | if num != 1:
151 | if s[-1] == "s" or s[-1] == "x":
152 | s = s + "e"
153 | s = s + "s"
154 | return s
155 |
156 | def fix_line_endings(val):
157 | """
158 | Cheap attempt to repair DOS-style strings.
159 | """
160 | return val.replace('\r', '')
161 |
162 | def strip_dirs(fn):
163 | """Remove directory names from a path.
164 | """
165 | # Strip out the directory part of any filename (up to the last slash).
166 | # We consider both Windows and regular slashes here, although
167 | # only regular slashes should turn up.
168 | # (Old code also considered the colon, which was a path separator in
169 | # classic MacOS. I've dropped that.)
170 | _, _, fn = fn.rpartition('/');
171 | _, _, fn = fn.rpartition('\\');
172 | if not fn:
173 | fn = 'file'
174 | return fn
175 |
176 | def clean_filename(fn):
177 | """Clean a filename from the HTML form. We replace characters considered
178 | unsafe with underscores. Unsafe is control characters (0-31, 127-159),
179 | plus slashes and backslashes.
180 | """
181 | pat = re.compile('[\x00-\x1F\x7F-\x9F/\\\\]+')
182 | fn = pat.sub('_', fn)
183 | return fn
184 |
185 | def mailme(msg="", name="", nemail="", mailsubj="Upload Report"):
186 | """Quick and dirty, pipe a message to sendmail, appending
187 | various environmental variables to the message. Also log the
188 | same information.
189 | """
190 | headerlist = [ 'REQUEST_URI','HTTP_USER_AGENT','REMOTE_ADDR','HTTP_FROM','REMOTE_HOST','REMOTE_PORT','SERVER_SOFTWARE','HTTP_REFERER','REMOTE_IDENT','REMOTE_USER','QUERY_STRING','DATE_LOCAL' ]
191 |
192 | if email:
193 | try:
194 | fl = io.StringIO()
195 | fl.write("To: %s\n" % email)
196 | fl.write("From: %s\n" % email)
197 | fl.write("Subject: %s\n" % mailsubj)
198 | if (nemail != ""):
199 | tempstr = "<" + nemail + ">"
200 | if (name != ""):
201 | tempstr = name + " " + tempstr
202 | fl.write("Reply-To: %s\n" % tempstr)
203 | fl.write("\n")
204 | fl.write("%s\n" % msg)
205 | fl.write("---------------------------------------\n")
206 | for x in headerlist:
207 | if x in os.environ:
208 | fl.write("%s: %s\n" % (x, os.environ[x]))
209 | fl.write("---------------------------------------\n")
210 | bytemsg = fl.getvalue().encode('utf-8')
211 | fl.close()
212 | subprocess.run([sendmail, '-t'], input=bytemsg, check=True)
213 | except IOError:
214 | pass
215 |
216 | logger.info('Upload subject: %s' % (mailsubj,))
217 | logger.info('Upload message: %s' % (msg,))
218 | for x in headerlist:
219 | if x in os.environ:
220 | logger.info('Upload env: %s: %s' % (x, os.environ[x],))
221 |
222 | def errpage(message):
223 | """Print a generic error page.
224 | The message must be HTML-escaped and preferably wrapped with
225 | tags.
226 | """
227 | footer = get_template('footer.html')
228 | map = { 'errorparas':message, 'footer':footer }
229 | write_template('error.html', map)
230 |
231 | def form(data, posturl):
232 | """Print the main form. This includes the GET case (no files
233 | uploaded yet) and the POST case (form submitted with files).
234 | """
235 | footer = get_template('footer.html')
236 |
237 | if "file.1" not in data:
238 | # No files, show the primary form.
239 | if totaldirsize < maxdirsize:
240 | button = 'type="submit" value="Upload File"'
241 | else:
242 | button = 'type="button" value="Upload Disabled (upload directory is full)"'
243 |
244 | map = { 'footer':footer, 'posturl':posturl, 'button':button }
245 | write_template('main.html', map)
246 | return
247 |
248 | # We have uploads!
249 |
250 | nameval = data.getfirst('name')
251 | if not nameval:
252 | nameval = 'Anonymous'
253 | emailval = data.getfirst('email')
254 | if not emailval:
255 | emailval = '???'
256 |
257 | directoryval = data.getfirst('directory')
258 | ifdbID = data.getfirst('ifdbid')
259 | tuid = data.getfirst('tuid')
260 |
261 | aboutval = data.getfirst('filedesc')
262 |
263 | tosval = data.getfirst('tos')
264 | if not tosval:
265 | msg = """You must agree to the Terms of Use in order to upload files to the Archive."""
266 | errpage('
'+msg+'
')
267 | return
268 |
269 | rightsval = data.getfirst('rights')
270 | if not rightsval:
271 | msg = """Please select whichever of the "Right to use" options applies to your upload."""
272 | errpage(''+msg+'
')
273 | return
274 |
275 | if totaldirsize >= maxdirsize:
276 | # We don't publicize the maximum size.
277 | msg = """There are already too many files in the upload area, preventing your files from being uploaded. We apologize for the inconvenience."""
278 | errpage(''+msg+'
')
279 | msg += ("\n\nUploaded by %s <%s>\n\n" % (nameval, emailval,))
280 | fn = "???"
281 | key = "file.%s" % (1,)
282 | if key in data:
283 | fn = data[key].filename
284 | mailme(msg, nameval, emailval, "IFArchive incoming full: "+fn)
285 | return
286 |
287 | if not os.path.exists(dirUpload):
288 | os.mkdir(dirUpload, 0o777)
289 |
290 | # This code originally accepted multiple files in a single
291 | # form submission. The current form does not support this,
292 | # but we keep the old loop in case we ever put it back.
293 | fnList = []
294 | kbList = []
295 | tsList = []
296 | kbCount = 0
297 | f = 1
298 | while f:
299 | key = "file.%s" % f
300 | if key in data:
301 | fn = data[key].filename
302 | if not fn:
303 | f = f + 1
304 | continue
305 |
306 | content = data[key].value # bytes
307 |
308 | # Get the md5 hash of the file data.
309 | hashval = hashlib.md5(content).hexdigest()
310 |
311 | uploadtime = time.time()
312 |
313 | # Clean the filename. Strip off the dir part of the path, if
314 | # any (that's ofn); then clean out unsafe characters (that's fn).
315 | ofn = strip_dirs(fn)
316 | fn = clean_filename(fn)
317 |
318 | # For some reason, double quotes are encoded as %22, even though
319 | # nothing else gets percent-encoded like that.
320 | # We fix this, even though it means that a *literal* "%22" will
321 | # get munged.
322 | # (I suspect the real fix is to switch this script from
323 | # CGI to WSGI.)
324 | fn = fn.replace('%22', '"')
325 |
326 | # If the file already exists, add a timestamp to the new filename.
327 | if os.path.isfile(os.path.join(dirUpload, fn)):
328 | timestamp = "."+str(uploadtime)
329 | else:
330 | timestamp = ""
331 |
332 | # Try opening the file, exiting on error
333 | try:
334 | o = open(os.path.join(dirUpload, fn)+timestamp, "wb")
335 | o.write(content)
336 | o.close()
337 | except:
338 | logger.error('ERROR %s' % traceback.format_exc())
339 | errpage("""We were unable to process your uploaded file
340 | at this time.
341 | We apologize for the inconvenience, and ask that you try again later. If the
342 | problem persists, please contact the archive maintainers.
""")
343 | return
344 |
345 | if fn == ofn:
346 | logger.info('UPLOAD %s (%s)' % (fn+timestamp, remoteaddr))
347 | fnList.append(fn)
348 | else:
349 | logger.info('UPLOAD %s ORIGINAL NAME %s (%s)' % (fn+timestamp, ofn, remoteaddr))
350 | fnList.append('%s (originally %s)' % (fn, ofn))
351 |
352 | # Make sure the ifdbID and tuid are alnum only
353 | if ifdbID:
354 | if re.search('\W', ifdbID):
355 | logger.error("IFDB ID %s isn't alphanumeric" % ifdbID)
356 | if tuid:
357 | if re.search('\W', tuid):
358 | logger.error("TUID %s isn't alphanumeric" % tuid)
359 |
360 | try:
361 | db = sqlite3.connect(dbFile)
362 | db.isolation_level = None # autocommit
363 | curs = db.cursor()
364 | curs.execute('INSERT INTO uploads (uploadtime, md5, size, filename, origfilename, donorname, donoremail, donorip, donoruseragent, permission, suggestdir, ifdbid, tuid, about) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', (
365 | uploadtime,
366 | hashval,
367 | len(content),
368 | fn+timestamp, ofn,
369 | nameval, emailval,
370 | remoteaddr, browser,
371 | rightsval, directoryval,
372 | ifdbID, tuid,
373 | aboutval,
374 | ))
375 | del curs
376 | db.close()
377 | del db
378 | except:
379 | logger.error('SQL ERROR %s' % (traceback.format_exc(),))
380 |
381 |
382 | tsList.append(timestamp)
383 | kbList.append(len(content))
384 | kbCount = kbCount + len(content)
385 | f = f + 1
386 | else:
387 | f = 0
388 |
389 | if (not len(fnList)):
390 | errpage("""No files were received.
""")
391 | return
392 |
393 | fnamesForMailing = []
394 | htmlfiles = []
395 |
396 | htmlfiles.append('')
397 | for x in range(0, len(fnList)):
398 | htmlfiles.append("- %s (%i bytes)
" % (fnList[x],kbList[x]))
399 | htmlfiles.append('
')
400 | htmlfiles = '\n'.join(htmlfiles)
401 |
402 | msg = []
403 | msg.append("%s %s totalling %.2f kb uploaded successfully:\n\n" % (len(fnList),plural("file",len(fnList)),kbCount / 1024.0))
404 |
405 | for x in range(0, len(fnList)):
406 | msg.append(" * %s (%.2f kb)\n" % (fnList[x]+tsList[x],kbList[x] / 1024.0))
407 | fnamesForMailing.append(fnList[x]+tsList[x])
408 |
409 | msg.append("\nUploaded by %s <%s>\n\n" % (nameval, emailval,))
410 |
411 | if aboutval:
412 | msg.append(fix_line_endings(aboutval) + "\n")
413 | if directoryval:
414 | msg.append("Suggested directory: if-archive/%s\n" % (directoryval,))
415 | msg.append("Permission from: %s\n" % (rightsval,))
416 | if ifdbID:
417 | msg.append("IFDB ID: %s\n" % (ifdbID,))
418 | if tuid:
419 | msg.append("TUID: %s\n" % (tuid,))
420 | msg.append('\n\n')
421 |
422 | msg.append('https://admin.ifarchive.org/admin/incoming')
423 | msg.append('\n\n')
424 |
425 | msg = ''.join(msg)
426 | fnamesForMailing = ' '.join(fnamesForMailing)
427 |
428 | mailme(msg, nameval, emailval, "IFArchive Upload "+fnamesForMailing)
429 |
430 | map = { 'footer':footer, 'filenames':htmlfiles }
431 | write_template('accepted.html', map)
432 | return
433 |
434 |
435 | # Begin work.
436 |
437 | # This ensures that any exception will be nicely formatted.
438 | import cgitb
439 | cgitb.enable()
440 |
441 | # Send everything to stdout.
442 | sys.stderr = sys.stdout
443 |
444 | # Write the HTTP header.
445 | print("Content-Type: text/html; charset=utf-8")
446 | print()
447 |
448 | # Load a logger
449 | logger = logging.getLogger('upload')
450 | hdlr = logging.FileHandler(logfile)
451 | formatter = logging.Formatter('%(asctime)s %(message)s', '%d/%b/%Y:%H:%M:%S')
452 | hdlr.setFormatter(formatter)
453 | logger.addHandler(hdlr)
454 | logger.setLevel(logging.DEBUG)
455 |
456 | if "HTTP_USER_AGENT" in os.environ:
457 | browser = os.environ["HTTP_USER_AGENT"]
458 | else:
459 | browser = "No Known Browser"
460 |
461 | if "SCRIPT_NAME" in os.environ:
462 | posturl = os.environ["SCRIPT_NAME"]
463 | else:
464 | posturl = ""
465 |
466 | if "REMOTE_ADDR" in os.environ:
467 | remoteaddr = os.environ["REMOTE_ADDR"]
468 | else:
469 | remoteaddr = "?"
470 |
471 | # Figure out the total size (bytes) of files in the incoming directory.
472 | totaldirsize = 0
473 | for ent in os.scandir(dirUpload):
474 | if ent.is_file():
475 | totaldirsize += ent.stat().st_size
476 |
477 | data = cgi.FieldStorage()
478 | form(data, posturl)
479 |
480 | logging.shutdown()
481 |
482 |
483 |
--------------------------------------------------------------------------------