Commits

Svyatoslav Bulbakha  committed 013d1a9

Initial commit

  • Participants

Comments (0)

Files changed (336)

+syntax: glob
+
+dist
+build
+*.dat
+*.egg*
+*.pyc
+*.swp
+*.log
+*~
+#*#
+MANIFEST
+===========================
++         AUTHORS         +
+===========================
+Svyatoslav Bulbakha <mail@ssbb.me>
+
+
+===========================
++      CONTRIBUTORS       +
+===========================
+ContributeError: no have contributors
+Copyright (c) 2013, Svyatoslav Bulbakha
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met: 
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer. 
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution. 
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+The views and conclusions contained in the software and documentation are those
+of the authors and should not be interpreted as representing official policies, 
+either expressed or implied, of the django-cked Project.
+Django CKEd
+===========
+
+**CKEditor and elFinder integration for Django Framework.**
+
+Provides a ``RichTextField`` and ``CKEditorWidget`` with upload and
+browse support.
+
+Installation
+------------
+
+1. Install or add django-cked to your PYTHONPATH.
+2. Add ``cked`` to your ``INSTALLED_APPS`` setting.
+3. Add a ``CKED_UPLOAD_PATH`` to your proejct ``settings.py`` file.
+   Default: ``os.path.join(MEDIA_ROOT, 'uploads')``.
+4. Add CKEd URL include to your project ``urls.py`` file:
+   ``url(r'^cked/', include('cked.urls')),``
+
+Settings
+--------
+
+-  **CKED\_UPLOAD\_PATH**: Path to store your uploads. Default:
+   ``os.path.join(MEDIA_ROOT, 'uploads')``
+-  **CKEDITOR\_OPTIONS**: CKEditor config. See
+   `http://docs.ckeditor.com/#!/guide/dev\_configuration`_.
+-  **ELFINDER\_OPTIONS**: elFinder config. See
+   `https://github.com/Studio-42/elFinder/wiki/Client-configuration-options`_
+
+Usage
+-----
+
+Model field
+~~~~~~~~~~~
+
+::
+
+    from django.db import models
+    from cked.fields import RichTextField
+
+
+    class Entry(models.Model):
+        text = RichTextField()
+
+Widget
+~~~~~~
+
+::
+
+    from django import forms
+    from cked.widgets import CKEditorWidget
+
+    class MyForm(forms.Form):
+        text = forms.CharField(widget=CKEditorWidget)
+
+**NOTE**: If you are using custom forms, dont’r forget to include form
+media to your template: ``{{ form.media }}``.
+
+.. _`http://docs.ckeditor.com/#!/guide/dev\_configuration`: http://docs.ckeditor.com/#!/guide/dev_configuration
+.. _`https://github.com/Studio-42/elFinder/wiki/Client-configuration-options`: https://github.com/Studio-42/elFinder/wiki/Client-configuration-options

File cked/__init__.py

Empty file added.

File cked/default_settings.py

+ELFINDER_DEFAULT_OPTIONS = {
+}
+
+CKEDITOR_DEFAULT_OPTIONS = {
+    'skin': 'moono',
+    'toolbar': 'Full',
+    'filebrowserWindowWidth': 940,
+    'filebrowserWindowHeight': 450,
+}

File cked/elFinder.py

