├── sample.config ├── lib ├── footer.html ├── error.html ├── accepted.html └── main.html ├── README.md ├── ifdbize.py └── upload.py /sample.config: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | 3 | # This file contains shared config info for upload.py and ifdbize.py. 4 | # It must be readable by www-data and admins. 5 | 6 | # Shelve path where IFDB IDs are stored (no longer used) 7 | # IFDBIdMapFile = /var/ifarchive/lib/ifids 8 | 9 | # SQLite database for admin tasks. 10 | DBFile = /var/ifarchive/lib/sql/admin.db 11 | 12 | # Max total size of files in the incoming directory 13 | MaxIncomingDirSize = 10000 14 | 15 | # This is a secret shared with IFDB 16 | IFDBCommitKey = XXXX 17 | -------------------------------------------------------------------------------- /lib/footer.html: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # upload.py -- CGI script that handles IF Archive file uploads 2 | 3 | - Copyright 2017-23 by the Interactive Fiction Technology Foundation 4 | - Distributed under the MIT license 5 | - Created by Andrew Plotkin 6 | 7 | This script runs the web form which accepts IF Archive file uploads. 8 | 9 | HTML pages are generated from the templates in the lib directory. The templating mechanism is as simple as it could be -- constant substring of the form "{tag}" are replaced by other constant strings. (This is much simpler than the templating mechanism in the ifarchive-ifmap-py repository.) 10 | 11 | (This repository was made public in 2023. It never really contained anything secret.) 12 | 13 | -------------------------------------------------------------------------------- /lib/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Upload Error 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 |
18 |

Upload Error

19 |
20 | 21 |
22 | 23 | {errorparas} 24 | 25 |
26 | 27 | {footer} 28 | 29 |
30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /lib/accepted.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Thank You For Your Upload 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 |
18 |

Thank You For Your Upload

19 |
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 | IF Archive File Upload 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 |
18 |

Upload to the IF Archive

19 |
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 |
48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 60 | 61 | 63 | 64 | 65 | 66 | 68 |
File: 
Your name: 
Your email address: 
About this file
(including version, license, etc.):
 
Right to use:  59 |
  62 |
Suggested directory:if‑archive/
Agreement:  67 |
69 | 70 |

71 | 72 |

73 | 74 |
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('') 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 | --------------------------------------------------------------------------------