#!/usr/bin/python # # PyUp - Python CGI Upload Script # By Isam M. http://biodegradablegeek.com/ # September 21st 2004 # # This hideous arrangement of bytes was a # direct port from a CGI script I wrote in C # # This script actually gained a lot of momentum # back when ImageShack and RapidShare didn't exist. # I had to make it private because the bandwidth usage # was lagging me in Counter-Strike. # # TODO Filter results # TODO can not have upload list on the same page.. # TODO Arrow to show desc/asc sort display # TODO ability to browse more than one file at a time # TODO Read settings from config file # import os import re import glob import time import sys import string import cgi import cgitb; cgitb.enable(); import urllib ############################################################################### # Modify these to fit your needs. 'True' and 'False' are case sensitive. ############################################################################### # Used to link to the local files HOST='http://do.main.me/' # Uploaded files will go here. Server needs write permission. UPLOADDIR='../upload/files' # After a successful upload, a link to the file will be displayed. # LINK/filename_of_the_uploaded_file LINK=HOST+'upload/files/' # Ban file, has a list of the IPS to ban, ascii file, ip on each line BANFILE='../upload/bans' # Log files (Server needs write permission) LOG_ACCESS='../upload/logs/access.log' LOG_ERROR='../uploadlogs/error.log' # Templates TEMPLATE_BANNED='../upload/templates/banned.html' TEMPLATE_MAIN='../upload/templates/main.html' TEMPLATE_FORM='../upload/templates/form.html' TEMPLATE_FORM_UPFIELD='../upload/templates/uploadfield.html' # Turning logging off is not r_tttmended. LOG=True # Upload file size limit in bytes. MAXSZ=1024*1024*15 # Chunk of file read and written to disk at a time. CHUNKSZ=1024*1024 # If False, uploaded files are not publicly displayed. DISPLAYLOCALFILES=True # The field name in the HTML code, you need a %d in it. FIELDFMT='file_%d_' RENAMEFMT='as_%d' def StripPath(filename): """Strip the path from file and return filename alone.""" # Some OSs do not use / in their file system, Windows # for example uses \, so let's convert all to / if not filename: return '' tmp=re.sub(r'(%(2f|2F|5c|5C|3a|3A))|\\|:', '/', filename) x=string.split(tmp, '/') return x[len(x)-1] def FileExtension(filename): dot=filename.rfind('.') if (-1==dot): return None # The user can embed HTML as the file extension. return cgi.escape(filename[dot+1:].lower()) def FormatFileSize(sz): """Return filesize in readable human form.""" # Bytes if 1024>sz: ret=str(sz) return ret # Kilobytes if ((1024*1024)>sz): kb=float(sz/1024.0) ret=str(round(kb,1))+'K' return ret # Megabytes if ((1024*1024*1024)>sz): mb=float((sz/1024.0)/1024.0) ret=str(round(mb,1))+'M' else: # Gigabytes gb=float(((sz/1024.0)/1024.0)/1024) ret=str(round(gb,1))+'G' return ret class Time: def __init__(self): self.seconds=0 self.minutes=0 self.hours=0 def __str__(self): # Omit seconds m=str(self.minutes) if len(m)==1: m = '0' + m return str(self.hours) + ':' \ + m def TimeToSeconds(t): minutes = t.hours * 60 + t.minutes seconds = minutes * 60 + t.seconds return seconds def SecondsToTime(s): time = Time() time.hours = s/3600 s = s- time.hours * 3600 time.minutes = s/60 s = s- time.minutes * 60 time.s = s return time def LogLine(logfile, line): try: fp=file(logfile, 'a') entry=time.strftime('[%m/%d/%y %I:%M:%S %p] ') entry+=GetVisitorIP()+': '+line fp.write(entry+'\n') fp.close() except: # Try to log to error file, if we can not (bad) just leave try: fp=file(LOG_ERROR, 'a') entry=time.strftime('[%m/%d/%y %I:%M:%S %p] ') entry+=': Log entry "' + line + '" to file "' + logfile + '"' fp.write(entry+'\n') fp.close() except: return def LogAccess(line): LogLine(LOG_ACCESS, line) def LogUpload(remotefile, uploadedfile): x='Uploaded ' + remotefile if remotefile != uploadedfile: x += ' as ' + uploadedfile LogLine(LOG_ACCESS, x) def LogError(error): x='Error: ' + error LogLine(LOG_ERROR, x) def LinkToLocalFile(file, short=True, maxlen=-1, quote=False): """Return html link code for file. short, if true will return the link in a short form, file as opposed to long form, which is: http://asdf/file maxlen is used only when short is True, maxlen is the of chars to display for file, if the file is too long, it returns the link with 3 dots appended to the end to show that it continues on. longfilebl... -1 means turn that off and show all of it """ if quote: file=urllib.quote(file) if short: if -1==maxlen: a=file else: a=file[0:maxlen] if (len(file)-len(a)) >= 3: a+='...' else: a=LINK+file link='' return link + cgi.escape(a) + '' def ColumnSortLink(col, order, name=None): # Col if not name: name=col.lower() script = os.path.basename(sys.argv[0]) return '%s' \ % (script, name, order, col) def SortDictionary(lidic, column, order): """ This is very bad.. bubble sort to sort a list of dictionaries by a specific key. """ for a in range(0, len(lidic)-1): for b in range(a, len(lidic)): if order=='asc': if lidic[a][column] > lidic[b][column]: lidic[a], lidic[b] = lidic[b], lidic[a] else: if lidic[a][column] < lidic[b][column]: lidic[a], lidic[b] = lidic[b], lidic[a] return lidic def ColColor(column, x): if column==x: return '#cccccc' return '#ffffff' def GetLocalFiles(form): """Print a table of the files in the upload directory.""" # Let us see how the files are to be sorted accepted_columns = ['name', 'size', 'dt', 'mime', 'ext', 'ip'] if form.has_key('sort') and \ form['sort'].value and \ form['sort'].value.lower() in accepted_columns: column=form['sort'].value.lower() else: column='dt' if form.has_key('order') and \ form['order'].value and \ form['order'].value.lower() in ['asc', 'desc']: order=form['order'].value.lower() else: order='asc' # We have a 'database' file that lists information # about each uploaded file. # filename, type, date uploaded, IP of uploader # We want to open up this file now, if it doesn't # exist, either something is broken or more likely # this is the first time this script is being run. try: db=file('../upload/files.db', 'r') # File is delimeted by \ fd={} files=[] f=db.readline() while f: # Ignore lines that begin with a # if f != '\n' and f.lstrip()[0]!='#': d=string.split(f, ' / ') fd['name']=d[0] fd['size']=int(d[1]) fd['dt']=d[2] fd['mime']=d[3] fd['ip']=d[4][:-1] # Ends with \n fd['ext']=FileExtension(d[0]) files.append(fd.copy()) f=db.readline() db.close() if files==[]: return '' except IOError: # File doesn't exist? Let's create an empty one try: # Try to create the file. db=file('../upload/files.db', 'w') db.close() return '' except IOError: LogError('Could not open/create local list of uploaded files! Check permissions.') return '' res = '' res += '\n' if order=='asc': order='desc' else: order='asc' res += '' % ColumnSortLink('Name', order) res += '' % ColumnSortLink('Size', order) res += '' % ColumnSortLink('Date/Time', order, name='dt') res += '' % ColumnSortLink('Ext', order) res += '' % ColumnSortLink('MIME', order) res += '' % ColumnSortLink('IP', order) res += '' sorted=SortDictionary(files, column, order) gray = True for i in range(len(sorted)): lnk=LinkToLocalFile(files[i]['name'], maxlen=35) sz=FormatFileSize(files[i]['size']) dt=files[i]['dt'] if not files[i]['ext']: files[i]['ext']='N/A' if gray: res += '' gray=False; else: res += '' gray=True # res += '' res += '\n' res += '\n' res += '\n' res += '\n' res += '\n' res += '\n' res += '' res += '