+#!/usr/bin/env python
+#
+# Connector for elFinder File Manager
+# author Troex Nevelin <troex@fury.scancode.ru>
+
+import hashlib
+import mimetypes
+import os
+import os.path
+import re
+import shutil
+import time
+from datetime import datetime
+
+class connector():
+	"""Connector for elFinder"""
+
+	_options = {
+		'root': '',
+		'URL': '',
+		'rootAlias': 'Home',
+		'dotFiles': False,
+		'dirSize': True,
+		'fileMode': 0644,
+		'dirMode': 0755,
+		'imgLib': 'auto',
+		'tmbDir': '.tmb',
+		'tmbAtOnce': 5,
+		'tmbSize': 48,
+		'fileURL': True,
+		'uploadMaxSize': 256,
+		'uploadWriteChunk': 8192,
+		'uploadAllow': [],
+		'uploadDeny': [],
+		'uploadOrder': ['deny', 'allow'],
+		# 'aclObj': None, # TODO
+		# 'aclRole': 'user', # TODO
+		'defaults': {
+			'read': True,
+			'write': True,
+			'rm': True
+		},
+		'perms': {},
+		'archiveMimes': {},
+		'archivers': {
+			'create': {},
+			'extract': {}
+		},
+		'disabled': [],
+		'debug': False
+	}
+
+	_commands = {
+		'open':	'__open',
+		'reload': '__reload',
+		'mkdir': '__mkdir',
+		'mkfile': '__mkfile',
+		'rename': '__rename',
+		'upload': '__upload',
+		'paste': '__paste',
+		'rm': '__rm',
+		'duplicate': '__duplicate',
+		'read': '__read',
+		'edit': '__edit',
+		'extract': '__extract',
+		'archive': '__archive',
+		'resize': '__resize',
+		'tmb': '__thumbnails',
+		'ping': '__ping'
+	}
+
+	_mimeType = {
+		# text
+		'txt': 'text/plain',
+		'conf': 'text/plain',
+		'ini': 'text/plain',
+		'php': 'text/x-php',
+		'html': 'text/html',
+		'htm': 'text/html',
+		'js' : 'text/javascript',
+		'css': 'text/css',
+		'rtf': 'text/rtf',
+		'rtfd': 'text/rtfd',
+		'py' : 'text/x-python',
+		'java': 'text/x-java-source',
+		'rb' : 'text/x-ruby',
+		'sh' : 'text/x-shellscript',
+		'pl' : 'text/x-perl',
+		'sql': 'text/x-sql',
+		# apps
+		'doc': 'application/msword',
+		'ogg': 'application/ogg',
+		'7z': 'application/x-7z-compressed',
+		# video
+		'ogm': 'appllication/ogm',
+		'mkv': 'video/x-matroska'
+	}
+
+	_time = 0
+	_request = {}
+	_response = {}
+	_errorData = {}
+	_form = {}
+	_im = None
+	_sp = None
+	_today = 0
+	_yesterday = 0
+
+	# public variables
+	httpAllowedParameters = ('cmd', 'target', 'targets[]', 'current', 'tree', 'name',
+		'content', 'src', 'dst', 'cut', 'init', 'type', 'width', 'height', 'upload[]')
+	# return variables
+	httpStatusCode = 0
+	httpHeader = {}
+	httpResponse = None
+
+	def __init__(self, opts):
+		for opt in opts:
+			self._options[opt] = opts.get(opt)
+
+		self._response['debug'] = {}
+		self._options['URL'] = self.__checkUtf8(self._options['URL'])
+		self._options['URL'] = self._options['URL'].rstrip('/')
+		self._options['root'] = self.__checkUtf8(self._options['root'])
+		self._options['root'] = self._options['root'].rstrip(os.sep)
+		self.__debug('URL', self._options['URL'])
+		self.__debug('root', self._options['root'])
+
+		for cmd in self._options['disabled']:
+			if cmd in self._commands:
+				del self._commands[cmd]
+
+		if self._options['tmbDir']:
+			self._options['tmbDir'] = os.path.join(self._options['root'], self._options['tmbDir'])
+			if not os.path.exists(self._options['tmbDir']):
+				self._options['tmbDir'] = False
+
+
+	def __reset(self):
+		"""Flush per request variables"""
+		self.httpStatusCode = 0
+		self.httpHeader = {}
+		self.httpResponse = None
+		self._request = {}
+		self._response = {}
+		self._errorData = {}
+		self._form = {}
+
+		self._time = time.time()
+		t = datetime.fromtimestamp(self._time)
+		self._today = time.mktime(datetime(t.year, t.month, t.day).timetuple())
+		self._yesterday = self._today - 86400
+
+		self._response['debug'] = {}
+
+
+	def run(self, httpRequest = []):
+		"""main function"""
+		self.__reset()
+		rootOk = True
+		if not os.path.exists(self._options['root']) or self._options['root'] == '':
+			rootOk = False
+			self._response['error'] = 'Invalid backend configuration'
+		elif not self.__isAllowed(self._options['root'], 'read'):
+			rootOk = False
+			self._response['error'] = 'Access denied'
+
+		for field in self.httpAllowedParameters:
+			if field in httpRequest:
+				self._request[field] = httpRequest[field]
+
+		if rootOk is True:
+			if 'cmd' in self._request:
+				if self._request['cmd'] in self._commands:
+					cmd = self._commands[self._request['cmd']]
+					func = getattr(self, '_' + self.__class__.__name__ + cmd, None)
+					if callable(func):
+						try:
+							func()
+						except Exception, e:
+							self._response['error'] = 'Command Failed'
+							self.__debug('exception', str(e))
+				else:
+					self._response['error'] = 'Unknown command'
+			else:
+				self.__open()
+
+			if 'init' in self._request:
+				self.__checkArchivers()
+				self._response['disabled'] = self._options['disabled']
+				if not self._options['fileURL']:
+					url = ''
+				else:
+					url = self._options['URL']
+				self._response['params'] = {
+					'dotFiles': self._options['dotFiles'],
+					'uplMaxSize': str(self._options['uploadMaxSize']) + 'M',
+					'archives': self._options['archivers']['create'].keys(),
+					'extract': self._options['archivers']['extract'].keys(),
+					'url': url
+				}
+
+		if self._errorData:
+			self._response['errorData'] = self._errorData
+
+		if self._options['debug']:
+			self.__debug('time', (time.time() - self._time))
+		else:
+			if 'debug' in self._response:
+				del self._response['debug']
+
+		if self.httpStatusCode < 100:
+			self.httpStatusCode = 200
+
+		if not 'Content-type' in self.httpHeader:
+			if ('cmd' in self._request and self._request['cmd'] == 'upload') or self._options['debug']:
+				self.httpHeader['Content-type'] = 'text/html'
+			else:
+				self.httpHeader['Content-type'] = 'application/json'
+
+		self.httpResponse = self._response
+
+		return self.httpStatusCode, self.httpHeader, self.httpResponse
+
+
+	def __open(self):
+		"""Open file or directory"""
+		# try to open file
+		if 'current' in self._request:
+			curDir = self.__findDir(self._request['current'], None)
+			curFile = self.__find(self._request['target'], curDir)
+
+			if not curDir or not curFile or os.path.isdir(curFile):
+				self.httpStatusCode = 404
+				self.httpHeader['Content-type'] = 'text/html'
+				self.httpResponse = 'File not found'
+				return
+			if not self.__isAllowed(curDir, 'read') or not self.__isAllowed(curFile, 'read'):
+				self.httpStatusCode = 403
+				self.httpHeader['Content-type'] = 'text/html'
+				self.httpResponse = 'Access denied'
+				return
+
+			if os.path.islink(curFile):
+				curFile = self.__readlink(curFile)
+				if not curFile or os.path.isdir(curFile):
+					self.httpStatusCode = 404
+					self.httpHeader['Content-type'] = 'text/html'
+					self.httpResponse = 'File not found'
+					return
+				if (
+					not self.__isAllowed(os.path.dirname(curFile), 'read')
+					or not self.__isAllowed(curFile, 'read')
+					):
+					self.httpStatusCode = 403
+					self.httpHeader['Content-type'] = 'text/html'
+					self.httpResponse = 'Access denied'
+					return
+
+			mime = self.__mimetype(curFile)
+			parts = mime.split('/', 2)
+			if parts[0] == 'image': disp = 'image'
+			elif parts[0] == 'text': disp = 'inline'
+			else: disp = 'attachments'
+
+			self.httpStatusCode = 200
+			self.httpHeader['Content-type'] = mime
+			self.httpHeader['Content-Disposition'] = disp + '; filename=' + os.path.basename(curFile)
+			self.httpHeader['Content-Location'] = curFile.replace(self._options['root'], '')
+			self.httpHeader['Content-Transfer-Encoding'] = 'binary'
+			self.httpHeader['Content-Length'] = str(os.lstat(curFile).st_size)
+			self.httpHeader['Connection'] = 'close'
+			self._response['file'] = open(curFile, 'r')
+			return
+		# try dir
+		else:
+			path = self._options['root']
+
+			if 'target' in self._request and self._request['target']:
+				target = self.__findDir(self._request['target'], None)
+				if not target:
+					self._response['error'] = 'Invalid parameters'
+				elif not self.__isAllowed(target, 'read'):
+					self._response['error'] = 'Access denied'
+				else:
+					path = target
+
+			self.__content(path, 'tree' in self._request)
+		pass
+
+
+	def __rename(self):
+		"""Rename file or dir"""
+		current = name = target = None
+		curDir = curName = newName = None
+		if 'name' in self._request and 'current' in self._request and 'target' in self._request:
+			name = self._request['name']
+			current = self._request['current']
+			target = self._request['target']
+			curDir = self.__findDir(current, None)
+			curName = self.__find(target, curDir)
+			newName = os.path.join(curDir, name)
+
+		if not curDir or not curName:
+			self._response['error'] = 'File not found'
+		elif not self.__isAllowed(curDir, 'write') and self.__isAllowed(curName, 'rm'):
+			self._response['error'] = 'Access denied'
+		elif not self.__checkName(name):
+			self._response['error'] = 'Invalid name'
+		elif os.path.exists(newName):
+			self._response['error'] = 'File or folder with the same name already exists'
+		else:
+			self.__rmTmb(curName)
+			try:
+				os.rename(curName, newName)
+				self._response['select'] = [self.__hash(newName)]
+				self.__content(curDir, os.path.isdir(newName))
+			except:
+				self._response['error'] = 'Unable to rename file'
+
+
+	def __mkdir(self):
+		"""Create new directory"""
+		current = None
+		path = None
+		newDir = None
+		if 'name' in self._request and 'current' in self._request:
+			name = self._request['name']
+			current = self._request['current']
+			path = self.__findDir(current, None)
+			newDir = os.path.join(path, name)
+
+		if not path:
+			self._response['error'] = 'Invalid parameters'
+		elif not self.__isAllowed(path, 'write'):
+			self._response['error'] = 'Access denied'
+		elif not self.__checkName(name):
+			self._response['error'] = 'Invalid name'
+		elif os.path.exists(newDir):
+			self._response['error'] = 'File or folder with the same name already exists'
+		else:
+			try:
+				os.mkdir(newDir, int(self._options['dirMode']))
+				self._response['select'] = [self.__hash(newDir)]
+				self.__content(path, True)
+			except:
+				self._response['error'] = 'Unable to create folder'
+
+
+	def __mkfile(self):
+		"""Create new file"""
+		name = current = None
+		curDir = newFile = None
+		if 'name' in self._request and 'current' in self._request:
+			name = self._request['name']
+			current = self._request['current']
+			curDir = self.__findDir(current, None)
+			newFile = os.path.join(curDir, name)
+
+		if not curDir or not name:
+			self._response['error'] = 'Invalid parameters'
+		elif not self.__isAllowed(curDir, 'write'):
+			self._response['error'] = 'Access denied'
+		elif not self.__checkName(name):
+			self._response['error'] = 'Invalid name'
+		elif os.path.exists(newFile):
+			self._response['error'] = 'File or folder with the same name already exists'
+		else:
+			try:
+				open(newFile, 'w').close()
+				self._response['select'] = [self.__hash(newFile)]
+				self.__content(curDir, False)
+			except:
+				self._response['error'] = 'Unable to create file'
+
+
+	def __rm(self):
+		"""Delete files and directories"""
+		current = rmList = None
+		curDir = rmFile = None
+		if 'current' in self._request and 'targets[]' in self._request:
+			current = self._request['current']
+			rmList = self._request['targets[]']
+			curDir = self.__findDir(current, None)
+
+		if not rmList or not curDir:
+			self._response['error'] = 'Invalid parameters'
+			return False
+
+		if not isinstance(rmList, list):
+			rmList = [rmList]
+
+		for rm in rmList:
+			rmFile = self.__find(rm, curDir)
+			if not rmFile: continue
+			self.__remove(rmFile)
+		# TODO if errorData not empty return error
+		self.__content(curDir, True)
+
+
+	def __upload(self):
+		"""Upload files"""
+		try: # Windows needs stdio set for binary mode.
+			import msvcrt
+			msvcrt.setmode (0, os.O_BINARY) # stdin  = 0
+			msvcrt.setmode (1, os.O_BINARY) # stdout = 1
+		except ImportError:
+			pass
+
+		if 'current' in self._request:
+			curDir = self.__findDir(self._request['current'], None)
+			if not curDir:
+				self._response['error'] = 'Invalid parameters'
+				return
+			if not self.__isAllowed(curDir, 'write'):
+				self._response['error'] = 'Access denied'
+				return
+			if not 'upload[]' in self._request:
+				self._response['error'] = 'No file to upload'
+				return
+
+			upFiles = self._request['upload[]']
+			# invalid format
+			# must be dict('filename1': 'filedescriptor1', 'filename2': 'filedescriptor2', ...)
+			if not isinstance(upFiles, dict):
+				self._response['error'] = 'Invalid parameters'
+				return
+
+			self._response['select'] = []
+			total = 0
+			upSize = 0
+			maxSize = self._options['uploadMaxSize'] * 1024 * 1024
+			for name, data in upFiles.iteritems():
+				if name:
+					total += 1
+					name = os.path.basename(name)
+					if not self.__checkName(name):
+						self.__errorData(name, 'Invalid name')
+					else:
+						name = os.path.join(curDir, name)
+						try:
+							f = open(name, 'wb', self._options['uploadWriteChunk'])
+							for chunk in self.__fbuffer(data):
+								f.write(chunk)
+							f.close()
+							upSize += os.lstat(name).st_size
+							if self.__isUploadAllow(name):
+								os.chmod(name, self._options['fileMode'])
+								self._response['select'].append(self.__hash(name))
+							else:
+								self.__errorData(name, 'Not allowed file type')
+								try:
+									os.unlink(name)
+								except:
+									pass
+						except:
+							self.__errorData(name, 'Unable to save uploaded file')
+						if upSize > maxSize:
+							try:
+								os.unlink(name)
+								self.__errorData(name, 'File exceeds the maximum allowed filesize')
+							except:
+								pass
+								# TODO ?
+								self.__errorData(name, 'File was only partially uploaded')
+							break
+
+			if self._errorData:
+				if len(self._errorData) == total:
+					self._response['error'] = 'Unable to upload files'
+				else:
+					self._response['error'] = 'Some files was not uploaded'
+
+			self.__content(curDir, False)
+			return
+
+
+	def __paste(self):
+		"""Copy or cut files/directories"""
+		if 'current' in self._request and 'src' in self._request and 'dst' in self._request:
+			curDir = self.__findDir(self._request['current'], None)
+			src = self.__findDir(self._request['src'], None)
+			dst = self.__findDir(self._request['dst'], None)
+			if not curDir or not src or not dst or not 'targets[]' in self._request:
+				self._response['error'] = 'Invalid parameters'
+				return
+			files = self._request['targets[]']
+			if not isinstance(files, list):
+				files = [files]
+
+			cut = False
+			if 'cut' in self._request:
+				if self._request['cut'] == '1':
+					cut = True
+
+			if not self.__isAllowed(src, 'read') or not self.__isAllowed(dst, 'write'):
+				self._response['error'] = 'Access denied'
+				return
+
+			for fhash in files:
+				f = self.__find(fhash, src)
+				if not f:
+					self._response['error'] = 'File not found'
+					return
+				newDst = os.path.join(dst, os.path.basename(f))
+				if dst.find(f) == 0:
+					self._response['error'] = 'Unable to copy into itself'
+					return
+
+				if cut:
+					if not self.__isAllowed(f, 'rm'):
+						self._response['error'] = 'Move failed'
+						self._errorData(f, 'Access denied')
+						self.__content(curDir, True)
+						return
+					# TODO thumbs
+					if os.path.exists(newDst):
+						self._response['error'] = 'Unable to move files'
+						self._errorData(f, 'File or folder with the same name already exists')
+						self.__content(curDir, True)
+						return
+					try:
+						os.rename(f, newDst)
+						self.__rmTmb(f)
+						continue
+					except:
+						self._response['error'] = 'Unable to move files'
+						self._errorData(f, 'Unable to move')
+						self.__content(curDir, True)
+						return
+				else:
+					if not self.__copy(f, newDst):
+						self._response['error'] = 'Unable to copy files'
+						self.__content(curDir, True)
+						return
+					continue
+			self.__content(curDir, True)
+		else:
+			self._response['error'] = 'Invalid parameters'
+
+		return
+
+
+	def __duplicate(self):
+		"""Create copy of files/directories"""
+		if 'current' in self._request and 'target' in self._request:
+			curDir = self.__findDir(self._request['current'], None)
+			target = self.__find(self._request['target'], curDir)
+			if not curDir or not target:
+				self._response['error'] = 'Invalid parameters'
+				return
+			if not self.__isAllowed(target, 'read') or not self.__isAllowed(curDir, 'write'):
+				self._response['error'] = 'Access denied'
+			newName = self.__uniqueName(target)
+			if not self.__copy(target, newName):
+				self._response['error'] = 'Unable to create file copy'
+				return
+
+		self.__content(curDir, True)
+		return
+
+
+	def __resize(self):
+		"""Scale image size"""
+		if not (
+			'current' in self._request and 'target' in self._request
+			and 'width' in self._request and 'height' in self._request
+			):
+			self._response['error'] = 'Invalid parameters'
+			return
+
+		width = int(self._request['width'])
+		height = int(self._request['height'])
+		curDir = self.__findDir(self._request['current'], None)
+		curFile = self.__find(self._request['target'], curDir)
+
+		if width < 1 or height < 1 or not curDir or not curFile:
+			self._response['error'] = 'Invalid parameters'
+			return
+		if not self.__isAllowed(curFile, 'write'):
+			self._response['error'] = 'Access denied'
+			return
+		if not self.__mimetype(curFile).find('image') == 0:
+			self._response['error'] = 'File is not an image'
+			return
+
+		self.__debug('resize ' + curFile, str(width) + ':' + str(height))
+		self.__initImgLib()
+		try:
+			im = self._im.open(curFile)
+			imResized = im.resize((width, height), self._im.ANTIALIAS)
+			imResized.save(curFile)
+		except Exception, e:
+			self.__debug('resizeFailed_' + path, str(e))
+			self._response['error'] = 'Unable to resize image'
+			return
+
+		self._response['select'] = [self.__hash(curFile)]
+		self.__content(curDir, False)
+		return
+
+
+	def __thumbnails(self):
+		"""Create previews for images"""
+		if 'current' in self._request:
+			curDir = self.__findDir(self._request['current'], None)
+			if not curDir or curDir == self._options['tmbDir']:
+				return False
+		else:
+			return False
+
+		self.__initImgLib()
+		if self.__canCreateTmb():
+			if self._options['tmbAtOnce'] > 0:
+				tmbMax = self._options['tmbAtOnce']
+			else:
+				tmbMax = 5
+			self._response['current'] = self.__hash(curDir)
+			self._response['images'] = {}
+			i = 0
+			for f in os.listdir(curDir):
+				path = os.path.join(curDir, f)
+				fhash = self.__hash(path)
+				if self.__canCreateTmb(path) and self.__isAllowed(path, 'read'):
+					tmb = os.path.join(self._options['tmbDir'], fhash + '.png')
+					if not os.path.exists(tmb):
+						if self.__tmb(path, tmb):
+							self._response['images'].update({
+								fhash: self.__path2url(tmb)
+							})
+							i += 1
+				if i >= tmbMax:
+					self._response['tmb'] = True
+					break
+		else:
+			return False
+
+		return
+
+
+	def __content(self, path, tree):
+		"""CWD + CDC + maybe(TREE)"""
+		self.__cwd(path)
+		self.__cdc(path)
+
+		if tree:
+			self._response['tree'] = self.__tree(self._options['root'])
+
+
+	def __cwd(self, path):
+		"""Current Working Directory"""
+		name = os.path.basename(path)
+		if path == self._options['root']:
+			name = self._options['rootAlias']
+			root = True
+		else:
+			root = False
+
+		if self._options['rootAlias']:
+			basename = self._options['rootAlias']
+		else:
+			basename = os.path.basename(self._options['root'])
+
+		rel = basename + path[len(self._options['root']):]
+
+		self._response['cwd'] = {
+			'hash': self.__hash(path),
+			'name': self.__checkUtf8(name),
+			'mime': 'directory',
+			'rel': self.__checkUtf8(rel),
+			'size': 0,
+			'date': datetime.fromtimestamp(os.stat(path).st_mtime).strftime("%d %b %Y %H:%M"),
+			'read': True,
+			'write': self.__isAllowed(path, 'write'),
+			'rm': not root and self.__isAllowed(path, 'rm')
+		}
+
+
+	def __cdc(self, path):
+		"""Current Directory Content"""
+		files = []
+		dirs = []
+
+		for f in sorted(os.listdir(path)):
+			if not self.__isAccepted(f): continue
+			pf = os.path.join(path, f)
+			info = {}
+			info = self.__info(pf)
+			info['hash'] = self.__hash(pf)
+			if info['mime'] == 'directory':
+				dirs.append(info)
+			else:
+				files.append(info)
+
+		dirs.extend(files)
+		self._response['cdc'] = dirs
+
+
+	def __info(self, path):
+		mime = ''
+		filetype = 'file'
+		if os.path.isfile(path): filetype = 'file'
+		if os.path.isdir(path): filetype = 'dir'
+		if os.path.islink(path): filetype = 'link'
+
+		stat = os.lstat(path)
+		statDate = datetime.fromtimestamp(stat.st_mtime)
+
+		fdate = ''
+		if stat.st_mtime >= self._today:
+			fdate = 'Today ' + statDate.strftime("%H:%M")
+		elif stat.st_mtime >= self._yesterday and stat.st_mtime < self._today:
+			fdate = 'Yesterday ' + statDate.strftime("%H:%M")
+		else:
+			fdate = statDate.strftime("%d %b %Y %H:%M")
+
+		info = {
+			'name': self.__checkUtf8(os.path.basename(path)),
+			'hash': self.__hash(path),
+			'mime': 'directory' if filetype == 'dir' else self.__mimetype(path),
+			'date': fdate,
+			'size': self.__dirSize(path) if filetype == 'dir' else stat.st_size,
+			'read': self.__isAllowed(path, 'read'),
+			'write': self.__isAllowed(path, 'write'),
+			'rm': self.__isAllowed(path, 'rm')
+		}
+
+		if filetype == 'link':
+			lpath = self.__readlink(path)
+			if not lpath:
+				info['mime'] = 'symlink-broken'
+				return info
+
+			if os.path.isdir(lpath):
+				info['mime'] = 'directory'
+			else:
+				info['parent'] = self.__hash(os.path.dirname(lpath))
+				info['mime'] = self.__mimetype(lpath)
+
+			if self._options['rootAlias']:
+				basename = self._options['rootAlias']
+			else:
+				basename = os.path.basename(self._options['root'])
+
+			info['link'] = self.__hash(lpath)
+			info['linkTo'] = basename + lpath[len(self._options['root']):]
+			info['read'] = info['read'] and self.__isAllowed(lpath, 'read')
+			info['write'] = info['write'] and self.__isAllowed(lpath, 'write')
+			info['rm'] = self.__isAllowed(lpath, 'rm')
+		else:
+			lpath = False
+
+		if not info['mime'] == 'directory':
+			if self._options['fileURL'] and info['read'] is True:
+				if lpath:
+					info['url'] = self.__path2url(lpath)
+				else:
+					info['url'] = self.__path2url(path)
+			if info['mime'][0:5] == 'image':
+				if self.__canCreateTmb():
+					dim = self.__getImgSize(path)
+					if dim:
+						info['dim'] = dim
+						info['resize'] = True
+
+					# if we are in tmb dir, files are thumbs itself
+					if os.path.dirname(path) == self._options['tmbDir']:
+						info['tmb'] = self.__path2url(path)
+						return info
+
+					tmb = os.path.join(self._options['tmbDir'], info['hash'] + '.png')
+
+					if os.path.exists(tmb):
+						tmbUrl = self.__path2url(tmb)
+						info['tmb'] = tmbUrl
+					else:
+						self._response['tmb'] = True
+
+		return info
+
+
+	def __tree(self, path):
+		"""Return directory tree starting from path"""
+
+		if not os.path.isdir(path): return ''
+		if os.path.islink(path): return ''
+
+		if path == self._options['root'] and self._options['rootAlias']:
+			name = self._options['rootAlias']
+		else:
+			name = os.path.basename(path)
+		tree = {
+			'hash': self.__hash(path),
+			'name': self.__checkUtf8(name),
+			'read': self.__isAllowed(path, 'read'),
+			'write': self.__isAllowed(path, 'write'),
+			'dirs': []
+		}
+
+		if self.__isAllowed(path, 'read'):
+			for d in sorted(os.listdir(path)):
+				pd = os.path.join(path, d)
+				if os.path.isdir(pd) and not os.path.islink(pd) and self.__isAccepted(d):
+					tree['dirs'].append(self.__tree(pd))
+
+		return tree
+
+
+	def __uniqueName(self, path, copy = ' copy'):
+		"""Generate unique name for file copied file"""
+		curDir = os.path.dirname(path)
+		curName = os.path.basename(path)
+		lastDot = curName.rfind('.')
+		ext = newName = ''
+
+		if not os.path.isdir(path) and re.search(r'\..{3}\.(gz|bz|bz2)$', curName):
+			pos = -7
+			if curName[-1:] == '2':
+				pos -= 1
+			ext = curName[pos:]
+			oldName = curName[0:pos]
+			newName = oldName + copy
+		elif os.path.isdir(path) or lastDot <= 0:
+			oldName = curName
+			newName = oldName + copy
+			pass
+		else:
+			ext = curName[lastDot:]
+			oldName = curName[0:lastDot]
+			newName = oldName + copy
+
+		pos = 0
+
+		if oldName[-len(copy):] == copy:
+			newName = oldName
+		elif re.search(r'' + copy +'\s\d+$', oldName):
+			pos = oldName.rfind(copy) + len(copy)
+			newName = oldName[0:pos]
+		else:
+			newPath = os.path.join(curDir, newName + ext)
+			if not os.path.exists(newPath):
+				return newPath
+
+		# if we are here then copy already exists or making copy of copy
+		# we will make new indexed copy *black magic*
+		idx = 1
+		if pos > 0: idx = int(oldName[pos:])
+		while True:
+			idx += 1
+			newNameExt = newName + ' ' + str(idx) + ext
+			newPath = os.path.join(curDir, newNameExt)
+			if not os.path.exists(newPath):
+				return newPath
+			# if idx >= 1000: break # possible loop
+
+		return
+
+
+	def __remove(self, target):
+		"""Internal remove procedure"""
+		if not self.__isAllowed(target, 'rm'):
+			self.__errorData(target, 'Access denied')
+
+		if not os.path.isdir(target):
+			try:
+				os.unlink(target)
+				return True
+			except:
+				self.__errorData(target, 'Remove failed')
+				return False
+		else:
+			for i in os.listdir(target):
+				if self.__isAccepted(i):
+					self.__remove(os.path.join(target, i))
+
+			try:
+				os.rmdir(target)
+				return True
+			except:
+				self.__errorData(target, 'Remove failed')
+				return False
+		pass
+
+
+	def __copy(self, src, dst):
+		"""Internal copy procedure"""
+		dstDir = os.path.dirname(dst)
+		if not self.__isAllowed(src, 'read'):
+			self.__errorData(src, 'Access denied')
+			return False
+		if not self.__isAllowed(dstDir, 'write'):
+			self.__errorData(dstDir, 'Access denied')
+			return False
+		if os.path.exists(dst):
+			self.__errorData(dst, 'File or folder with the same name already exists')
+			return False
+
+		if not os.path.isdir(src):
+			try:
+				shutil.copyfile(src, dst)
+				shutil.copymode(src, dst)
+				return True
+			except:
+				self.__errorData(src, 'Unable to copy files')
+				return False
+		else:
+			try:
+				os.mkdir(dst)
+				shutil.copymode(src, dst)
+			except:
+				self.__errorData(src, 'Unable to copy files')
+				return False
+
+			for i in os.listdir(src):
+				newSrc = os.path.join(src, i)
+				newDst = os.path.join(dst, i)
+				if not self.__copy(newSrc, newDst):
+					self.__errorData(newSrc, 'Unable to copy files')
+					return False
+
+		return True
+
+
+	def __checkName(self, name):
+		"""Check for valid file/dir name"""
+		pattern = r'[\/\\\:\<\>]'
+		if re.search(pattern, name):
+			return False
+		return True
+
+
+	def __findDir(self, fhash, path):
+		"""Find directory by hash"""
+		fhash = str(fhash)
+		if not path:
+			path = self._options['root']
+			if fhash == self.__hash(path):
+				return path
+
+		if not os.path.isdir(path):
+			return None
+
+		for d in os.listdir(path):
+			pd = os.path.join(path, d)
+			if os.path.isdir(pd) and not os.path.islink(pd):
+				if fhash == self.__hash(pd):
+					return pd
+				else:
+					ret = self.__findDir(fhash, pd)
+					if ret:
+						return ret
+
+		return None
+
+
+	def __find(self, fhash, parent):
+		"""Find file/dir by hash"""
+		fhash = str(fhash)
+		if os.path.isdir(parent):
+			for i in os.listdir(parent):
+				path = os.path.join(parent, i)
+				if fhash == self.__hash(path):
+					return path
+		return None
+
+
+	def __read(self):
+		if 'current' in self._request and 'target' in self._request:
+			curDir = self.__findDir(self._request['current'], None)
+			curFile = self.__find(self._request['target'], curDir)
+			if curDir and curFile:
+				if self.__isAllowed(curFile, 'read'):
+					self._response['content'] = open(curFile, 'r').read()
+				else:
+					self._response['error'] = 'Access denied'
+				return
+
+		self._response['error'] = 'Invalid parameters'
+		return
+
+
+	def __edit(self):
+		"""Save content in file"""
+		error = ''
+		if 'current' in self._request and 'target' in self._request and 'content' in self._request:
+			curDir = self.__findDir(self._request['current'], None)
+			curFile = self.__find(self._request['target'], curDir)
+			error = curFile
+			if curFile and curDir:
+				if self.__isAllowed(curFile, 'write'):
+					try:
+						f = open(curFile, 'w+')
+						f.write(self._request['content'])
+						f.close()
+						self._response['target'] = self.__info(curFile)
+					except:
+						self._response['error'] = 'Unable to write to file'
+				else:
+					self._response['error'] = 'Access denied'
+			return
+
+		self._response['error'] = 'Invalid parameters'
+		return
+
+
+	def __archive(self):
+		"""Compress files/directories to archive"""
+		self.__checkArchivers()
+
+		if (
+			not self._options['archivers']['create']
+			or not 'type' in self._request
+			or not 'current' in self._request
+			or not 'targets[]' in self._request
+			):
+			self._response['error'] = 'Invalid parameters'
+			return
+
+		curDir = self.__findDir(self._request['current'], None)
+		archiveType = self._request['type']
+		if (
+			not archiveType in self._options['archivers']['create']
+			or not archiveType in self._options['archiveMimes']
+			or not curDir
+			or not self.__isAllowed(curDir, 'write')
+			):
+			self._response['error'] = 'Unable to create archive'
+			return
+
+		files = self._request['targets[]']
+		if not isinstance(files, list):
+			files = [files]
+
+		realFiles = []
+		for fhash in files:
+			curFile = self.__find(fhash, curDir)
+			if not curFile:
+				self._response['error'] = 'File not found'
+				return
+			realFiles.append(os.path.basename(curFile))
+
+		arc = self._options['archivers']['create'][archiveType]
+		if len(realFiles) > 1:
+			archiveName = 'Archive'
+		else:
+			archiveName = realFiles[0]
+		archiveName += '.' + arc['ext']
+		archiveName = self.__uniqueName(archiveName, '')
+		archivePath = os.path.join(curDir, archiveName)
+
+		cmd = [arc['cmd']]
+		for a in arc['argc'].split():
+			cmd.append(a)
+		cmd.append(archiveName)
+		for f in realFiles:
+			cmd.append(f)
+
+		curCwd = os.getcwd()
+		os.chdir(curDir)
+		self.__runSubProcess(cmd)
+		os.chdir(curCwd)
+
+		if os.path.exists(archivePath):
+			self.__content(curDir, False)
+			self._response['select'] = [self.__hash(archivePath)]
+		else:
+			self._response['error'] = 'Unable to create archive'
+
+		return
+
+
+	def __extract(self):
+		"""Uncompress archive"""
+		if not 'current' in self._request or not 'target' in self._request:
+			self._response['error'] = 'Invalid parameters'
+			return
+
+		curDir = self.__findDir(self._request['current'], None)
+		curFile = self.__find(self._request['target'], curDir)
+		mime = self.__mimetype(curFile)
+		self.__checkArchivers()
+
+		if (
+			not mime in self._options['archivers']['extract']
+			or not curDir
+			or not curFile
+			or not self.__isAllowed(curDir, 'write')
+			):
+			self._response['error'] = 'Invalid parameters'
+			return
+
+		arc = self._options['archivers']['extract'][mime]
+
+		cmd = [arc['cmd']]
+		for a in arc['argc'].split():
+			cmd.append(a)
+		cmd.append(os.path.basename(curFile))
+
+		curCwd = os.getcwd()
+		os.chdir(curDir)
+		ret = self.__runSubProcess(cmd)
+		os.chdir(curCwd)
+
+		if ret:
+			self.__content(curDir, True)
+		else:
+			self._response['error'] = 'Unable to extract files from archive'
+
+		return
+
+
+	def __ping(self):
+		"""Workaround for Safari"""
+		self.httpStatusCode = 200
+		self.httpHeader['Connection'] = 'close'
+		return
+
+
+	def __mimetype(self, path):
+		"""Detect mimetype of file"""
+		mime = mimetypes.guess_type(path)[0] or 'unknown'
+		ext = path[path.rfind('.') + 1:]
+
+		if mime == 'unknown' and ('.' + ext) in mimetypes.types_map:
+			mime = mimetypes.types_map['.' + ext]
+
+		if mime == 'text/plain' and ext == 'pl':
+			mime = self._mimeType[ext]
+
+		if mime == 'application/vnd.ms-office' and ext == 'doc':
+			mime = self._mimeType[ext]
+
+		if mime == 'unknown':
+			if os.path.basename(path) in ['README', 'ChangeLog']:
+				mime = 'text/plain'
+			else:
+				if ext in self._mimeType:
+					mime = self._mimeType[ext]
+
+		# self.__debug('mime ' + os.path.basename(path), ext + ' ' + mime)
+		return mime
+
+
+	def __tmb(self, path, tmb):
+		"""Internal thumbnail create procedure"""
+		try:
+			im = self._im.open(path).copy()
+			size = self._options['tmbSize'], self._options['tmbSize']
+			box = self.__cropTuple(im.size)
+			if box:
+				im = im.crop(box)
+			im.thumbnail(size, self._im.ANTIALIAS)
+			im.save(tmb, 'PNG')
+		except Exception, e:
+			self.__debug('tmbFailed_' + path, str(e))
+			return False
+		return True
+
+
+	def __rmTmb(self, path):
+		tmb = self.__tmbPath(path)
+		if self._options['tmbDir']:
+			if os.path.exists(tmb):
+				try:
+					os.unlink(tmb)
+				except:
+					pass
+
+
+	def __cropTuple(self, size):
+		w, h = size
+		if w > h: # landscape
+			l = int((w - h) / 2)
+			u = 0
+			r = l + h
+			d = h
+			return (l, u, r, d)
+		elif h > w: # portrait
+			l = 0
+			u = int((h - w) / 2)
+			r = w
+			d = u + w
+			return (l, u, r, d)
+		else: # cube
+			pass
+
+		return False
+
+
+	def __readlink(self, path):
+		"""Read link and return real path if not broken"""
+		target = os.readlink(path);
+		if not target[0] == '/':
+			target = os.path.join(os.path.dirname(path), target)
+		target = os.path.normpath(target)
+		if os.path.exists(target):
+			if not target.find(self._options['root']) == -1:
+				return target
+		return False
+
+
+	def __dirSize(self, path):
+		total_size = 0
+		if self._options['dirSize']:
+			for dirpath, dirnames, filenames in os.walk(path):
+				for f in filenames:
+					fp = os.path.join(dirpath, f)
+					if os.path.exists(fp):
+						total_size += os.stat(fp).st_size
+		else:
+			total_size = os.lstat(path).st_size
+		return total_size
+
+
+	def __fbuffer(self, f, chunk_size = _options['uploadWriteChunk']):
+		while True:
+			chunk = f.read(chunk_size)
+			if not chunk: break
+			yield chunk
+
+
+	def __canCreateTmb(self, path = None):
+		if self._options['imgLib'] and self._options['tmbDir']:
+			if path is not None:
+				mime = self.__mimetype(path)
+				if not mime[0:5] == 'image':
+					return False
+			return True
+		else:
+			return False
+
+
+	def __tmbPath(self, path):
+		tmb = False
+		if self._options['tmbDir']:
+			if not os.path.dirname(path) == self._options['tmbDir']:
+				tmb = os.path.join(self._options['tmbDir'], self.__hash(path) + '.png')
+		return tmb
+
+
+	def __isUploadAllow(self, name):
+		allow = False
+		deny = False
+		mime = self.__mimetype(name)
+
+		if 'all' in self._options['uploadAllow']:
+			allow = True
+		else:
+			for a in self._options['uploadAllow']:
+				if mime.find(a) == 0:
+					allow = True
+
+		if 'all' in self._options['uploadDeny']:
+			deny = True
+		else:
+			for d in self._options['uploadDeny']:
+				if mime.find(d) == 0:
+					deny = True
+
+		if self._options['uploadOrder'][0] == 'allow': # ,deny
+			if deny is True:
+				return False
+			elif allow is True:
+				return True
+			else:
+				return False
+		else: # deny,allow
+			if allow is True:
+				return True
+			elif deny is True:
+				return False
+			else:
+				return True
+
+
+	def __isAccepted(self, target):
+		if target == '.' or target == '..':
+			return False
+		if target[0:1] == '.' and not self._options['dotFiles']:
+			return False
+		return True
+
+
+	def __isAllowed(self, path, access):
+		if not os.path.exists(path):
+			return False
+
+		if access == 'read':
+			if not os.access(path, os.R_OK):
+				self.__errorData(path, access)
+				return False
+		elif access == 'write':
+			if not os.access(path, os.W_OK):
+				self.__errorData(path, access)
+				return False
+		elif access == 'rm':
+			if not os.access(os.path.dirname(path), os.W_OK):
+				self.__errorData(path, access)
+				return False
+		else:
+			return False
+
+		path = path[len(os.path.normpath(self._options['root'])):]
+		for ppath in self._options['perms']:
+			regex = r'' + ppath
+			if re.search(regex, path) and access in self._options['perms'][ppath]:
+				return self._options['perms'][ppath][access]
+
+		return self._options['defaults'][access]
+
+
+	def __hash(self, path):
+		"""Hash of the path"""
+		m = hashlib.md5()
+		m.update(path)
+		return str(m.hexdigest())
+
+
+	def __path2url(self, path):
+		curDir = path
+		length = len(self._options['root'])
+		url = self.__checkUtf8(self._options['URL'] + curDir[length:]).replace(os.sep, '/')
+
+		try:
+			import urllib
+			url = urllib.quote(url, '/:~')
+		except:
+			pass
+		return url
+
+
+	def __errorData(self, path, msg):
+		"""Collect error/warning messages"""
+		self._errorData[path] = msg
+
+
+	def __initImgLib(self):
+		if not self._options['imgLib'] is False and self._im is None:
+			try:
+				import Image
+				Image
+				self._im = Image
+				self._options['imgLib'] = 'PIL'
+			except:
+				self._options['imgLib'] = False
+				self._im = False
+
+		self.__debug('imgLib', self._options['imgLib'])
+		return self._options['imgLib']
+
+
+	def __getImgSize(self, path):
+		self.__initImgLib();
+		if self.__canCreateTmb():
+			try:
+				im = self._im.open(path)
+				return str(im.size[0]) + 'x' + str(im.size[1])
+			except:
+				pass
+
+		return False
+
+
+	def __debug(self, k, v):
+		if self._options['debug']:
+			self._response['debug'].update({k: v})
+		return
+
+
+	def __checkArchivers(self):
+		# import subprocess
+		# sp = subprocess.Popen(['tar', '--version'], shell = False,
+		# stdout = subprocess.PIPE, stderr=subprocess.PIPE)
+		# out, err = sp.communicate()
+		# print 'out:', out, '\nerr:', err, '\n'
+		archive = { 'create': {}, 'extract': {} }
+
+		if 'archive' in self._options['disabled'] and 'extract' in self._options['disabled']:
+			self._options['archiveMimes'] = []
+			self._options['archivers'] = archive
+			return
+
+		tar = self.__runSubProcess(['tar', '--version'])
+		gzip = self.__runSubProcess(['gzip', '--version'])
+		bzip2 = self.__runSubProcess(['bzip2', '--version'])
+		zipc = self.__runSubProcess(['zip', '--version'])
+		unzip = self.__runSubProcess(['unzip', '--help'])
+		rar = self.__runSubProcess(['rar', '--version'], validReturn = [0, 7])
+		unrar = self.__runSubProcess(['unrar'], validReturn = [0, 7])
+		p7z = self.__runSubProcess(['7z', '--help'])
+		p7za = self.__runSubProcess(['7za', '--help'])
+		p7zr = self.__runSubProcess(['7zr', '--help'])
+
+		# tar = False
+		# tar = gzip = bzip2 = zipc = unzip = rar = unrar = False
+		# print tar, gzip, bzip2, zipc, unzip, rar, unrar, p7z, p7za, p7zr
+
+		c = archive['create']
+		e = archive['extract']
+
+		if tar:
+			mime = 'application/x-tar'
+			c.update({mime: {'cmd': 'tar', 'argc': '-cf', 'ext': 'tar'}})
+			e.update({mime: {'cmd': 'tar', 'argc': '-xf', 'ext': 'tar'}})
+
+		if tar and gzip:
+			mime = 'application/x-gzip'
+			c.update({mime: {'cmd': 'tar', 'argc': '-czf', 'ext': 'tar.gz'}})
+			e.update({mime: {'cmd': 'tar', 'argc': '-xzf', 'ext': 'tar.gz'}})
+
+		if tar and bzip2:
+			mime = 'application/x-bzip2'
+			c.update({mime: {'cmd': 'tar', 'argc': '-cjf', 'ext': 'tar.bz2'}})
+			e.update({mime: {'cmd': 'tar', 'argc': '-xjf', 'ext': 'tar.bz2'}})
+
+		mime = 'application/zip'
+		if zipc:
+			c.update({mime: {'cmd': 'zip', 'argc': '-r9', 'ext': 'zip'}})
+		if unzip:
+			e.update({mime: {'cmd': 'unzip', 'argc': '', 'ext': 'zip'}})
+
+		mime = 'application/x-rar'
+		if rar:
+			c.update({mime: {'cmd': 'rar', 'argc': 'a -inul', 'ext': 'rar'}})
+			e.update({mime: {'cmd': 'rar', 'argc': 'x -y', 'ext': 'rar'}})
+		elif unrar:
+			e.update({mime: {'cmd': 'unrar', 'argc': 'x -y', 'ext': 'rar'}})
+
+		p7zip = None
+		if p7z:
+			p7zip = '7z'
+		elif p7za:
+			p7zip = '7za'
+		elif p7zr:
+			p7zip = '7zr'
+
+		if p7zip:
+			mime = 'application/x-7z-compressed'
+			c.update({mime: {'cmd': p7zip, 'argc': 'a -t7z', 'ext': '7z'}})
+			e.update({mime: {'cmd': p7zip, 'argc': 'e -y', 'ext': '7z'}})
+
+			mime = 'application/x-tar'
+			if not mime in c:
+				c.update({mime: {'cmd': p7zip, 'argc': 'a -ttar', 'ext': 'tar'}})
+			if not mime in e:
+				e.update({mime: {'cmd': p7zip, 'argc': 'e -y', 'ext': 'tar'}})
+
+			mime = 'application/x-gzip'
+			if not mime in c:
+				c.update({mime: {'cmd': p7zip, 'argc': 'a -tgzip', 'ext': 'gz'}})
+			if not mime in e:
+				e.update({mime: {'cmd': p7zip, 'argc': 'e -y', 'ext': 'tar.gz'}})
+
+			mime = 'application/x-bzip2'
+			if not mime in c:
+				c.update({mime: {'cmd': p7zip, 'argc': 'a -tbzip2', 'ext': 'bz2'}})
+			if not mime in e:
+				e.update({mime: {'cmd': p7zip, 'argc': 'e -y', 'ext': 'tar.bz2'}})
+
+			mime = 'application/zip'
+			if not mime in c:
+				c.update({mime: {'cmd': p7zip, 'argc': 'a -tzip', 'ext': 'zip'}})
+			if not mime in e:
+				e.update({mime: {'cmd': p7zip, 'argc': 'e -y', 'ext': 'zip'}})
+
+		if not self._options['archiveMimes']:
+			self._options['archiveMimes'] = c.keys()
+		else:
+			pass
+
+		self._options['archivers'] = archive
+		pass
+
+
+	def __runSubProcess(self, cmd, validReturn = [0]):
+		if self._sp is None:
+			import subprocess
+			self._sp = subprocess
+
+		try:
+			sp = self._sp.Popen(cmd, shell = False, stdout = self._sp.PIPE, stderr = self._sp.PIPE, stdin = self._sp.PIPE)
+			out, err = sp.communicate('')
+			ret = sp.returncode
+			# print cmd, ret, out, err
+		except:
+			return False
+
+		if not ret in validReturn:
+			return False
+
+		return True
+
+
+	def __checkUtf8(self, name):
+		try:
+			name.decode('utf-8')
+		except UnicodeDecodeError:
+			name = unicode(name, 'utf-8', 'replace')
+			self.__debug('invalid encoding', name)
+			#name += ' (invalid encoding)'
+		return name
+

