#!/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 += '
%s | ' % ColumnSortLink('Name', order)
res += '%s | ' % ColumnSortLink('Size', order)
res += '%s | ' % ColumnSortLink('Date/Time', order, name='dt')
res += '%s | ' % ColumnSortLink('Ext', order)
res += '%s | ' % ColumnSortLink('MIME', order)
res += '%s | ' % ColumnSortLink('IP', order)
res += '|
' + str(i+1) + ' | '
res += '' + lnk + ' | \n'
res += '' + sz + ' | \n'
res += '' + dt + ' | \n'
res += '' + files[i]['ext'] + ' | \n'
res += '' + files[i]['mime'] + ' | \n'
res += '' + files[i]['ip'] + ' | \n'
res += '
Name "'+fn+'" is not allowed.
'
res += 'Please choose another name for the file.
A file named '+fn+\
' already exists.
'\
'Please choose another name and try again.
'+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'+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
'
# 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()