4
FCKeditor - The text editor for internet
5
Copyright (C) 2003-2005 Frederico Caldeira Knabben
7
Licensed under the terms of the GNU Lesser General Public License:
8
http://www.opensource.org/licenses/lgpl-license.php
10
For further information visit:
11
http://www.fckeditor.net/
13
"Support Open Source software. What about a donation today?"
15
File Name: connector.py
22
Zope Version: (Zope 2.8.1-final, python 2.3.5, linux2)
23
Python Version: 2.3.5 (#4, Mar 10 2005, 01:40:25)
24
[GCC 3.3.3 20040412 (Red Hat Linux 3.3.3-7)]
25
System Platform: linux2
28
Andrew Liu (andrew@liuholdings.com)
32
Author Notes (04 December 2005):
33
This module has gone through quite a few phases of change. Obviously,
34
I am only supporting that part of the code that I use. Initially
35
I had the upload directory as a part of zope (ie. uploading files
36
directly into Zope), before realising that there were too many
37
complex intricacies within Zope to deal with. Zope is one ugly piece
38
of code. So I decided to complement Zope by an Apache server (which
39
I had running anyway, and doing nothing). So I mapped all uploads
40
from an arbitrary server directory to an arbitrary web directory.
41
All the FCKeditor uploading occurred this way, and I didn't have to
42
stuff around with fiddling with Zope objects and the like (which are
43
terribly complex and something you don't want to do - trust me).
45
Maybe a Zope expert can touch up the Zope components. In the end,
46
I had FCKeditor loaded in Zope (probably a bad idea as well), and
47
I replaced the connector.py with an alias to a server module.
48
Right now, all Zope components will simple remain as is because
49
I've had enough of Zope.
51
See notes right at the end of this file for how I aliased out of Zope.
53
Anyway, most of you probably wont use Zope, so things are pretty
54
simple in that regard.
56
Typically, SERVER_DIR is the root of WEB_DIR (not necessarily).
57
Most definitely, SERVER_USERFILES_DIR points to WEB_USERFILES_DIR.
68
Converts the special characters '<', '>', and '&'.
70
RFC 1866 specifies that these characters be represented
71
in HTML as < > and & respectively. In Python
72
1.5 we use the new string.replace() function for speed.
74
def escape(text, replace=string.replace):
75
text = replace(text, '&', '&') # must be done 1st
76
text = replace(text, '<', '<')
77
text = replace(text, '>', '>')
78
text = replace(text, '"', '"')
84
Creates a new instance of an FCKeditorConnector, and runs it
86
def getFCKeditorConnector(context=None):
87
# Called from Zope. Passes the context through
88
connector = FCKeditorConnector(context=context)
89
return connector.run()
95
A wrapper around the request object
96
Can handle normal CGI request, or a Zope request
99
class FCKeditorRequest(object):
100
def __init__(self, context=None):
101
if (context is not None):
104
r = cgi.FieldStorage()
105
self.context = context
109
if (self.context is not None):
113
def has_key(self, key):
114
return self.request.has_key(key)
116
def get(self, key, default=None):
119
value = self.request.get(key, default)
121
if key in self.request.keys():
122
value = self.request[key].value
132
class FCKeditorConnector(object):
133
# Configuration for FCKEditor
134
# can point to another server here, if linked correctly
135
#WEB_HOST = "http://127.0.0.1/"
137
SERVER_DIR = "/var/www/html/"
139
WEB_USERFILES_FOLDER = WEB_HOST + "upload/"
140
SERVER_USERFILES_FOLDER = SERVER_DIR + "upload/"
142
# Allow access (Zope)
143
__allow_access_to_unprotected_subobjects__ = 1
145
parentFolderRe = re.compile("[\/][^\/]+[\/]?$")
150
def __init__(self, context=None):
151
# The given root path will NOT be shown to the user
152
# Only the userFilesPath will be shown
154
# Instance Attributes
155
self.context = context
156
self.request = FCKeditorRequest(context=context)
157
self.rootPath = self.SERVER_DIR
158
self.userFilesFolder = self.SERVER_USERFILES_FOLDER
159
self.webUserFilesFolder = self.WEB_USERFILES_FOLDER
161
# Enables / Disables the connector
162
self.enabled = False # Set to True to enable this connector
164
# These are instance variables
165
self.zopeRootContext = None
166
self.zopeUploadContext = None
168
# Copied from php module =)
169
self.allowedExtensions = {
175
self.deniedExtensions = {
176
"File": [ "php", "php3", "php5", "phtml", "asp", "aspx", "ascx", "jsp", "cfm", "cfc", "pl", "bat", "exe", "dll", "reg", "cgi" ],
177
"Image": [ "php", "php3", "php5", "phtml", "asp", "aspx", "ascx", "jsp", "cfm", "cfc", "pl", "bat", "exe", "dll", "reg", "cgi" ],
178
"Flash": [ "php", "php3", "php5", "phtml", "asp", "aspx", "ascx", "jsp", "cfm", "cfc", "pl", "bat", "exe", "dll", "reg", "cgi" ],
179
"Media": [ "php", "php3", "php5", "phtml", "asp", "aspx", "ascx", "jsp", "cfm", "cfc", "pl", "bat", "exe", "dll", "reg", "cgi" ]
183
Zope specific functions
186
# The context object is the zope object
187
if (self.context is not None):
191
def getZopeRootContext(self):
192
if self.zopeRootContext is None:
193
self.zopeRootContext = self.context.getPhysicalRoot()
194
return self.zopeRootContext
196
def getZopeUploadContext(self):
197
if self.zopeUploadContext is None:
198
folderNames = self.userFilesFolder.split("/")
199
c = self.getZopeRootContext()
200
for folderName in folderNames:
201
if (folderName <> ""):
203
self.zopeUploadContext = c
204
return self.zopeUploadContext
207
Generic manipulation functions
209
def getUserFilesFolder(self):
210
return self.userFilesFolder
212
def getWebUserFilesFolder(self):
213
return self.webUserFilesFolder
215
def getAllowedExtensions(self, resourceType):
216
return self.allowedExtensions[resourceType]
218
def getDeniedExtensions(self, resourceType):
219
return self.deniedExtensions[resourceType]
221
def removeFromStart(self, string, char):
222
return string.lstrip(char)
224
def removeFromEnd(self, string, char):
225
return string.rstrip(char)
227
def convertToXmlAttribute(self, value):
232
def convertToPath(self, path):
233
if (path[-1] <> "/"):
238
def getUrlFromPath(self, resourceType, path):
239
if (resourceType is None) or (resourceType == ''):
241
self.removeFromEnd(self.getUserFilesFolder(), '/'),
246
self.getUserFilesFolder(),
252
def getWebUrlFromPath(self, resourceType, path):
253
if (resourceType is None) or (resourceType == ''):
255
self.removeFromEnd(self.getWebUserFilesFolder(), '/'),
260
self.getWebUserFilesFolder(),
266
def removeExtension(self, fileName):
267
index = fileName.rindex(".")
268
newFileName = fileName[0:index]
271
def getExtension(self, fileName):
272
index = fileName.rindex(".") + 1
273
fileExtension = fileName[index:]
276
def getParentFolder(self, folderPath):
277
parentFolderPath = self.parentFolderRe.sub('', folderPath)
278
return parentFolderPath
283
Purpose: works out the folder map on the server
285
def serverMapFolder(self, resourceType, folderPath):
286
# Get the resource type directory
287
resourceTypeFolder = "%s%s/" % (
288
self.getUserFilesFolder(),
291
# Ensure that the directory exists
292
self.createServerFolder(resourceTypeFolder)
294
# Return the resource type directory combined with the
298
self.removeFromStart(folderPath, '/')
304
Purpose: physically creates a folder on the server
306
def createServerFolder(self, folderPath):
307
# Check if the parent exists
308
parentFolderPath = self.getParentFolder(folderPath)
309
if not(os.path.exists(parentFolderPath)):
310
errorMsg = self.createServerFolder(parentFolderPath)
311
if errorMsg is not None:
313
# Check if this exists
314
if not(os.path.exists(folderPath)):
316
os.chmod(folderPath, 0755)
319
if os.path.isdir(folderPath):
322
raise "createServerFolder: Non-folder of same name already exists"
329
Purpose: returns the root path on the server
331
def getRootPath(self):
337
Purpose: to prepare the headers for the xml to return
339
def setXmlHeaders(self):
340
#now = self.context.BS_get_now()
342
self.setHeader("Content-Type", "text/xml")
343
#self.setHeader("Expires", yesterday)
344
#self.setHeader("Last-Modified", now)
345
#self.setHeader("Cache-Control", "no-store, no-cache, must-revalidate")
349
def setHeader(self, key, value):
351
self.context.REQUEST.RESPONSE.setHeader(key, value)
353
print "%s: %s" % (key, value)
356
def printHeaders(self):
357
# For non-Zope requests, we need to print an empty line
358
# to denote the end of headers
359
if (not(self.isZope())):
365
Purpose: returns the xml header
367
def createXmlHeader(self, command, resourceType, currentFolder):
370
# Create the XML document header
371
s += """<?xml version="1.0" encoding="utf-8" ?>"""
372
# Create the main connector node
373
s += """<Connector command="%s" resourceType="%s">""" % (
377
# Add the current folder node
378
s += """<CurrentFolder path="%s" url="%s" />""" % (
379
self.convertToXmlAttribute(currentFolder),
380
self.convertToXmlAttribute(
381
self.getWebUrlFromPath(
392
Purpose: returns the xml footer
394
def createXmlFooter(self):
395
s = """</Connector>"""
401
Purpose: in the event of an error, return an xml based error
403
def sendError(self, number, text):
406
# Create the XML document header
407
s += """<?xml version="1.0" encoding="utf-8" ?>"""
408
s += """<Connector>"""
409
s += """<Error number="%s" text="%s" />""" % (number, text)
410
s += """</Connector>"""
416
Purpose: command to recieve a list of folders
418
def getFolders(self, resourceType, currentFolder):
420
return self.getZopeFolders(resourceType, currentFolder)
422
return self.getNonZopeFolders(resourceType, currentFolder)
424
def getZopeFolders(self, resourceType, currentFolder):
425
# Open the folders node
428
zopeFolder = self.findZopeFolder(resourceType, currentFolder)
429
for (name, o) in zopeFolder.objectItems(["Folder"]):
430
s += """<Folder name="%s" />""" % (
431
self.convertToXmlAttribute(name)
433
# Close the folders node
434
s += """</Folders>"""
437
def getNonZopeFolders(self, resourceType, currentFolder):
438
# Map the virtual path to our local server
439
serverPath = self.serverMapFolder(resourceType, currentFolder)
440
# Open the folders node
443
for someObject in os.listdir(serverPath):
444
someObjectPath = os.path.join(serverPath, someObject)
445
if os.path.isdir(someObjectPath):
446
s += """<Folder name="%s" />""" % (
447
self.convertToXmlAttribute(someObject)
449
# Close the folders node
450
s += """</Folders>"""
456
Purpose: command to recieve a list of folders and files
458
def getFoldersAndFiles(self, resourceType, currentFolder):
460
return self.getZopeFoldersAndFiles(resourceType, currentFolder)
462
return self.getNonZopeFoldersAndFiles(resourceType, currentFolder)
464
def getNonZopeFoldersAndFiles(self, resourceType, currentFolder):
465
# Map the virtual path to our local server
466
serverPath = self.serverMapFolder(resourceType, currentFolder)
467
# Open the folders / files node
468
folders = """<Folders>"""
469
files = """<Files>"""
470
for someObject in os.listdir(serverPath):
471
someObjectPath = os.path.join(serverPath, someObject)
472
if os.path.isdir(someObjectPath):
473
folders += """<Folder name="%s" />""" % (
474
self.convertToXmlAttribute(someObject)
476
elif os.path.isfile(someObjectPath):
477
size = os.path.getsize(someObjectPath)
478
files += """<File name="%s" size="%s" />""" % (
479
self.convertToXmlAttribute(someObject),
480
os.path.getsize(someObjectPath)
482
# Close the folders / files node
483
folders += """</Folders>"""
484
files += """</Files>"""
489
def getZopeFoldersAndFiles(self, resourceType, currentFolder):
490
folders = self.getZopeFolders(resourceType, currentFolder)
491
files = self.getZopeFiles(resourceType, currentFolder)
495
def getZopeFiles(self, resourceType, currentFolder):
496
# Open the files node
499
zopeFolder = self.findZopeFolder(resourceType, currentFolder)
500
for (name, o) in zopeFolder.objectItems(["File","Image"]):
501
s += """<File name="%s" size="%s" />""" % (
502
self.convertToXmlAttribute(name),
503
((o.get_size() / 1024) + 1)
505
# Close the files node
509
def findZopeFolder(self, resourceType, folderName):
510
# returns the context of the resource / folder
511
zopeFolder = self.getZopeUploadContext()
512
folderName = self.removeFromStart(folderName, "/")
513
folderName = self.removeFromEnd(folderName, "/")
514
if (resourceType <> ""):
516
zopeFolder = zopeFolder[resourceType]
518
zopeFolder.manage_addProduct["OFSP"].manage_addFolder(id=resourceType, title=resourceType)
519
zopeFolder = zopeFolder[resourceType]
520
if (folderName <> ""):
521
folderNames = folderName.split("/")
522
for folderName in folderNames:
523
zopeFolder = zopeFolder[folderName]
529
Purpose: command to create a new folder
531
def createFolder(self, resourceType, currentFolder):
533
return self.createZopeFolder(resourceType, currentFolder)
535
return self.createNonZopeFolder(resourceType, currentFolder)
537
def createZopeFolder(self, resourceType, currentFolder):
538
# Find out where we are
539
zopeFolder = self.findZopeFolder(resourceType, currentFolder)
542
if self.request.has_key("NewFolderName"):
543
newFolder = self.request.get("NewFolderName", None)
544
zopeFolder.manage_addProduct["OFSP"].manage_addFolder(id=newFolder, title=newFolder)
547
error = """<Error number="%s" originalDescription="%s" />""" % (
549
self.convertToXmlAttribute(errorMsg)
553
def createNonZopeFolder(self, resourceType, currentFolder):
556
if self.request.has_key("NewFolderName"):
557
newFolder = self.request.get("NewFolderName", None)
558
currentFolderPath = self.serverMapFolder(
563
newFolderPath = currentFolderPath + newFolder
564
errorMsg = self.createServerFolder(newFolderPath)
565
if (errorMsg is not None):
571
error = """<Error number="%s" originalDescription="%s" />""" % (
573
self.convertToXmlAttribute(errorMsg)
580
Purpose: helper function to extrapolate the filename
582
def getFileName(self, filename):
583
for splitChar in ["/", "\\"]:
584
array = filename.split(splitChar)
592
Purpose: command to upload files to server
594
def fileUpload(self, resourceType, currentFolder):
596
return self.zopeFileUpload(resourceType, currentFolder)
598
return self.nonZopeFileUpload(resourceType, currentFolder)
600
def zopeFileUpload(self, resourceType, currentFolder, count=None):
601
zopeFolder = self.findZopeFolder(resourceType, currentFolder)
602
file = self.request.get("NewFile", None)
603
fileName = self.getFileName(file.filename)
604
fileNameOnly = self.removeExtension(fileName)
605
fileExtension = self.getExtension(fileName).lower()
607
nid = "%s.%s.%s" % (fileNameOnly, count, fileExtension)
612
zopeFolder.manage_addProduct['OFSP'].manage_addFile(
622
self.zopeFileUpload(resourceType, currentFolder, count)
625
def nonZopeFileUpload(self, resourceType, currentFolder):
628
if self.request.has_key("NewFile"):
629
# newFile has all the contents we need
630
newFile = self.request.get("NewFile", "")
632
newFileName = newFile.filename
633
newFileNameOnly = self.removeExtension(newFileName)
634
newFileExtension = self.getExtension(newFileName).lower()
635
allowedExtensions = self.getAllowedExtensions(resourceType)
636
deniedExtensions = self.getDeniedExtensions(resourceType)
637
if (allowedExtensions is not None):
640
if (newFileExtension in allowedExtensions):
642
elif (deniedExtensions is not None):
645
if (newFileExtension in deniedExtensions):
648
# No extension limitations
654
self.zopeFileUpload(resourceType, currentFolder)
656
# Upload to operating system
657
# Map the virtual path to the local server path
658
currentFolderPath = self.serverMapFolder(
664
newFilePath = "%s%s" % (
668
if os.path.exists(newFilePath):
670
newFilePath = "%s%s(%s).%s" % (
679
fileHandle = open(newFilePath,'w')
682
#line = newFile.file.readline()
683
line = newFile.readline()
685
fileHandle.write("%s" % line)
687
os.chmod(newFilePath, 0777)
690
newFileName = "Extension not allowed"
693
newFileName = "No File"
697
<script type="text/javascript">
698
window.parent.frames["frmUpload"].OnUploadCompleted(%s,"%s");
702
newFileName.replace('"',"'")
709
# Check if this is disabled
710
if not(self.enabled):
711
return self.sendError(1, "This connector is disabled. Please check the connector configurations and try again")
712
# Make sure we have valid inputs
714
(self.request.has_key("Command")) and
715
(self.request.has_key("Type")) and
716
(self.request.has_key("CurrentFolder"))
720
command = self.request.get("Command", None)
722
resourceType = self.request.get("Type", None)
723
# folder syntax must start and end with "/"
724
currentFolder = self.request.get("CurrentFolder", None)
725
if (currentFolder[-1] <> "/"):
727
if (currentFolder[0] <> "/"):
728
currentFolder = "/" + currentFolder
729
# Check for invalid paths
730
if (".." in currentFolder):
731
return self.sendError(102, "")
732
# File upload doesn't have to return XML, so intercept
734
if (command == "FileUpload"):
735
return self.fileUpload(resourceType, currentFolder)
737
s += self.createXmlHeader(command, resourceType, currentFolder)
738
# Execute the command
739
if (command == "GetFolders"):
741
elif (command == "GetFoldersAndFiles"):
742
f = self.getFoldersAndFiles
743
elif (command == "CreateFolder"):
744
f = self.createFolder
748
s += f(resourceType, currentFolder)
749
s += self.createXmlFooter()
754
# Running from command line
755
if __name__ == '__main__':
756
# To test the output, uncomment the standard headers
757
#print "Content-Type: text/html"
759
print getFCKeditorConnector()
762
Running from zope, you will need to modify this connector.
763
If you have uploaded the FCKeditor into Zope (like me), you need to
764
move this connector out of Zope, and replace the "connector" with an
765
alias as below. The key to it is to pass the Zope context in, as
766
we then have a like to the Zope context.
768
## Script (Python) "connector.py"
769
##bind container=container
770
##bind context=context
773
##bind subpath=traverse_subpath
774
##parameters=*args, **kws
777
import Products.connector as connector
778
return connector.getFCKeditorConnector(context=context).run()