%s

%s

%s

%s

%s

%s

' + str(i+1) + '

' + lnk + '

' + sz + '

' + dt + '

' + files[i]['ext'] + '

' + files[i]['mime'] + '

' + files[i]['ip'] + '

' return res def GetVisitorIP(): return os.getenv('REMOTE_ADDR', 'get.ip.failed') def HandleUpload(form, NumOfUploads): """Save from form to file dir.""" # Let us see if the user DID click the upload button. if not form.has_key('upform'): return '' res='' for i in range(NumOfUploads): field=FIELDFMT % (i) if not form.has_key(field): # This is probably the user's first time on the page. continue else: fs=form[field] if not fs.value or not fs.filename: continue # Check if user wants to save this locally with a different name. renameto=RENAMEFMT % (i) if form.has_key(renameto) and ''!=form[renameto].value: fn=form[renameto].value else: fn=StripPath(fs.filename) # Check if the name is allowed. if '/' in fn or \ '\\' in fn or \ '.htaccess'==fn or \ 'robots.txt'==fn or \ fn.startswith('index.'): res += '

Name "'+fn+'" is not allowed.
' res += 'Please choose another name for the file.

' continue # At this point, we have the absolute path to the local file. pfn=os.path.join(UPLOADDIR, fn) # Check if file exists already. try: tmp=file(pfn, 'rb') tmp.close() res += '

