Skip to content
Snippets Groups Projects
upload.py 3.51 KiB
Newer Older
Bryan Tong Minh's avatar
Bryan Tong Minh committed
import random
from cStringIO import StringIO

class Upload(object):
	"""
	Base class for upload objects. This class should always be subclassed 
	by upload classes and its constructor always be called.
	
	Upload classes are file like object/iterators that have additional 
	variables length and content_type.
	"""
	
	BLOCK_SIZE = 8192
	def __init__(self, length, content_type):
		self.length = length
		self.content_type = content_type
		
	def __iter__(self):
		return self
	def next(self):
		data = self.read(self.BLOCK_SIZE)
		if data == '':
			raise StopIteration
		return data
		
	@staticmethod
	def encode(s):
		if type(s) is str:
			return s
		elif type(s) is unicode:
			return s.encode('utf-8')
		else:
			return s

class UploadRawData(Upload):
	"""
	This upload class is simply a wrapper around StringIO 
	"""
	def __init__(self, data, content_type = 'application/x-www-form-urlencoded'):
		self.fstr = StringIO(data)
		Upload.__init__(self, len(data), content_type)
	def read(self, length = -1):
		return self.fstr.read(length)
		
		
class UploadDict(UploadRawData):
	"""
	This class creates an x-www-form-urlencoded representation of a dict 
	and then passes it through its parent UploadRawData 
	"""
	def __init__(self, data):
		postdata = '&'.join('%s=%s' % (self.encode(i), self.encode(data[i])) for i in data)
		UploadRawData.__init__(self, postdata)
		
class UploadFile(Upload):
	"""
	This class accepts a file with information and a postdata dictionary
	and creates a multipart/form-data representation from it.
	"""
	STAGE_FILEHEADER = 0
	STAGE_FILE = 1
	STAGE_POSTDATA = 2
	STAGE_FOOTER = 3
	STAGE_DONE = 4
	def __init__(self, filefield, filename, filelength, file, data):
		self.stage = self.STAGE_FILEHEADER;
		self.boundary = self.generate_boundary()
		self.postdata = self.generate_multipart_from_dict(data)
		self.footer = '\r\n--%s--\r\n' % self.boundary
		self.fileheader = ('--%s\r\n' % self.boundary +
				'Content-Disposition: form-data; name="%s"; filename="%s"\r\n' %
					(self.encode(filefield), self.encode(filename)) +
				'Content-Type: application/octet-stream\r\n\r\n')
		self.file = file
		self.length_left = filelength
		self.str_data = None
		
		Upload.__init__(self, len(self.fileheader) + filelength + len(self.postdata) + len(self.footer) + 2,
			'multipart/form-data; boundary=' + self.boundary)
		
	def read(self, length):
		if self.stage == self.STAGE_DONE:
			return ''
		elif self.stage != self.STAGE_FILE:	
			if self.str_data is None:
				if self.stage == self.STAGE_FILEHEADER:
					self.str_data = StringIO(self.fileheader)
Bryan Tong Minh's avatar
Bryan Tong Minh committed
				elif self.stage == self.STAGE_POSTDATA:
					self.str_data = StringIO(self.postdata)
				elif self.stage == self.STAGE_FOOTER:
					self.str_data = StringIO(self.footer)
Bryan Tong Minh's avatar
Bryan Tong Minh committed
			data = self.str_data.read(length)
		else:
			if self.length_left:
				if length > self.length_left:
					length = self.length_left
				data = self.file.read(length)
				self.length_left -= len(data)
			else:
				self.stage += 1
				return '\r\n'
		
		if data == '':
			self.stage += 1
			self.str_data = None
			return self.read(length)
		return data

		
	@staticmethod
	def generate_boundary():
		return '----%s----' % ''.join((random.choice(
			'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') 
			for i in xrange(32)))
	
	def generate_multipart_from_dict(self, data):
		postdata = []
		for i in data:
			postdata.append('--' + self.boundary) 
			postdata.append('Content-Disposition: form-data; name="%s"' % self.encode(i))
			postdata.append('')
			postdata.append(self.encode(data[i]))
		return '\r\n'.join(postdata)