File cked/fields.py

+from django.db import models
+from django import forms
+
+from cked.widgets import CKEditorWidget
+
+
+class RichTextField(models.TextField):
+    def __init__(self, *args, **kwargs):
+        super(RichTextField, self).__init__(*args, **kwargs)
+
+    def formfield(self, **kwargs):
+        defaults = {
+            'form_class': RichTextFormField,
+        }
+        defaults.update(kwargs)
+        return super(RichTextField, self).formfield(**defaults)
+
+
+class RichTextFormField(forms.fields.Field):
+    def __init__(self, *args, **kwargs):
+        kwargs.update({'widget': CKEditorWidget()})
+        super(RichTextFormField, self).__init__(*args, **kwargs)
+
+# Fix field for South
+try:
+    from south.modelsinspector import add_introspection_rules
+    add_introspection_rules([], ["^cked\.fields\.RichTextField"])
+except:
+    pass

File cked/models.py

Empty file added.

File cked/static/cked/ckeditor/CHANGES.md

+CKEditor 4 Changelog
+====================
+
+## CKEditor 4.0.1
+
+Fixed issues:
+
+* [#9655](http://dev.ckeditor.com/ticket/9655): Support for IE Quirks Mode in new Moono skin.
+* Accessibility issues (mainly on inline editor): [#9364](http://dev.ckeditor.com/ticket/9364), [#9368](http://dev.ckeditor.com/ticket/9368), [#9369](http://dev.ckeditor.com/ticket/9369), [#9370](http://dev.ckeditor.com/ticket/9370), [#9541](http://dev.ckeditor.com/ticket/9541), [#9543](http://dev.ckeditor.com/ticket/9543), [#9841](http://dev.ckeditor.com/ticket/9841), [#9844](http://dev.ckeditor.com/ticket/9844).
+* Magic-line:
+    * [#9481](http://dev.ckeditor.com/ticket/9481): Added accessibility support for Magic-line.
+    * [#9509](http://dev.ckeditor.com/ticket/9509): Added Magic-line support for forms.
+    * [#9573](http://dev.ckeditor.com/ticket/9573): Magic-line doesn't disappear on `mouseout` in the specific case.
+* [#9754](http://dev.ckeditor.com/ticket/9754): [Webkit] Cut & paste simple unformatted text generates inline wrapper in Webkits.
+* [#9456](http://dev.ckeditor.com/ticket/9456): [Chrome] Properly paste bullet list style from MS-Word.
+* [#9699](http://dev.ckeditor.com/ticket/9699), [#9758](http://dev.ckeditor.com/ticket/9758): Improved selection locking when selecting by dragging.
+* Context menu:
+    * [#9712](http://dev.ckeditor.com/ticket/9712): Context menu open destroys editor focus.
+    * [#9366](http://dev.ckeditor.com/ticket/9366): Context menu should be displayed over floating toolbar.
+    * [#9706](http://dev.ckeditor.com/ticket/9706): Context menu generates JS error in inline mode when editor attached to header element.
+* [#9800](http://dev.ckeditor.com/ticket/9800): Hide float panel when resizing window.
+* [#9721](http://dev.ckeditor.com/ticket/9721): Padding in content of div based editor puts editing area under bottom UI space.
+* [#9528](http://dev.ckeditor.com/ticket/9528): Host page's `box-sizing` style shouldn't influence editor UI elements.
+* [#9503](http://dev.ckeditor.com/ticket/9503): Forms plugin adds context menu listeners only on supported input types. Added support for `tel, email, search` and `url` input types.
+* [#9769](http://dev.ckeditor.com/ticket/9769): Improved floating toolbar positioning in narrow window.
+* [#9875](http://dev.ckeditor.com/ticket/9875): Table dialog doesn't populate width correctly.
+* [#8675](http://dev.ckeditor.com/ticket/8675): Deleting cells in nested table removes outer table cell.
+* [#9815](http://dev.ckeditor.com/ticket/9815): Can't edit dialog fields on editor initialized in jQuery UI modal dialog.