A file named '+fn+\ ' already exists.
'\ 'Please choose another name and try again.

' if LOG: LogAccess('Tried uploaded file "' + fn + '" but it already exists') except IOError: # We probably caught a file not found exception. # Meaning we are good to go. try: f=file(pfn, 'wb') except IOError: res += '

'+cgi.escape(fn)+' could not be' \ 'uploaded.

' evidence.append('Upload of ' + fn + 'failed') continue # Write file to local disk. tb=0 while 1: if tb>MAXSZ: res += '

Max file size limit exceeded (' \ +FormatFileSize(MAXSZ) + ')

' evidence.append('Exceeded file limit with file: ' + fn) os.remove(pfn) return res br=fs.file.read(CHUNKSZ) if not br: break tb+=len(br) f.write(br) f.close() if LOG: LogUpload(fs.filename, fn) res += '

Uploaded ' + cgi.escape(fn) + \ ' successfully.

' # res += '

ID of file:

%d
' % (fileid) res += '

'+LinkToLocalFile(fn, short=False, quote=True) res += '

' # Upload was successful, let's add this file to the db now. try: f=file('../upload/files.db', 'a') dt=time.strftime('%D %I:%M %p') tp=fs.type # " / " is our delimeter in the file, we can't # have it in the type else it will ruin EVERYTHING. tp=tp.replace(' / ', '/') if len(tp) > 25: tp = tp[0:25] + '...' line=fn + ' / ' \ + str(tb) + ' / ' \ + dt + ' / ' \ + tp + ' / ' \ + GetVisitorIP() f.write(line + '\n') f.close() except: res += 'File could not be added to file list' res += 'You will not be able to
see the file' res += 'on the file list but you can still access it' res += '
using the URL but tell the admin ' res += 'what happened.' return res return res def GetNumFields(form): """Return # of upload fields we should display.""" # Did user click 'update' to update the number of fields? n=0 if form.has_key('numfields'): n=form['numfields'].value else: # Guess not, let's see if we have a previous number. if form.has_key('numfieldshidden'): n=form['numfieldshidden'].value try: n = int(n) if 0 < n < 100: return n except: pass return 3 def GetTemplate(template): """Open and return a template file""" try: f = file(template) tmp = f.read() f.close() return tmp except: return None def GetUploadForm(NumOfUpFields): """Generate the html upload form and return.""" raw = GetTemplate(TEMPLATE_FORM) if not raw: LogError('Could not open template file "' + TEMPLATE_FORM + '"') PrintErrorPage('Could not open form template, check permissions') raw = raw.replace('%FORM_SCRIPT_NAME%', os.path.basename(sys.argv[0])) raw = raw.replace('%FORM_NUMUP%', str(NumOfUpFields)) upfieldtemp = GetTemplate(TEMPLATE_FORM_UPFIELD) if not upfieldtemp: LogError('Could not open template file "' + TEMPLATE_FORM + '"') PrintErrorPage('Could not open form upfield template, check permissions') upfieldtemp = upfieldtemp.replace('%FORM_UPFIELDNAME%', 'file_%d_') upfieldtemp = upfieldtemp.replace('%FORM_UPLOADAS%', 'as_%d') upfieldtemp = upfieldtemp.replace('%FORM_ARROW%', 'id_%d') upfieldtemp = upfieldtemp.replace('%FORM_OPTIONAL_TABLE%', 'optional_' + 'id_%d') for n in range(NumOfUpFields): tmp = upfieldtemp % (n, n, n, n) raw = raw.replace('%FORM_UPFIELDS%', tmp + '%FORM_UPFIELDS%') raw = raw.replace('%FORM_UPFIELDS%', '') return raw def ParseTemplate(raw, Replace): """Take the raw template, replace some variables in it and return.""" if Replace.has_key('%MAIN_RESULT%'): raw = raw.replace('%MAIN_RESULT%', Replace['%MAIN_RESULT%']) if Replace.has_key('%LOCAL_FILES%'): raw = raw.replace('%LOCAL_FILES%', Replace['%LOCAL_FILES%']) if Replace.has_key('%UPLOAD_FORM%'): raw = raw.replace('%UPLOAD_FORM%', Replace['%UPLOAD_FORM%']) if Replace.has_key('%MAX_SIZE%'): raw = raw.replace('%MAX_SIZE%', Replace['%MAX_SIZE%']) return raw def PrintErrorPage(error, MIME='Content-type: text/plain\n'): print MIME print error sys.exit(0) def ParsePrintTemplate(template, NumOfUpFields=None, MainResult='', LocalFiles='', MIME='Content-type: text/html\n'): """Parse and print a template file.""" res = GetTemplate(template) # TODO LOG ERROR if not res: PrintErrorPage('Could not open main template, check permissions') replace = {} replace['%MAIN_RESULT%'] = MainResult replace['%LOCAL_FILES%'] = LocalFiles replace['%UPLOAD_FORM%'] = GetUploadForm(NumOfUpFields) replace['%MAX_SIZE%'] = FormatFileSize(MAXSZ) res = ParseTemplate(res, replace) print MIME print res sys.exit(0) def main(): # ------------------------------------------------ # print 'Content-type: text/html\n' # print '' # sys.exit(0) # ------------------------------------------------ form=cgi.FieldStorage() numupfields = GetNumFields(form) result = HandleUpload(form, numupfields) if DISPLAYLOCALFILES: ParsePrintTemplate(TEMPLATE_MAIN, numupfields, result, GetLocalFiles(form)) else: ParsePrintTemplate(TEMPLATE_MAIN, numupfields, result) def PrintHtmlFile(HtmlFile, MIME='Content-type: text/html\n'): """Print passed file to screen.""" res = GetTemplate(HtmlFile) if res: print MIME print res else: LogError('Could not open template file "' + TEMPLATE_FORM + '"') PrintErrorPage('You are banned.') def CheckIP(): """Checks if this ip is on the ban list, if so - do something accordingly.""" try: user=GetVisitorIP() bf=file(BANFILE, 'r') for ip in bf.xreadlines(): if ip[:-1]==user: LogAccess('Banned user tried to access page') sys.exit(0) bf.close() except: if sys.exc_type == SystemExit: PrintHtmlFile(TEMPLATE_BANNED) # Send this user the banned template sys.exit(0) # Something happened while reading bans file.. # Let us log it and move on. LogError('Error checking bans files "' + BANFILE + '"') if __name__=='__main__': CheckIP() main()