Index: /FCKeditor/trunk/_whatsnew.html
===================================================================
--- /FCKeditor/trunk/_whatsnew.html	(revision 590)
+++ /FCKeditor/trunk/_whatsnew.html	(revision 591)
@@ -55,4 +55,8 @@
 		<li>[<a target="_blank" href="http://dev.fckeditor.net/ticket/688">#688</a>] Now the Perl quick upload is 
 			available.</li>
+		<li>[<a target="_blank" href="http://dev.fckeditor.net/ticket/575">#575</a>] The Python connector has been 
+			rewritten as a WSGI app to be fully compatible with the latest python frameworks and servers. The 
+			QuickUpload feature has been added as well as all the features available in the PHP connector. 
+			Thanks to Mariano Reingart.</li>
 		<li>[<a target="_blank" href="http://dev.fckeditor.net/ticket/561">#561</a>] The asp connector provides an
 			AbsolutePath setting so it's possible to set the url to a full domain or a relative path and specify that
Index: /FCKeditor/trunk/editor/filemanager/connectors/py/config.py
===================================================================
--- /FCKeditor/trunk/editor/filemanager/connectors/py/config.py	(revision 591)
+++ /FCKeditor/trunk/editor/filemanager/connectors/py/config.py	(revision 591)
@@ -0,0 +1,135 @@
+#!/usr/bin/env python
+"""
+ * FCKeditor - The text editor for Internet - http://www.fckeditor.net
+ * Copyright (C) 2003-2007 Frederico Caldeira Knabben
+ *
+ * == BEGIN LICENSE ==
+ *
+ * Licensed under the terms of any of the following licenses at your
+ * choice:
+ *
+ *  - GNU General Public License Version 2 or later (the "GPL")
+ *    http://www.gnu.org/licenses/gpl.html
+ *
+ *  - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+ *    http://www.gnu.org/licenses/lgpl.html
+ *
+ *  - Mozilla Public License Version 1.1 or later (the "MPL")
+ *    http://www.mozilla.org/MPL/MPL-1.1.html
+ *
+ * == END LICENSE ==
+ *
+ * Configuration file for the File Manager Connector for Python 
+"""
+
+# INSTALLATION NOTE: You must set up your server enviroment accordingly to run 
+# python scripts. This connector requires Python 2.4 or greater.
+# 
+# Supported operation modes: 
+#  * WSGI (recommended): You'll need apache + mod_python + modpython_gateway 
+#                        or any web server capable of the WSGI python standard
+#  * Plain Old CGI:      Any server capable of running standartd python scripts
+#                        (although mod_python is recommended for performance)
+#                        This was the previous connector version operation mode
+#
+# If you're using Apache web server, replace the htaccess.txt to to .htaccess, 
+# and set the proper options and paths.
+# For WSGI and mod_python, you may need to download modpython_gateway from:
+# http://projects.amor.org/misc/svn/modpython_gateway.py and copy it in this 
+# directory.
+
+   
+# SECURITY: You must explicitelly enable this "connector". (Set it to "True").
+# WARNING: don't just set "ConfigIsEnabled = True", you must be sure that only 
+#		authenticated users can access this file or use some kind of session checking.
+Enabled = True
+
+# Path to user files relative to the document root.
+UserFilesPath = '/userfiles/' 
+
+# Fill the following value it you prefer to specify the absolute path for the
+# user files directory. Usefull if you are using a virtual directory, symbolic
+# link or alias. Examples: 'C:\\MySite\\userfiles\\' or '/root/mysite/userfiles/'.
+# Attention: The above 'UserFilesPath' must point to the same directory.
+# WARNING: GetRootPath may not work in virtual or mod_python configurations, and
+# may not be thread safe. Use this configuration parameter instead.
+UserFilesAbsolutePath = '' 
+
+# Due to security issues with Apache modules, it is reccomended to leave the
+# following setting enabled.
+ForceSingleExtension = True 
+
+# What the user can do with this connector
+ConfigAllowedCommands = [ 'QuickUpload', 'FileUpload', 'GetFolders', 'GetFoldersAndFiles', 'CreateFolder' ] 
+
+# Allowed Resource Types
+ConfigAllowedTypes = ['File', 'Image', 'Flash', 'Media'] 
+
+# Do not touch this 3 lines, see "Configuration settings for each Resource Type"
+AllowedExtensions = {}; DeniedExtensions = {};
+FileTypesPath = {}; FileTypesAbsolutePath = {};
+QuickUploadPath = {}; QuickUploadAbsolutePath = {};
+
+#	Configuration settings for each Resource Type
+#
+#	- AllowedExtensions: the possible extensions that can be allowed. 
+#		If it is empty then any file type can be uploaded.
+#	- DeniedExtensions: The extensions that won't be allowed. 
+#		If it is empty then no restrictions are done here.
+#
+#	For a file to be uploaded it has to fullfil both the AllowedExtensions
+#	and DeniedExtensions (that's it: not being denied) conditions.
+#
+#	- FileTypesPath: the virtual folder relative to the document root where
+#		these resources will be located. 
+#		Attention: It must start and end with a slash: '/'
+#
+#	- FileTypesAbsolutePath: the physical path to the above folder. It must be
+#		an absolute path. 
+#		If it's an empty string then it will be autocalculated.
+#		Usefull if you are using a virtual directory, symbolic link or alias. 
+#		Examples: 'C:\\MySite\\userfiles\\' or '/root/mysite/userfiles/'.
+#		Attention: The above 'FileTypesPath' must point to the same directory.
+#		Attention: It must end with a slash: '/'
+#
+#
+#	- QuickUploadPath: the virtual folder relative to the document root where
+#		these resources will be uploaded using the Upload tab in the resources 
+#		dialogs.
+#		Attention: It must start and end with a slash: '/'
+#
+#	- QuickUploadAbsolutePath: the physical path to the above folder. It must be
+#		an absolute path. 
+#		If it's an empty string then it will be autocalculated.
+#		Usefull if you are using a virtual directory, symbolic link or alias. 
+#		Examples: 'C:\\MySite\\userfiles\\' or '/root/mysite/userfiles/'.
+#		Attention: The above 'QuickUploadPath' must point to the same directory.
+#		Attention: It must end with a slash: '/'
+
+AllowedExtensions['File'] 		= []
+DeniedExtensions['File'] 		= ['html','htm','php','php2','php3','php4','php5','phtml','pwml','inc','asp','aspx','ascx','jsp','cfm','cfc','pl','bat','exe','com','dll','vbs','js','reg','cgi','htaccess','asis','sh','shtml','shtm','phtm', 'py', 'spy', 'pyw']
+FileTypesPath['File'] 			= UserFilesPath + 'file/' 
+FileTypesAbsolutePath['File'] 	= (not UserFilesAbsolutePath == '') and (UserFilesAbsolutePath + 'file/') or ''
+QuickUploadPath['File']			= FileTypesPath['File']
+QuickUploadAbsolutePath['File']	= FileTypesAbsolutePath['File']
+
+AllowedExtensions['Image']		= ['jpg','gif','jpeg','png']
+DeniedExtensions['Image']		= []
+FileTypesPath['Image']			= UserFilesPath + 'image/' 
+FileTypesAbsolutePath['Image']	= (not UserFilesAbsolutePath == '') and UserFilesAbsolutePath + 'image/' or ''
+QuickUploadPath['Image']		= FileTypesPath['Image']
+QuickUploadAbsolutePath['Image']= FileTypesAbsolutePath['Image']
+
+AllowedExtensions['Flash']		= ['swf','fla']
+DeniedExtensions['Flash']		= []
+FileTypesPath['Flash']			= UserFilesPath + 'flash/'
+FileTypesAbsolutePath['Flash']	= ( not UserFilesAbsolutePath == '') and UserFilesAbsolutePath + 'flash/' or ''
+QuickUploadPath['Flash']		= FileTypesPath['Flash']
+QuickUploadAbsolutePath['Flash']= FileTypesAbsolutePath['Flash']
+
+AllowedExtensions['Media']		= [ 'swf','fla','jpg','gif','jpeg','png','avi','mpg','mpeg' ]
+DeniedExtensions['Media']		= []
+FileTypesPath['Media']			= UserFilesPath + 'media/'
+FileTypesAbsolutePath['Media']	= ( not UserFilesAbsolutePath == '') and UserFilesAbsolutePath + 'media/' or ''
+QuickUploadPath['Media']		= FileTypesPath['Media']
+QuickUploadAbsolutePath['Media']= FileTypesAbsolutePath['Media']
Index: /FCKeditor/trunk/editor/filemanager/connectors/py/connector.py
===================================================================
--- /FCKeditor/trunk/editor/filemanager/connectors/py/connector.py	(revision 590)
+++ /FCKeditor/trunk/editor/filemanager/connectors/py/connector.py	(revision 591)
@@ -21,765 +21,98 @@
 == END LICENSE ==
 
-Connector for Python.
+Connector for Python (CGI and WSGI).
 
-Tested With:
-Standard:
-	Python 2.3.3
-Zope:
-	Zope Version: (Zope 2.8.1-final, python 2.3.5, linux2)
-	Python Version: 2.3.5 (#4, Mar 10 2005, 01:40:25)
-		[GCC 3.3.3 20040412 (Red Hat Linux 3.3.3-7)]
-	System Platform: linux2
-"""
+See config.py for configuration settings
 
 """
-Author Notes (04 December 2005):
-This module has gone through quite a few phases of change.  Obviously,
-I am only supporting that part of the code that I use.  Initially
-I had the upload directory as a part of zope (ie. uploading files
-directly into Zope), before realising that there were too many
-complex intricacies within Zope to deal with.  Zope is one ugly piece
-of code.  So I decided to complement Zope by an Apache server (which
-I had running anyway, and doing nothing).  So I mapped all uploads
-from an arbitrary server directory to an arbitrary web directory.
-All the FCKeditor uploading occurred this way, and I didn't have to
-stuff around with fiddling with Zope objects and the like (which are
-terribly complex and something you don't want to do - trust me).
+import os
 
-Maybe a Zope expert can touch up the Zope components.  In the end,
-I had FCKeditor loaded in Zope (probably a bad idea as well), and
-I replaced the connector.py with an alias to a server module.
-Right now, all Zope components will simple remain as is because
-I've had enough of Zope.
+from fckutil import *
+from fckcommands import * 	# default command's implementation
+from fckoutput import * 	# base http, xml and html output mixins
+from fckconnector import FCKeditorConnectorBase # import base connector
+import config as Config
 
-See notes right at the end of this file for how I aliased out of Zope.
+class FCKeditorConnector(	FCKeditorConnectorBase,
+							GetFoldersCommandMixin,
+							GetFoldersAndFilesCommandMixin,
+							CreateFolderCommandMixin,
+							UploadFileCommandMixin, 
+							BaseHttpMixin, BaseXmlMixin, BaseHtmlMixin  ):
+	"The Standard connector class."
+	def doResponse(self):
+		"Main function. Process the request, set headers and return a string as response."
+		s = ""
+		# Check if this connector is disabled
+		if not(Config.Enabled):
+			return self.sendError(1, "This connector is disabled.  Please check the connector configurations in \"editor/filemanager/connectors/py/config.py\" and try again.")
+		# Make sure we have valid inputs
+		for key in ("Command","Type","CurrentFolder"):
+			if not self.request.has_key (key):
+				return
+		# Get command, resource type and current folder
+		command = self.request.get("Command")
+		resourceType = self.request.get("Type")
+		currentFolder = getCurrentFolder(self.request.get("CurrentFolder"))
+		# Check for invalid paths
+		if currentFolder is None:
+			return self.sendError(102, "")
+		
+		# Check if it is an allowed command
+		if ( not command in Config.ConfigAllowedCommands ):
+			return self.sendError( 1, 'The %s command isn\'t allowed' % command ) 
+		
+		if ( not resourceType in Config.ConfigAllowedTypes  ):
+			return self.sendError( 1, 'Invalid type specified' ) 
 
-Anyway, most of you probably wont use Zope, so things are pretty
-simple in that regard.
+		# Setup paths
+		if command == "QuickUpload":
+			self.userFilesFolder = Config.QuickUploadAbsolutePath[resourceType] 
+			self.webUserFilesFolder =  Config.QuickUploadPath[resourceType]
+		else:
+			self.userFilesFolder = Config.FileTypesAbsolutePath[resourceType]
+			self.webUserFilesFolder = Config.FileTypesPath[resourceType]	
+		
+		if not self.userFilesFolder: # no absolute path given (dangerous...)
+			self.userFilesFolder = mapServerPath(self.environ, 
+									self.webUserFilesFolder)
+		# Ensure that the directory exists.
+		if not os.path.exists(self.userFilesFolder):
+			try:
+				self.createServerFoldercreateServerFolder( self.userFilesFolder ) 
+			except:
+				return self.sendError(1, "This connector couldn\'t access to local user\'s files directories.  Please check the UserFilesAbsolutePath in \"editor/filemanager/connectors/py/config.py\" and try again. ")
 
-Typically, SERVER_DIR is the root of WEB_DIR (not necessarily).
-Most definitely, SERVER_USERFILES_DIR points to WEB_USERFILES_DIR.
-"""
-
-import cgi
-import re
-import os
-import string
-
-"""
-escape
-
-Converts the special characters '<', '>', and '&'.
-
-RFC 1866 specifies that these characters be represented
-in HTML as &lt; &gt; and &amp; respectively. In Python
-1.5 we use the new string.replace() function for speed.
-"""
-def escape(text, replace=string.replace):
-    text = replace(text, '&', '&amp;') # must be done 1st
-    text = replace(text, '<', '&lt;')
-    text = replace(text, '>', '&gt;')
-    text = replace(text, '"', '&quot;')
-    return text
-
-"""
-getFCKeditorConnector
-
-Creates a new instance of an FCKeditorConnector, and runs it
-"""
-def getFCKeditorConnector(context=None):
-	# Called from Zope.  Passes the context through
-	connector = FCKeditorConnector(context=context)
-	return connector.run()
-
-
-"""
-FCKeditorRequest
-
-A wrapper around the request object
-Can handle normal CGI request, or a Zope request
-Extend as required
-"""
-class FCKeditorRequest(object):
-	def __init__(self, context=None):
-		if (context is not None):
-			r = context.REQUEST
-		else:
-			r = cgi.FieldStorage()
-		self.context = context
-		self.request = r
-
-	def isZope(self):
-		if (self.context is not None):
-			return True
-		return False
-
-	def has_key(self, key):
-		return self.request.has_key(key)
-
-	def get(self, key, default=None):
-		value = None
-		if (self.isZope()):
-			value = self.request.get(key, default)
-		else:
-			if key in self.request.keys():
-				value = self.request[key].value
-			else:
-				value = default
-		return value
-
-"""
-FCKeditorConnector
-
-The connector class
-"""
-class FCKeditorConnector(object):
-	# Configuration for FCKEditor
-	# can point to another server here, if linked correctly
-	#WEB_HOST = "http://127.0.0.1/"
-	WEB_HOST = ""
-	SERVER_DIR = "/var/www/html/"
-
-	WEB_USERFILES_FOLDER = WEB_HOST + "upload/"
-	SERVER_USERFILES_FOLDER = SERVER_DIR + "upload/"
-
-	# Allow access (Zope)
-	__allow_access_to_unprotected_subobjects__ = 1
-	# Class Attributes
-	parentFolderRe = re.compile("[\/][^\/]+[\/]?$")
-
-	"""
-	Constructor
-	"""
-	def __init__(self, context=None):
-		# The given root path will NOT be shown to the user
-		# Only the userFilesPath will be shown
-
-		# Instance Attributes
-		self.context = context
-		self.request = FCKeditorRequest(context=context)
-		self.rootPath = self.SERVER_DIR
-		self.userFilesFolder = self.SERVER_USERFILES_FOLDER
-		self.webUserFilesFolder = self.WEB_USERFILES_FOLDER
-
-		# Enables / Disables the connector
-		self.enabled = False # Set to True to enable this connector
-
-		# These are instance variables
-		self.zopeRootContext = None
-		self.zopeUploadContext = None
-
-		# Copied from php module =)
-		self.allowedExtensions = {
-				"File": None,
-				"Image": None,
-				"Flash": None,
-				"Media": None
-				}
-		self.deniedExtensions = {
-				"File": [ "html","htm","php","php2","php3","php4","php5","phtml","pwml","inc","asp","aspx","ascx","jsp","cfm","cfc","pl","bat","exe","com","dll","vbs","js","reg","cgi","htaccess","asis","sh","shtml","shtm","phtm" ],
-				"Image": [ "html","htm","php","php2","php3","php4","php5","phtml","pwml","inc","asp","aspx","ascx","jsp","cfm","cfc","pl","bat","exe","com","dll","vbs","js","reg","cgi","htaccess","asis","sh","shtml","shtm","phtm" ],
-				"Flash": [ "html","htm","php","php2","php3","php4","php5","phtml","pwml","inc","asp","aspx","ascx","jsp","cfm","cfc","pl","bat","exe","com","dll","vbs","js","reg","cgi","htaccess","asis","sh","shtml","shtm","phtm" ],
-				"Media": [ "html","htm","php","php2","php3","php4","php5","phtml","pwml","inc","asp","aspx","ascx","jsp","cfm","cfc","pl","bat","exe","com","dll","vbs","js","reg","cgi","htaccess","asis","sh","shtml","shtm","phtm" ]
-				}
-
-	"""
-	Zope specific functions
-	"""
-	def isZope(self):
-		# The context object is the zope object
-		if (self.context is not None):
-			return True
-		return False
-
-	def getZopeRootContext(self):
-		if self.zopeRootContext is None:
-			self.zopeRootContext = self.context.getPhysicalRoot()
-		return self.zopeRootContext
-
-	def getZopeUploadContext(self):
-		if self.zopeUploadContext is None:
-			folderNames = self.userFilesFolder.split("/")
-			c = self.getZopeRootContext()
-			for folderName in folderNames:
-				if (folderName <> ""):
-					c = c[folderName]
-			self.zopeUploadContext = c
-		return self.zopeUploadContext
-
-	"""
-	Generic manipulation functions
-	"""
-	def getUserFilesFolder(self):
-		return self.userFilesFolder
-
-	def getWebUserFilesFolder(self):
-		return self.webUserFilesFolder
-
-	def getAllowedExtensions(self, resourceType):
-		return self.allowedExtensions[resourceType]
-
-	def getDeniedExtensions(self, resourceType):
-		return self.deniedExtensions[resourceType]
-
-	def removeFromStart(self, string, char):
-		return string.lstrip(char)
-
-	def removeFromEnd(self, string, char):
-		return string.rstrip(char)
-
-	def convertToXmlAttribute(self, value):
-		if (value is None):
-			value = ""
-		return escape(value)
-
-	def convertToPath(self, path):
-		if (path[-1] <> "/"):
-			return path + "/"
-		else:
-			return path
-
-	def getUrlFromPath(self, resourceType, path):
-		if (resourceType is None) or (resourceType == ''):
-			url = "%s%s" % (
-					self.removeFromEnd(self.getUserFilesFolder(), '/'),
-					path
-					)
-		else:
-			url = "%s%s%s" % (
-					self.getUserFilesFolder(),
-					resourceType,
-					path
-					)
-		return url
-
-	def getWebUrlFromPath(self, resourceType, path):
-		if (resourceType is None) or (resourceType == ''):
-			url = "%s%s" % (
-					self.removeFromEnd(self.getWebUserFilesFolder(), '/'),
-					path
-					)
-		else:
-			url = "%s%s%s" % (
-					self.getWebUserFilesFolder(),
-					resourceType,
-					path
-					)
-		return url
-
-	def removeExtension(self, fileName):
-		index = fileName.rindex(".")
-		newFileName = fileName[0:index]
-		return newFileName
-
-	def getExtension(self, fileName):
-		index = fileName.rindex(".") + 1
-		fileExtension = fileName[index:]
-		return fileExtension
-
-	def getParentFolder(self, folderPath):
-		parentFolderPath = self.parentFolderRe.sub('', folderPath)
-		return parentFolderPath
-
-	"""
-	serverMapFolder
-
-	Purpose: works out the folder map on the server
-	"""
-	def serverMapFolder(self, resourceType, folderPath):
-		# Get the resource type directory
-		resourceTypeFolder = "%s%s/" % (
-				self.getUserFilesFolder(),
-				resourceType
-				)
-		# Ensure that the directory exists
-		self.createServerFolder(resourceTypeFolder)
-
-		# Return the resource type directory combined with the
-		# required path
-		return "%s%s" % (
-				resourceTypeFolder,
-				self.removeFromStart(folderPath, '/')
-				)
-
-	"""
-	createServerFolder
-
-	Purpose: physically creates a folder on the server
-	"""
-	def createServerFolder(self, folderPath):
-		# Check if the parent exists
-		parentFolderPath = self.getParentFolder(folderPath)
-		if not(os.path.exists(parentFolderPath)):
-			errorMsg = self.createServerFolder(parentFolderPath)
-			if errorMsg is not None:
-				return errorMsg
-		# Check if this exists
-		if not(os.path.exists(folderPath)):
-			os.mkdir(folderPath)
-			os.chmod(folderPath, 0755)
-			errorMsg = None
-		else:
-			if os.path.isdir(folderPath):
-				errorMsg = None
-			else:
-				raise "createServerFolder: Non-folder of same name already exists"
-		return errorMsg
-
-
-	"""
-	getRootPath
-
-	Purpose: returns the root path on the server
-	"""
-	def getRootPath(self):
-		return self.rootPath
-
-	"""
-	setXmlHeaders
-
-	Purpose: to prepare the headers for the xml to return
-	"""
-	def setXmlHeaders(self):
-		#now = self.context.BS_get_now()
-		#yesterday = now - 1
-		self.setHeader("Content-Type", "text/xml")
-		#self.setHeader("Expires", yesterday)
-		#self.setHeader("Last-Modified", now)
-		#self.setHeader("Cache-Control", "no-store, no-cache, must-revalidate")
-		self.printHeaders()
-		return
-
-	def setHeader(self, key, value):
-		if (self.isZope()):
-			self.context.REQUEST.RESPONSE.setHeader(key, value)
-		else:
-			print "%s: %s" % (key, value)
-		return
-
-	def printHeaders(self):
-		# For non-Zope requests, we need to print an empty line
-		# to denote the end of headers
-		if (not(self.isZope())):
-			print ""
-
-	"""
-	createXmlFooter
-
-	Purpose: returns the xml header
-	"""
-	def createXmlHeader(self, command, resourceType, currentFolder):
-		self.setXmlHeaders()
-		s = ""
-		# Create the XML document header
-		s += """<?xml version="1.0" encoding="utf-8" ?>"""
-		# Create the main connector node
-		s += """<Connector command="%s" resourceType="%s">""" % (
-				command,
-				resourceType
-				)
-		# Add the current folder node
-		s += """<CurrentFolder path="%s" url="%s" />""" % (
-				self.convertToXmlAttribute(currentFolder),
-				self.convertToXmlAttribute(
-					self.getWebUrlFromPath(
-						resourceType,
-						currentFolder
-						)
-					),
-				)
-		return s
-
-	"""
-	createXmlFooter
-
-	Purpose: returns the xml footer
-	"""
-	def createXmlFooter(self):
-		s = """</Connector>"""
-		return s
-
-	"""
-	sendError
-
-	Purpose: in the event of an error, return an xml based error
-	"""
-	def sendError(self, number, text):
-		self.setXmlHeaders()
-		s = ""
-		# Create the XML document header
-		s += """<?xml version="1.0" encoding="utf-8" ?>"""
-		s += """<Connector>"""
-		s += """<Error number="%s" text="%s" />""" % (number, text)
-		s += """</Connector>"""
-		return s
-
-	"""
-	getFolders
-
-	Purpose: command to recieve a list of folders
-	"""
-	def getFolders(self, resourceType, currentFolder):
-		if (self.isZope()):
-			return self.getZopeFolders(resourceType, currentFolder)
-		else:
-			return self.getNonZopeFolders(resourceType, currentFolder)
-
-	def getZopeFolders(self, resourceType, currentFolder):
-		# Open the folders node
-		s = ""
-		s += """<Folders>"""
-		zopeFolder = self.findZopeFolder(resourceType, currentFolder)
-		for (name, o) in zopeFolder.objectItems(["Folder"]):
-			s += """<Folder name="%s" />""" % (
-					self.convertToXmlAttribute(name)
-					)
-		# Close the folders node
-		s += """</Folders>"""
-		return s
-
-	def getNonZopeFolders(self, resourceType, currentFolder):
-		# Map the virtual path to our local server
-		serverPath = self.serverMapFolder(resourceType, currentFolder)
-		# Open the folders node
-		s = ""
-		s += """<Folders>"""
-		for someObject in os.listdir(serverPath):
-			someObjectPath = os.path.join(serverPath, someObject)
-			if os.path.isdir(someObjectPath):
-				s += """<Folder name="%s" />""" % (
-						self.convertToXmlAttribute(someObject)
-						)
-		# Close the folders node
-		s += """</Folders>"""
-		return s
-
-	"""
-	getFoldersAndFiles
-
-	Purpose: command to recieve a list of folders and files
-	"""
-	def getFoldersAndFiles(self, resourceType, currentFolder):
-		if (self.isZope()):
-			return self.getZopeFoldersAndFiles(resourceType, currentFolder)
-		else:
-			return self.getNonZopeFoldersAndFiles(resourceType, currentFolder)
-
-	def getNonZopeFoldersAndFiles(self, resourceType, currentFolder):
-		# Map the virtual path to our local server
-		serverPath = self.serverMapFolder(resourceType, currentFolder)
-		# Open the folders / files node
-		folders = """<Folders>"""
-		files = """<Files>"""
-		for someObject in os.listdir(serverPath):
-			someObjectPath = os.path.join(serverPath, someObject)
-			if os.path.isdir(someObjectPath):
-				folders += """<Folder name="%s" />""" % (
-						self.convertToXmlAttribute(someObject)
-						)
-			elif os.path.isfile(someObjectPath):
-				size = os.path.getsize(someObjectPath)
-				files += """<File name="%s" size="%s" />""" % (
-						self.convertToXmlAttribute(someObject),
-						os.path.getsize(someObjectPath)
-						)
-		# Close the folders / files node
-		folders += """</Folders>"""
-		files += """</Files>"""
-		# Return it
-		s = folders + files
-		return s
-
-	def getZopeFoldersAndFiles(self, resourceType, currentFolder):
-		folders = self.getZopeFolders(resourceType, currentFolder)
-		files = self.getZopeFiles(resourceType, currentFolder)
-		s = folders + files
-		return s
-
-	def getZopeFiles(self, resourceType, currentFolder):
-		# Open the files node
-		s = ""
-		s += """<Files>"""
-		zopeFolder = self.findZopeFolder(resourceType, currentFolder)
-		for (name, o) in zopeFolder.objectItems(["File","Image"]):
-			s += """<File name="%s" size="%s" />""" % (
-					self.convertToXmlAttribute(name),
-					((o.get_size() / 1024) + 1)
-					)
-		# Close the files node
-		s += """</Files>"""
-		return s
-
-	def findZopeFolder(self, resourceType, folderName):
-		# returns the context of the resource / folder
-		zopeFolder = self.getZopeUploadContext()
-		folderName = self.removeFromStart(folderName, "/")
-		folderName = self.removeFromEnd(folderName, "/")
-		if (resourceType <> ""):
-			try:
-				zopeFolder = zopeFolder[resourceType]
-			except:
-				zopeFolder.manage_addProduct["OFSP"].manage_addFolder(id=resourceType, title=resourceType)
-				zopeFolder = zopeFolder[resourceType]
-		if (folderName <> ""):
-			folderNames = folderName.split("/")
-			for folderName in folderNames:
-				zopeFolder = zopeFolder[folderName]
-		return zopeFolder
-
-	"""
-	createFolder
-
-	Purpose: command to create a new folder
-	"""
-	def createFolder(self, resourceType, currentFolder):
-		if (self.isZope()):
-			return self.createZopeFolder(resourceType, currentFolder)
-		else:
-			return self.createNonZopeFolder(resourceType, currentFolder)
-
-	def createZopeFolder(self, resourceType, currentFolder):
-		# Find out where we are
-		zopeFolder = self.findZopeFolder(resourceType, currentFolder)
-		errorNo = 0
-		errorMsg = ""
-		if self.request.has_key("NewFolderName"):
-			newFolder = self.request.get("NewFolderName", None)
-			zopeFolder.manage_addProduct["OFSP"].manage_addFolder(id=newFolder, title=newFolder)
-		else:
-			errorNo = 102
-		error = """<Error number="%s" originalDescription="%s" />""" % (
-				errorNo,
-				self.convertToXmlAttribute(errorMsg)
-				)
-		return error
-
-	def createNonZopeFolder(self, resourceType, currentFolder):
-		errorNo = 0
-		errorMsg = ""
-		if self.request.has_key("NewFolderName"):
-			newFolder = self.request.get("NewFolderName", None)
-			currentFolderPath = self.serverMapFolder(
-					resourceType,
-					currentFolder
-					)
-			try:
-				newFolderPath = currentFolderPath + newFolder
-				errorMsg = self.createServerFolder(newFolderPath)
-				if (errorMsg is not None):
-					errorNo = 110
-			except:
-				errorNo = 103
-		else:
-			errorNo = 102
-		error = """<Error number="%s" originalDescription="%s" />""" % (
-				errorNo,
-				self.convertToXmlAttribute(errorMsg)
-				)
-		return error
-
-	"""
-	getFileName
-
-	Purpose: helper function to extrapolate the filename
-	"""
-	def getFileName(self, filename):
-		for splitChar in ["/", "\\"]:
-			array = filename.split(splitChar)
-			if (len(array) > 1):
-				filename = array[-1]
-		return filename
-
-	"""
-	fileUpload
-
-	Purpose: command to upload files to server
-	"""
-	def fileUpload(self, resourceType, currentFolder):
-		if (self.isZope()):
-			return self.zopeFileUpload(resourceType, currentFolder)
-		else:
-			return self.nonZopeFileUpload(resourceType, currentFolder)
-
-	def zopeFileUpload(self, resourceType, currentFolder, count=None):
-		zopeFolder = self.findZopeFolder(resourceType, currentFolder)
-		file = self.request.get("NewFile", None)
-		fileName = self.getFileName(file.filename)
-		fileNameOnly = self.removeExtension(fileName)
-		fileExtension = self.getExtension(fileName).lower()
-		if (count):
-			nid = "%s.%s.%s" % (fileNameOnly, count, fileExtension)
-		else:
-			nid = fileName
-		title = nid
-		try:
-			zopeFolder.manage_addProduct['OFSP'].manage_addFile(
-					id=nid,
-					title=title,
-					file=file.read()
-					)
-		except:
-			if (count):
-				count += 1
-			else:
-				count = 1
-			self.zopeFileUpload(resourceType, currentFolder, count)
-		return
-
-	def nonZopeFileUpload(self, resourceType, currentFolder):
-		errorNo = 0
-		errorMsg = ""
-		if self.request.has_key("NewFile"):
-			# newFile has all the contents we need
-			newFile = self.request.get("NewFile", "")
-			# Get the file name
-			newFileName = newFile.filename
-			newFileNameOnly = self.removeExtension(newFileName)
-			newFileExtension = self.getExtension(newFileName).lower()
-			allowedExtensions = self.getAllowedExtensions(resourceType)
-			deniedExtensions = self.getDeniedExtensions(resourceType)
-			if (allowedExtensions is not None):
-				# Check for allowed
-				isAllowed = False
-				if (newFileExtension in allowedExtensions):
-					isAllowed = True
-			elif (deniedExtensions is not None):
-				# Check for denied
-				isAllowed = True
-				if (newFileExtension in deniedExtensions):
-					isAllowed = False
-			else:
-				# No extension limitations
-				isAllowed = True
-
-			if (isAllowed):
-				if (self.isZope()):
-					# Upload into zope
-					self.zopeFileUpload(resourceType, currentFolder)
-				else:
-					# Upload to operating system
-					# Map the virtual path to the local server path
-					currentFolderPath = self.serverMapFolder(
-							resourceType,
-							currentFolder
-							)
-					i = 0
-					while (True):
-						newFilePath = "%s%s" % (
-								currentFolderPath,
-								newFileName
-								)
-						if os.path.exists(newFilePath):
-							i += 1
-							newFilePath = "%s%s(%s).%s" % (
-									currentFolderPath,
-									newFileNameOnly,
-									i,
-									newFileExtension
-									)
-							errorNo = 201
-							break
-						else:
-							fileHandle = open(newFilePath,'w')
-							linecount = 0
-							while (1):
-								#line = newFile.file.readline()
-								line = newFile.readline()
-								if not line: break
-								fileHandle.write("%s" % line)
-								linecount += 1
-							os.chmod(newFilePath, 0777)
-							break
-			else:
-				newFileName = "Extension not allowed"
-				errorNo = 203
-		else:
-			newFileName = "No File"
-			errorNo = 202
-
-		string = """
-<script type="text/javascript">
-window.parent.frames["frmUpload"].OnUploadCompleted(%s,"%s");
-</script>
-				""" % (
-						errorNo,
-						newFileName.replace('"',"'")
-						)
-		return string
-
-	def run(self):
-		s = ""
-		try:
-			# Check if this is disabled
-			if not(self.enabled):
-				return self.sendError(1, "This connector is disabled.  Please check the connector configurations and try again")
-			# Make sure we have valid inputs
-			if not(
-					(self.request.has_key("Command")) and
-					(self.request.has_key("Type")) and
-					(self.request.has_key("CurrentFolder"))
-					):
-				return
-			# Get command
-			command = self.request.get("Command", None)
-			# Get resource type
-			resourceType = self.request.get("Type", None)
-			# folder syntax must start and end with "/"
-			currentFolder = self.request.get("CurrentFolder", None)
-			if (currentFolder[-1] <> "/"):
-				currentFolder += "/"
-			if (currentFolder[0] <> "/"):
-				currentFolder = "/" + currentFolder
-			# Check for invalid paths
-			if (".." in currentFolder):
-				return self.sendError(102, "")
-			# File upload doesn't have to return XML, so intercept
-			# her:e
-			if (command == "FileUpload"):
-				return self.fileUpload(resourceType, currentFolder)
-			# Begin XML
-			s += self.createXmlHeader(command, resourceType, currentFolder)
-			# Execute the command
-			if (command == "GetFolders"):
-				f = self.getFolders
-			elif (command == "GetFoldersAndFiles"):
-				f = self.getFoldersAndFiles
-			elif (command == "CreateFolder"):
-				f = self.createFolder
-			else:
-				f = None
-			if (f is not None):
-				s += f(resourceType, currentFolder)
-			s += self.createXmlFooter()
-		except Exception, e:
-			s = "ERROR: %s" % e
-		return s
-
-# Running from command line
+		# File upload doesn't have to return XML, so intercept here
+		if (command == "FileUpload"):
+			return self.uploadFile(resourceType, currentFolder)
+		
+		# Create Url
+		url = combinePaths( self.webUserFilesFolder, currentFolder )
+		
+		# Begin XML
+		s += self.createXmlHeader(command, resourceType, currentFolder, url)
+		# Execute the command
+		selector = {"GetFolders": self.getFolders,
+					"GetFoldersAndFiles": self.getFoldersAndFiles,
+					"CreateFolder": self.createFolder,
+					}
+		s += selector[command](resourceType, currentFolder)
+		s += self.createXmlFooter()
+		return s	
+	
+# Running from command line (plain old CGI)
 if __name__ == '__main__':
-	# To test the output, uncomment the standard headers
-	#print "Content-Type: text/html"
-	#print ""
-	print getFCKeditorConnector()
-
-"""
-Running from zope, you will need to modify this connector.
-If you have uploaded the FCKeditor into Zope (like me), you need to
-move this connector out of Zope, and replace the "connector" with an
-alias as below.  The key to it is to pass the Zope context in, as
-we then have a like to the Zope context.
-
-## Script (Python) "connector.py"
-##bind container=container
-##bind context=context
-##bind namespace=
-##bind script=script
-##bind subpath=traverse_subpath
-##parameters=*args, **kws
-##title=ALIAS
-##
-import Products.connector as connector
-return connector.getFCKeditorConnector(context=context).run()
-"""
-
-
+	try:
+		# Create a Connector Instance
+		conn = FCKeditorConnector()
+		data = conn.doResponse()
+		for header in conn.headers:
+			print '%s: %s' % header
+		print 
+		print data
+	except:
+		print "Content-Type: text/plain"
+		print
+		import cgi
+		cgi.print_exception()
Index: /FCKeditor/trunk/editor/filemanager/connectors/py/fckcommands.py
===================================================================
--- /FCKeditor/trunk/editor/filemanager/connectors/py/fckcommands.py	(revision 591)
+++ /FCKeditor/trunk/editor/filemanager/connectors/py/fckcommands.py	(revision 591)
@@ -0,0 +1,181 @@
+#!/usr/bin/env python
+
+"""
+FCKeditor - The text editor for Internet - http://www.fckeditor.net
+Copyright (C) 2003-2007 Frederico Caldeira Knabben
+
+== BEGIN LICENSE ==
+
+Licensed under the terms of any of the following licenses at your
+choice:
+
+- GNU General Public License Version 2 or later (the "GPL")
+http://www.gnu.org/licenses/gpl.html
+
+- GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+http://www.gnu.org/licenses/lgpl.html
+
+- Mozilla Public License Version 1.1 or later (the "MPL")
+http://www.mozilla.org/MPL/MPL-1.1.html
+
+== END LICENSE ==
+
+Connector for Python (CGI and WSGI).
+
+"""
+
+import os
+try: # Windows needs stdio set for binary mode for file upload to work.
+	import msvcrt
+	msvcrt.setmode (0, os.O_BINARY) # stdin  = 0
+	msvcrt.setmode (1, os.O_BINARY) # stdout = 1
+except ImportError:
+	pass
+
+from fckutil import *
+from fckoutput import *
+import config as Config
+
+class GetFoldersCommandMixin (object):
+	def getFolders(self, resourceType, currentFolder):
+		"""
+		Purpose: command to recieve a list of folders
+		"""
+		# Map the virtual path to our local server
+		serverPath = mapServerFolder(self.userFilesFolder,currentFolder)
+		s = """<Folders>"""	 # Open the folders node
+		for someObject in os.listdir(serverPath):
+			someObjectPath = mapServerFolder(serverPath, someObject)
+			if os.path.isdir(someObjectPath):
+				s += """<Folder name="%s" />""" % (
+						convertToXmlAttribute(someObject)
+						)
+		s += """</Folders>""" # Close the folders node
+		return s
+
+class GetFoldersAndFilesCommandMixin (object):
+	def getFoldersAndFiles(self, resourceType, currentFolder):
+		"""
+		Purpose: command to recieve a list of folders and files
+		"""
+		# Map the virtual path to our local server
+		serverPath = mapServerFolder(self.userFilesFolder,currentFolder)
+		# Open the folders / files node
+		folders = """<Folders>"""
+		files = """<Files>"""
+		for someObject in os.listdir(serverPath):
+			someObjectPath = mapServerFolder(serverPath, someObject)
+			if os.path.isdir(someObjectPath):
+				folders += """<Folder name="%s" />""" % (
+						convertToXmlAttribute(someObject)
+						)
+			elif os.path.isfile(someObjectPath):
+				size = os.path.getsize(someObjectPath)
+				files += """<File name="%s" size="%s" />""" % (
+						convertToXmlAttribute(someObject),
+						os.path.getsize(someObjectPath)
+						)
+		# Close the folders / files node
+		folders += """</Folders>"""
+		files += """</Files>"""
+		return folders + files
+
+class CreateFolderCommandMixin (object):
+	def createFolder(self, resourceType, currentFolder):
+		"""
+		Purpose: command to create a new folder
+		"""
+		errorNo = 0; errorMsg ='';
+		if self.request.has_key("NewFolderName"):
+			newFolder = self.request.get("NewFolderName", None)
+			newFolder = sanitizeFolderName (newFolder)
+			try:
+				newFolderPath = mapServerFolder(self.userFilesFolder, combinePaths(currentFolder, newFolder))
+				self.createServerFolder(newFolderPath)
+			except Exception, e:
+				errorMsg = str(e).decode('iso-8859-1').encode('utf-8') # warning with encodigns!!!
+				if hasattr(e,'errno'):
+					if e.errno==17: #file already exists
+						errorNo=0
+					elif e.errno==13: # permission denied
+						errorNo = 103
+					elif e.errno==36 or e.errno==2 or e.errno==22: # filename too long / no such file / invalid name
+						errorNo = 102 
+				else:
+					errorNo = 110
+		else:
+			errorNo = 102
+		return self.sendErrorNode ( errorNo, errorMsg )
+
+	def createServerFolder(self, folderPath):
+		"Purpose: physically creates a folder on the server"
+		# No need to check if the parent exists, just create all hierachy
+		oldumask = os.umask(0)
+		os.makedirs(folderPath,mode=0755)
+		os.umask( oldumask ) 
+
+class UploadFileCommandMixin (object):
+	def uploadFile(self, resourceType, currentFolder):
+		"""
+		Purpose: command to upload files to server (same as FileUpload)
+		"""
+		errorNo = 0
+		if self.request.has_key("NewFile"):
+			# newFile has all the contents we need
+			newFile = self.request.get("NewFile", "")
+			# Get the file name
+			newFileName = newFile.filename
+			newFileName = sanitizeFileName( newFileName ) 
+			newFileNameOnly = removeExtension(newFileName)
+			newFileExtension = getExtension(newFileName).lower()
+			allowedExtensions = Config.AllowedExtensions[resourceType]
+			deniedExtensions = Config.DeniedExtensions[resourceType]
+
+			if (allowedExtensions):
+				# Check for allowed
+				isAllowed = False
+				if (newFileExtension in allowedExtensions):
+					isAllowed = True
+			elif (deniedExtensions):
+				# Check for denied
+				isAllowed = True
+				if (newFileExtension in deniedExtensions):
+					isAllowed = False
+			else:
+				# No extension limitations
+				isAllowed = True
+
+			if (isAllowed):
+				# Upload to operating system
+				# Map the virtual path to the local server path
+				currentFolderPath = mapServerFolder(self.userFilesFolder, currentFolder)
+				i = 0
+				while (True):
+					newFilePath = os.path.join (currentFolderPath,newFileName)
+					if os.path.exists(newFilePath):
+						i += 1
+						newFileName = "%s(%04d).%s" % (
+								newFileNameOnly, i, newFileExtension
+								)
+						errorNo= 201 # file renamed
+					else:
+						# Read file contents and write to the desired path (similar to php's move_uploaded_file)
+						fout = file(newFilePath, 'wb')
+						while (True):
+							chunk = newFile.file.read(100000)
+							if not chunk: break
+							fout.write (chunk)
+						fout.close()
+
+						if os.path.exists ( newFilePath ):
+							oldumask = os.umask(0) 
+							os.chmod( newFilePath, 0755 ) 
+							os.umask( oldumask ) 
+
+						newFileUrl = self.webUserFilesFolder + currentFolder + newFileName
+
+						return self.sendUploadResults( errorNo , newFileUrl, newFileName )
+			else:
+				return self.sendUploadResults( errorNo = 203, customMsg = "Extension not allowed" )
+		else:
+			return self.sendUploadResults( errorNo = 202, customMsg = "No File" )
Index: /FCKeditor/trunk/editor/filemanager/connectors/py/fckconnector.py
===================================================================
--- /FCKeditor/trunk/editor/filemanager/connectors/py/fckconnector.py	(revision 591)
+++ /FCKeditor/trunk/editor/filemanager/connectors/py/fckconnector.py	(revision 591)
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+
+"""
+FCKeditor - The text editor for Internet - http://www.fckeditor.net
+Copyright (C) 2003-2007 Frederico Caldeira Knabben
+
+== BEGIN LICENSE ==
+
+Licensed under the terms of any of the following licenses at your
+choice:
+
+- GNU General Public License Version 2 or later (the "GPL")
+http://www.gnu.org/licenses/gpl.html
+
+- GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+http://www.gnu.org/licenses/lgpl.html
+
+- Mozilla Public License Version 1.1 or later (the "MPL")
+http://www.mozilla.org/MPL/MPL-1.1.html
+
+== END LICENSE ==
+
+Base Connector for Python (CGI and WSGI).
+
+See config.py for configuration settings
+
+"""
+import cgi, os
+
+from fckutil import *
+from fckcommands import * 	# default command's implementation
+from fckoutput import * 	# base http, xml and html output mixins
+import config as Config
+
+class FCKeditorConnectorBase( object ):
+	"The base connector class. Subclass it to extend functionality (see Zope example)"
+
+	def __init__(self, environ=None):
+		"Constructor: Here you should parse request fields, initialize variables, etc."
+		self.request = FCKeditorRequest(environ) # Parse request
+		self.headers = []						# Clean Headers 
+		if environ:
+			self.environ = environ
+		else:
+			self.environ = os.environ
+
+	# local functions
+
+	def setHeader(self, key, value):
+		self.headers.append ((key, value))
+		return
+
+class FCKeditorRequest(object):
+	"A wrapper around the request object"
+	def __init__(self, environ):
+		if environ: # WSGI
+			self.request = cgi.FieldStorage(fp=environ['wsgi.input'],
+							environ=environ,
+							keep_blank_values=1)
+			self.environ = environ
+		else: # plain old cgi
+			self.environ = os.environ
+			self.request = cgi.FieldStorage()
+		if 'REQUEST_METHOD' in self.environ and 'QUERY_STRING' in self.environ:
+			if self.environ['REQUEST_METHOD'].upper()=='POST':
+				# we are in a POST, but GET query_string exists
+				# cgi parses by default POST data, so parse GET QUERY_STRING too
+				self.get_request = cgi.FieldStorage(fp=None,
+							environ={
+							'REQUEST_METHOD':'GET',
+							'QUERY_STRING':self.environ['QUERY_STRING'],
+							},
+							)
+		else:
+			self.get_request={}
+
+	def has_key(self, key):
+		return self.request.has_key(key) or self.get_request.has_key(key)
+
+	def get(self, key, default=None):
+		if key in self.request.keys():
+			field = self.request[key]
+		elif key in self.get_request.keys():
+			field = self.get_request[key]
+		else:
+			return default
+		if hasattr(field,"filename") and field.filename: #file upload, do not convert return value
+			return field
+		else:
+			return field.value
Index: /FCKeditor/trunk/editor/filemanager/connectors/py/fckoutput.py
===================================================================
--- /FCKeditor/trunk/editor/filemanager/connectors/py/fckoutput.py	(revision 591)
+++ /FCKeditor/trunk/editor/filemanager/connectors/py/fckoutput.py	(revision 591)
@@ -0,0 +1,111 @@
+#!/usr/bin/env python
+
+"""
+FCKeditor - The text editor for Internet - http://www.fckeditor.net
+Copyright (C) 2003-2007 Frederico Caldeira Knabben
+
+== BEGIN LICENSE ==
+
+Licensed under the terms of any of the following licenses at your
+choice:
+
+- GNU General Public License Version 2 or later (the "GPL")
+http://www.gnu.org/licenses/gpl.html
+
+- GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+http://www.gnu.org/licenses/lgpl.html
+
+- Mozilla Public License Version 1.1 or later (the "MPL")
+http://www.mozilla.org/MPL/MPL-1.1.html
+
+== END LICENSE ==
+
+Connector for Python (CGI and WSGI).
+
+"""
+
+from time import gmtime, strftime
+import string
+
+def escape(text, replace=string.replace):
+	"""
+	Converts the special characters '<', '>', and '&'.
+
+	RFC 1866 specifies that these characters be represented
+	in HTML as &lt; &gt; and &amp; respectively. In Python
+	1.5 we use the new string.replace() function for speed.
+	"""
+	text = replace(text, '&', '&amp;') # must be done 1st
+	text = replace(text, '<', '&lt;')
+	text = replace(text, '>', '&gt;')
+	text = replace(text, '"', '&quot;')
+	return text
+
+def convertToXmlAttribute(value):
+	if (value is None):
+		value = ""
+	return escape(value)
+
+class BaseHttpMixin(object):
+	def setHttpHeaders(self, content_type='text/xml'):
+		"Purpose: to prepare the headers for the xml to return"
+		# Prevent the browser from caching the result.
+		# Date in the past
+		self.setHeader('Expires','Mon, 26 Jul 1997 05:00:00 GMT')
+		# always modified
+		self.setHeader('Last-Modified',strftime("%a, %d %b %Y %H:%M:%S GMT", gmtime())) 
+		# HTTP/1.1
+		self.setHeader('Cache-Control','no-store, no-cache, must-revalidate') 
+		self.setHeader('Cache-Control','post-check=0, pre-check=0') 
+		# HTTP/1.0
+		self.setHeader('Pragma','no-cache') 
+
+		# Set the response format.
+		self.setHeader( 'Content-Type', content_type + '; charset=utf-8' )
+		return
+
+class BaseXmlMixin(object):
+	def createXmlHeader(self, command, resourceType, currentFolder, url):
+		"Purpose: returns the xml header"
+		self.setHttpHeaders()
+		# Create the XML document header
+		s =  """<?xml version="1.0" encoding="utf-8" ?>"""
+		# Create the main connector node
+		s += """<Connector command="%s" resourceType="%s">""" % (
+				command,
+				resourceType
+				)
+		# Add the current folder node
+		s += """<CurrentFolder path="%s" url="%s" />""" % (
+				convertToXmlAttribute(currentFolder),
+				convertToXmlAttribute(url),
+				)
+		return s
+
+	def createXmlFooter(self):
+		"Purpose: returns the xml footer"
+		return """</Connector>"""
+
+	def sendError(self, number, text):
+		"Purpose: in the event of an error, return an xml based error"
+		self.setHttpHeaders()
+		return ("""<?xml version="1.0" encoding="utf-8" ?>""" +
+				"""<Connector>""" +
+				self.sendErrorNode (number, text) +
+				"""</Connector>""" )
+		
+	def sendErrorNode(self, number, text):
+		return """<Error number="%s" text="%s" />""" % (number, convertToXmlAttribute(text))
+
+class BaseHtmlMixin(object):
+	def sendUploadResults( self, errorNo = 0, fileUrl = '', fileName = '', customMsg = '' ):
+		self.setHttpHeaders("text/html")
+		"This is the function that sends the results of the uploading process"
+		return """<script type="text/javascript">
+			window.parent.OnUploadCompleted(%(errorNumber)s,"%(fileUrl)s","%(fileName)s","%(customMsg)s"); 
+			</script>""" % {
+			'errorNumber': errorNo,
+			'fileUrl': fileUrl.replace ('"', '\\"'),
+			'fileName': fileName.replace ( '"', '\\"' ) , 
+			'customMsg': customMsg.replace ( '"', '\\"' ),
+			}
Index: /FCKeditor/trunk/editor/filemanager/connectors/py/fckutil.py
===================================================================
--- /FCKeditor/trunk/editor/filemanager/connectors/py/fckutil.py	(revision 591)
+++ /FCKeditor/trunk/editor/filemanager/connectors/py/fckutil.py	(revision 591)
@@ -0,0 +1,127 @@
+#!/usr/bin/env python
+
+"""
+FCKeditor - The text editor for Internet - http://www.fckeditor.net
+Copyright (C) 2003-2007 Frederico Caldeira Knabben
+
+== BEGIN LICENSE ==
+
+Licensed under the terms of any of the following licenses at your
+choice:
+
+- GNU General Public License Version 2 or later (the "GPL")
+http://www.gnu.org/licenses/gpl.html
+
+- GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+http://www.gnu.org/licenses/lgpl.html
+
+- Mozilla Public License Version 1.1 or later (the "MPL")
+http://www.mozilla.org/MPL/MPL-1.1.html
+
+== END LICENSE ==
+
+Utility functions for the File Manager Connector for Python 
+
+"""
+
+import string, re
+import os
+import config as Config
+
+# Generic manipulation functions
+
+def removeExtension(fileName):
+	index = fileName.rindex(".")
+	newFileName = fileName[0:index]
+	return newFileName
+
+def getExtension(fileName):
+	index = fileName.rindex(".") + 1
+	fileExtension = fileName[index:]
+	return fileExtension
+
+def removeFromStart(string, char):
+	return string.lstrip(char)
+
+def removeFromEnd(string, char):
+	return string.rstrip(char)
+
+# Path functions
+
+def combinePaths( basePath, folder ):
+	return removeFromEnd( basePath, '/' ) + '/' + removeFromStart( folder, '/' ) 
+
+def getFileName(filename):
+	" Purpose: helper function to extrapolate the filename " 
+	for splitChar in ["/", "\\"]:
+		array = filename.split(splitChar)
+		if (len(array) > 1):
+			filename = array[-1]
+	return filename
+
+def sanitizeFolderName( newFolderName ):
+	"Do a cleanup of the folder name to avoid possible problems"
+	# Remove . \ / | : ? *
+	return re.sub( '\\.|\\\\|\\/|\\||\\:|\\?|\\*', '_', newFolderName )
+
+def sanitizeFileName( newFileName ):
+	"Do a cleanup of the file name to avoid possible problems"
+	# Replace dots in the name with underscores (only one dot can be there... security issue).
+	if ( Config.ForceSingleExtension ): # remove dots
+		newFileName = re.sub ( '/\\.(?![^.]*$)/', '_', newFileName ) ;
+	newFileName = newFileName.replace('\\','/')		# convert windows to unix path
+	newFileName = os.path.basename (newFileName)	# strip directories
+	# Remove \ / | : ? *
+	return re.sub ( '/\\\\|\\/|\\||\\:|\\?|\\*/', '_', newFileName )
+
+def getCurrentFolder(currentFolder):
+	if not currentFolder: 
+		currentFolder = '/' 
+
+	# Check the current folder syntax (must begin and end with a slash).
+	if (currentFolder[-1] <> "/"):
+		currentFolder += "/"
+	if (currentFolder[0] <> "/"):
+		currentFolder = "/" + currentFolder
+				
+	# Ensure the folder path has no double-slashes
+	while '//' in currentFolder:
+		currentFolder = currentFolder.replace('//','/') 
+
+	# Check for invalid folder paths (..)
+	if '..' in currentFolder:
+		return None
+
+	return currentFolder 
+
+def mapServerPath( environ, url):
+	" Emulate the asp Server.mapPath function. Given an url path return the physical directory that it corresponds to "
+	# This isn't correct but for the moment there's no other solution
+	# If this script is under a virtual directory or symlink it will detect the problem and stop
+	return combinePaths( getRootPath(environ), url )
+
+def mapServerFolder(resourceTypePath, folderPath):
+	return combinePaths ( resourceTypePath  , folderPath ) 
+
+def getRootPath(environ):
+	"Purpose: returns the root path on the server"
+	# WARNING: this may not be thread safe, and doesn't work w/ VirtualServer/mod_python
+	# Use Config.UserFilesAbsolutePath instead
+
+	if environ.has_key('DOCUMENT_ROOT'):
+		return environ['DOCUMENT_ROOT']
+	else:
+		realPath = os.path.realpath( './' ) 
+		selfPath = environ['SCRIPT_FILENAME']
+		selfPath = selfPath [ :  selfPath.rfind( '/'  ) ] 		
+		selfPath = selfPath.replace( '/', os.path.sep) 
+		
+		position = realPath.find(selfPath) 
+
+		# This can check only that this script isn't run from a virtual dir
+		# But it avoids the problems that arise if it isn't checked
+		raise realPath 
+		if ( position < 0 or position <> len(realPath) - len(selfPath) or realPath[ : position ]==''):
+			raise Exception('Sorry, can\'t map "UserFilesPath" to a physical path. You must set the "UserFilesAbsolutePath" value in "editor/filemanager/connectors/py/config.py".')
+		return realPath[ : position ]
+
Index: /FCKeditor/trunk/editor/filemanager/connectors/py/htaccess.txt
===================================================================
--- /FCKeditor/trunk/editor/filemanager/connectors/py/htaccess.txt	(revision 591)
+++ /FCKeditor/trunk/editor/filemanager/connectors/py/htaccess.txt	(revision 591)
@@ -0,0 +1,23 @@
+# replace the name of this file to .htaccess (if using apache), 
+# and set the proper options and paths according your enviroment
+
+Allow from all
+
+# If using mod_python uncomment this:
+PythonPath "[r'C:\Archivos de programa\Apache Software Foundation\Apache2.2\htdocs\fckeditor\editor\filemanager\connectors\py'] + sys.path"
+
+
+# Recomended: WSGI application running with mod_python and modpython_gateway
+SetHandler python-program
+PythonHandler modpython_gateway::handler
+PythonOption wsgi.application wsgi::App
+
+
+# Emulated CGI with mod_python and cgihandler
+#AddHandler mod_python .py
+#PythonHandler mod_python.cgihandler
+
+
+# Plain old CGI
+#Options +ExecCGI 
+#AddHandler cgi-script py
Index: /FCKeditor/trunk/editor/filemanager/connectors/py/upload.py
===================================================================
--- /FCKeditor/trunk/editor/filemanager/connectors/py/upload.py	(revision 591)
+++ /FCKeditor/trunk/editor/filemanager/connectors/py/upload.py	(revision 591)
@@ -0,0 +1,88 @@
+#!/usr/bin/env python
+
+"""
+FCKeditor - The text editor for Internet - http://www.fckeditor.net
+Copyright (C) 2003-2007 Frederico Caldeira Knabben
+
+== BEGIN LICENSE ==
+
+Licensed under the terms of any of the following licenses at your
+choice:
+
+- GNU General Public License Version 2 or later (the "GPL")
+http://www.gnu.org/licenses/gpl.html
+
+- GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+http://www.gnu.org/licenses/lgpl.html
+
+- Mozilla Public License Version 1.1 or later (the "MPL")
+http://www.mozilla.org/MPL/MPL-1.1.html
+
+== END LICENSE ==
+
+This is the "File Uploader" for Python
+
+"""
+import os
+
+from fckutil import *
+from fckcommands import * 	# default command's implementation
+from fckconnector import FCKeditorConnectorBase # import base connector
+import config as Config
+
+class FCKeditorQuickUpload(	FCKeditorConnectorBase,
+							UploadFileCommandMixin, 
+							BaseHttpMixin, BaseHtmlMixin):	
+	def doResponse(self):
+		"Main function. Process the request, set headers and return a string as response."
+		# Check if this connector is disabled
+		if not(Config.Enabled):
+			return self.sendUploadResults(1, "This file uploader is disabled. Please check the \"editor/filemanager/connectors/py/config.py\"")
+		command = 'QuickUpload'
+		# The file type (from the QueryString, by default 'File').
+		resourceType  = self.request.get('Type','File')
+		currentFolder = getCurrentFolder(self.request.get("CurrentFolder",""))
+		# Check for invalid paths
+		if currentFolder is None:
+			return self.sendUploadResults(102, '', '', "")
+
+		# Check if it is an allowed command
+		if ( not command in Config.ConfigAllowedCommands ):
+			return self.sendUploadResults( 1, '', '', 'The %s command isn\'t allowed' % command ) 
+		
+		if ( not resourceType in Config.ConfigAllowedTypes  ):
+			return self.sendUploadResults( 1, '', '', 'Invalid type specified' ) 
+
+		# Setup paths
+		self.userFilesFolder = Config.QuickUploadAbsolutePath[resourceType] 
+		self.webUserFilesFolder =  Config.QuickUploadPath[resourceType]	
+		if not self.userFilesFolder: # no absolute path given (dangerous...)
+			self.userFilesFolder = mapServerPath(self.environ, 
+									self.webUserFilesFolder)
+		
+		# Ensure that the directory exists.
+		if not os.path.exists(self.userFilesFolder):
+			try:
+				self.createServerFoldercreateServerFolder( self.userFilesFolder ) 
+			except:
+				return self.sendError(1, "This connector couldn\'t access to local user\'s files directories.  Please check the UserFilesAbsolutePath in \"editor/filemanager/connectors/py/config.py\" and try again. ")			
+
+		# File upload doesn't have to return XML, so intercept here
+		return self.uploadFile(resourceType, currentFolder)
+
+# Running from command line (plain old CGI)
+if __name__ == '__main__':
+	try:
+		# Create a Connector Instance
+		conn = FCKeditorQuickUpload()
+		data = conn.doResponse()
+		for header in conn.headers:
+			if not header is None:
+				print '%s: %s' % header
+		print 
+		print data
+	except:
+		print "Content-Type: text/plain"
+		print
+		import cgi
+		cgi.print_exception()
Index: /FCKeditor/trunk/editor/filemanager/connectors/py/wsgi.py
===================================================================
--- /FCKeditor/trunk/editor/filemanager/connectors/py/wsgi.py	(revision 591)
+++ /FCKeditor/trunk/editor/filemanager/connectors/py/wsgi.py	(revision 591)
@@ -0,0 +1,58 @@
+#!/usr/bin/env python
+
+"""
+FCKeditor - The text editor for Internet - http://www.fckeditor.net
+Copyright (C) 2003-2007 Frederico Caldeira Knabben
+
+== BEGIN LICENSE ==
+
+Licensed under the terms of any of the following licenses at your
+choice:
+
+ - GNU General Public License Version 2 or later (the "GPL")
+   http://www.gnu.org/licenses/gpl.html
+
+ - GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+   http://www.gnu.org/licenses/lgpl.html
+
+ - Mozilla Public License Version 1.1 or later (the "MPL")
+   http://www.mozilla.org/MPL/MPL-1.1.html
+
+== END LICENSE ==
+
+Connector/QuickUpload for Python (WSGI wrapper).
+
+See config.py for configuration settings
+
+"""
+
+from connector import FCKeditorConnector
+from upload import FCKeditorQuickUpload
+
+import cgitb
+from cStringIO import StringIO
+
+# Running from WSGI capable server (recomended)
+def App(environ, start_response): 
+	"WSGI entry point. Run the connector"
+	if environ['SCRIPT_NAME'].endswith("connector.py"):
+		conn = FCKeditorConnector(environ)
+	elif environ['SCRIPT_NAME'].endswith("upload.py"):
+		conn = FCKeditorQuickUpload(environ)
+	else:
+		start_response ("200 Ok", [('Content-Type','text/html')])
+		yield "Unknown page requested: "
+		yield environ['SCRIPT_NAME']
+		return
+	try:
+		# run the connector
+		data = conn.doResponse()
+		# Start WSGI response:
+		start_response ("200 Ok", conn.headers)
+		# Send response text
+		yield data
+	except:
+		start_response("500 Internal Server Error",[("Content-type","text/html")])
+		file = StringIO()
+		cgitb.Hook(file = file).handle()    
+		yield file.getvalue()
Index: /FCKeditor/trunk/editor/filemanager/connectors/py/zope.py
===================================================================
--- /FCKeditor/trunk/editor/filemanager/connectors/py/zope.py	(revision 591)
+++ /FCKeditor/trunk/editor/filemanager/connectors/py/zope.py	(revision 591)
@@ -0,0 +1,189 @@
+#!/usr/bin/env python
+
+"""
+FCKeditor - The text editor for Internet - http://www.fckeditor.net
+Copyright (C) 2003-2007 Frederico Caldeira Knabben
+
+== BEGIN LICENSE ==
+
+Licensed under the terms of any of the following licenses at your
+choice:
+
+- GNU General Public License Version 2 or later (the "GPL")
+http://www.gnu.org/licenses/gpl.html
+
+- GNU Lesser General Public License Version 2.1 or later (the "LGPL")
+http://www.gnu.org/licenses/lgpl.html
+
+- Mozilla Public License Version 1.1 or later (the "MPL")
+http://www.mozilla.org/MPL/MPL-1.1.html
+
+== END LICENSE ==
+
+Connector for Python and Zope.
+
+This code was not tested at all.
+It just was ported from pre 2.5 release, so for further reference see 
+\editor\filemanager\browser\default\connectors\py\connector.py in previous 
+releases.
+
+"""
+
+from fckutil import *
+from connector import *
+import config as Config
+
+class FCKeditorConnectorZope(FCKeditorConnector):
+	"""
+	Zope versiof FCKeditorConnector
+	"""
+	# Allow access (Zope)
+	__allow_access_to_unprotected_subobjects__ = 1
+
+	def __init__(self, context=None):
+		"""
+		Constructor
+		"""
+		FCKeditorConnector.__init__(self, environ=None) # call superclass constructor
+		# Instance Attributes
+		self.context = context
+		self.request = FCKeditorRequest(context)
+	
+	def getZopeRootContext(self):
+		if self.zopeRootContext is None:
+			self.zopeRootContext = self.context.getPhysicalRoot()
+		return self.zopeRootContext
+
+	def getZopeUploadContext(self):
+		if self.zopeUploadContext is None:
+			folderNames = self.userFilesFolder.split("/")
+			c = self.getZopeRootContext()
+			for folderName in folderNames:
+				if (folderName <> ""):
+					c = c[folderName]
+			self.zopeUploadContext = c
+		return self.zopeUploadContext
+
+	def setHeader(self, key, value):
+		self.context.REQUEST.RESPONSE.setHeader(key, value)
+
+	def getFolders(self, resourceType, currentFolder):
+		# Open the folders node
+		s = ""
+		s += """<Folders>"""
+		zopeFolder = self.findZopeFolder(resourceType, currentFolder)
+		for (name, o) in zopeFolder.objectItems(["Folder"]):
+			s += """<Folder name="%s" />""" % (
+					convertToXmlAttribute(name)
+					)
+		# Close the folders node
+		s += """</Folders>"""
+		return s
+
+	def getZopeFoldersAndFiles(self, resourceType, currentFolder):
+		folders = self.getZopeFolders(resourceType, currentFolder)
+		files = self.getZopeFiles(resourceType, currentFolder)
+		s = folders + files
+		return s
+
+	def getZopeFiles(self, resourceType, currentFolder):
+		# Open the files node
+		s = ""
+		s += """<Files>"""
+		zopeFolder = self.findZopeFolder(resourceType, currentFolder)
+		for (name, o) in zopeFolder.objectItems(["File","Image"]):
+			s += """<File name="%s" size="%s" />""" % (
+					convertToXmlAttribute(name),
+					((o.get_size() / 1024) + 1)
+					)
+		# Close the files node
+		s += """</Files>"""
+		return s
+
+	def findZopeFolder(self, resourceType, folderName):
+		# returns the context of the resource / folder
+		zopeFolder = self.getZopeUploadContext()
+		folderName = self.removeFromStart(folderName, "/")
+		folderName = self.removeFromEnd(folderName, "/")
+		if (resourceType <> ""):
+			try:
+				zopeFolder = zopeFolder[resourceType]
+			except:
+				zopeFolder.manage_addProduct["OFSP"].manage_addFolder(id=resourceType, title=resourceType)
+				zopeFolder = zopeFolder[resourceType]
+		if (folderName <> ""):
+			folderNames = folderName.split("/")
+			for folderName in folderNames:
+				zopeFolder = zopeFolder[folderName]
+		return zopeFolder
+
+	def createFolder(self, resourceType, currentFolder):
+		# Find out where we are
+		zopeFolder = self.findZopeFolder(resourceType, currentFolder)
+		errorNo = 0
+		errorMsg = ""
+		if self.request.has_key("NewFolderName"):
+			newFolder = self.request.get("NewFolderName", None)
+			zopeFolder.manage_addProduct["OFSP"].manage_addFolder(id=newFolder, title=newFolder)
+		else:
+			errorNo = 102
+		return self.sendErrorNode ( errorNo, errorMsg )
+
+	def uploadFile(self, resourceType, currentFolder, count=None):
+		zopeFolder = self.findZopeFolder(resourceType, currentFolder)
+		file = self.request.get("NewFile", None)
+		fileName = self.getFileName(file.filename)
+		fileNameOnly = self.removeExtension(fileName)
+		fileExtension = self.getExtension(fileName).lower()
+		if (count):
+			nid = "%s.%s.%s" % (fileNameOnly, count, fileExtension)
+		else:
+			nid = fileName
+		title = nid
+		try:
+			zopeFolder.manage_addProduct['OFSP'].manage_addFile(
+					id=nid,
+					title=title,
+					file=file.read()
+					)
+		except:
+			if (count):
+				count += 1
+			else:
+				count = 1
+			return self.zopeFileUpload(resourceType, currentFolder, count)
+		return self.sendUploadResults( 0 )
+
+class FCKeditorRequest(object):
+	"A wrapper around the request object"
+	def __init__(self, context=None):
+		r = context.REQUEST
+		self.request = r
+
+	def has_key(self, key):
+		return self.request.has_key(key)
+
+	def get(self, key, default=None):
+		return self.request.get(key, default)
+
+"""
+Running from zope, you will need to modify this connector.
+If you have uploaded the FCKeditor into Zope (like me), you need to
+move this connector out of Zope, and replace the "connector" with an
+alias as below.  The key to it is to pass the Zope context in, as
+we then have a like to the Zope context.
+
+## Script (Python) "connector.py"
+##bind container=container
+##bind context=context
+##bind namespace=
+##bind script=script
+##bind subpath=traverse_subpath
+##parameters=*args, **kws
+##title=ALIAS
+##
+
+import Products.zope as connector
+return connector.FCKeditorConnectorZope(context=context).doResponse()
+"""
+
Index: /FCKeditor/trunk/editor/filemanager/connectors/uploadtest.html
===================================================================
--- /FCKeditor/trunk/editor/filemanager/connectors/uploadtest.html	(revision 590)
+++ /FCKeditor/trunk/editor/filemanager/connectors/uploadtest.html	(revision 591)
@@ -94,4 +94,5 @@
 									<option value="perl/upload.cgi">Perl</option>
 									<option value="php/upload.php">PHP</option>
+									<option value="py/upload.py">Python</option>
 									<option value="">(Custom)</option>
 								</select>
Index: /FCKeditor/trunk/fckconfig.js
===================================================================
--- /FCKeditor/trunk/fckconfig.js	(revision 590)
+++ /FCKeditor/trunk/fckconfig.js	(revision 591)
@@ -189,5 +189,5 @@
 // Custom implementations should just ignore it.
 var _FileBrowserLanguage	= 'php' ;	// asp | aspx | cfm | lasso | perl | php | py
-var _QuickUploadLanguage	= 'php' ;	// asp | aspx | cfm | lasso | perl | php
+var _QuickUploadLanguage	= 'php' ;	// asp | aspx | cfm | lasso | perl | php | py
 
 // Don't care about the following line. It just calculates the correct connector
