Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Z
Zope
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
Zope
Commits
2d1d87c9
Commit
2d1d87c9
authored
Jan 22, 1999
by
Amos Latteier
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
First alpha of FTP support
parent
016fba5d
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
1402 additions
and
0 deletions
+1402
-0
ZServer/CHANGES.txt
ZServer/CHANGES.txt
+18
-0
ZServer/README.txt
ZServer/README.txt
+191
-0
ZServer/ZServerFTP.py
ZServer/ZServerFTP.py
+492
-0
lib/python/ZServer/CHANGES.txt
lib/python/ZServer/CHANGES.txt
+18
-0
lib/python/ZServer/README.txt
lib/python/ZServer/README.txt
+191
-0
lib/python/ZServer/ZServerFTP.py
lib/python/ZServer/ZServerFTP.py
+492
-0
No files found.
ZServer/CHANGES.txt
0 → 100644
View file @
2d1d87c9
ZServer Changes
This file gives information on changes made to ZServer over time,
things that need to be done before the next release and future
plans.
Releases
Zserver 1.0a1
New Features
Inital release. Includes new threaded architecture--currently
limited to a thread pool of 1. Includes preliminary FTP
support. Includes HTTP support.
Bugs Fixed
ZServer/README.txt
0 → 100644
View file @
2d1d87c9
ZServer Release 1.0a1
---------------------
Welcome to the first Zope ZServer alpha release. This release provides
a first look at Zope/Medusa integration, and introduces FTP support in
Zope.
What is ZServer?
ZServer is an integration of the Zope application server and the
Medusa information server. See the ZServer architecture document for
more information.::
http://www.zope.org/Documentation/Reference/ZServer
ZServer gives you HTTP and FTP access. In later releases it will
probably offer more protocols such as PCGI, WebDAV, etc.
What is Medusa?
Medusa is a Python server framework with uses a single threaded
asynchronous sockets approach. For more information see::
http://www.nightmare.com/medusa
There's also an interesting Medusa tutorial at::
http://www.nightmare.com:8080/nm/apps/medusa/docs/programming.html
ZServer FTP support
FTP access to Zope allows you to FTP to the Zope object hierarchy in
order to perform managerial tasks. You can:
* Navigate the object hierarchy with 'cd'
* Replace the content of Documents, Images, and Files
* Create Documents, Images, Files, Folders
* Delete any sort of object
So basically you can do more than is possible with PUT. Also, unlike
PUT, FTP gives you access to Document content. So when you download
a Document you are getting its content, not what it looks like when
it rendered.
FTP permissions
FTP support is provided for Folders, Documents, Images, and Files.
You can control access to FTP via the new 'FTP access' permission.
This permission controls the ability to 'cd' to a Folder and to
download objects. Uploading and deleting and creating objects are
controlled by existing permissions.
Properties and FTP: The next step
The next phase of FTP support will allow you to edit properties of
all Zope objects. Probably properties will be exposed via special
files which will contain an XML representation of the object's
properties. You could then download the file, edit the XML and
upload it to change the object's properties.
We do not currently have a target date for FTP property support.
It will probably need to wait until Zope has property sheets.
Differences between ZopeHTTPServer and ZServer
Both ZopeHTTPServer and ZServer are Python HTTP servers.
ZopeHTTPServer is built on the standard Python SimpleHTTPServer
framework. ZServer is built on Medusa.
ZopeHTTPServer is very limited. It can only publish one module at a
time. It can only publish via HTTP. It has no support for thread
pools. And more importantly, it is no longer being actively
developed, since its author has moved on the ZServer.
ZServer on the other hand is more complex and supports publishing
multiple modules, thread pools, and it uses a new threaded
architecture for accessing ZPublisher. Right now the thread pool is
limited to one thread, since the object database cannot yet support
concurrent access. This should change within the next few months.
How does FTP work?
The ZServer's FTP channel object translates FTP requests into
ZPublisher requests. The FTP channel then analyses the response and
formulates an appropriate FTP response. The FTP channel stores some
state such as the current working directory and the username and
password.
On the Zope side of things, the 'FTPSupport.py' module provides a
few mix-in classes for Document, Folder, and File (which Image
inherits from). These mix in classes provide directory listing and
stat-like methods. All the other FTP functions are handled by
existing methods like 'manage_delObjects', and 'PUT', etc.
Who should use ZServer?
This release is *alpha* quality. It should be used by Zope hackers.
If you are not inquisitive and self-reliant, this release will
frustrate you.
Installation
To install ZServer you need to do two things: edit the start script
and update some Zope files to introduce FTP support.
To edit the start up script, open 'start.py' in your favorite editor
and change the configuration variables. If you understand Medusa,
you can also change the rest of the script.
To enable FTP support in Zope you need to update some files in
'lib/python/OFS'. You should probably first back up your 'OFS'
directory, or at least make copies of the files that will be
replaced ('Document.py', 'Image.py', 'Folder.py'). Then copy the
contents of ZServer's 'OFS' directory to your Zope 'lib/python/OFS'
directory.
Finally make sure the shebang line is right on the start script. You
can use your own copy of Python, or the copy that came with Zope (if
you are using a binary distribution).
Now your ready to go.
Usage
To start ZServer run the start script::
./start.py
To stop the server type 'control-c'.
You should see some logging information come up on the screen.
Once you start ZServer is will publish Zope (or any Python module)
on HTTP and/or FTP. To access Zope via HTTP point your browser at
the server like so::
http://www.example.com:9673/Main
This assumes that you have chosen to put HTTP on port 9673 and that
you are publishing a module named 'Main'. Note: to publish Zope
normally you publish the 'lib/python/Main.py' module.
To access Zope via FTP you need to FTP to it at the port you set FTP
to run on. For example::
ftp www.example.com 8021
This starts and FTP session to your machine on port 8021, ZServer's
default FTP port. When you are prompted to log in you should supply
a Zope username and password. (Probably you should use an account
with the 'Manager' role, unless you have configured Zope to allow
FTP access to the 'Anonymous' role.) You can also enter 'anonymous'
and any password for anonymous FTP access. Once you have logged in
you can start issuing normal FTP commands. Right now ZServer only
supports basic FTP commands. Note: When you log in your working
directory is set to '/'. If you do not have FTP permissions in this
directory, you will need to 'cd' to a directory where you have
permissions before you can do anything.
Support
Questions and comments should go to 'support@digicool.com'.
You can report bugs and check on the status of bugs using the Zope
bug collector.::
http://www.zope.org/Collector/
License
ZServer is covered by the ZPL despite the fact that it comes with
much of the Medusa source code. The portions of Medusa that come
with ZServer are licensed under the ZPL.
Outstanding issues
PCGI support is not done yet. The FTP interface for Zope objects may
be changed, i.e. 'manage_FTPlist' and 'manage_FTPstat' maybe changed
and/or renamed. When FTP support for properties is added, this may
change a lot. WebDAV support will also probably cause a little
reorganization.
Currently ZServer's Medusa files are a bit modified from the
originals. It would be good idea to try and keep deviations to a
minimum. For this reason we have been feeding bug reports and change
requests back to Sam Rushing for inclusion in the official Medusa
souces. It is possible, however, that Medusa and ZServer will
diverge in some small respects despite our best efforts to keep them
unified. One area in particular where ZServer currently departs from
Medusa is in the use of future producers. Change is likely in
ZServer's use of future producers.
ZServer/ZServerFTP.py
0 → 100644
View file @
2d1d87c9
"""ZServer FTP Channel for use the medusa's ftp server.
"""
from
PubCore
import
handle
from
medusa.ftp_server
import
ftp_channel
,
ftp_server
from
medusa
import
asynchat
,
filesys
from
medusa.producers
import
NotReady
from
cStringIO
import
StringIO
import
string
import
os
from
regsub
import
gsub
from
base64
import
encodestring
from
mimetypes
import
guess_type
import
marshal
import
stat
import
time
class
zope_ftp_channel
(
ftp_channel
):
"Passes its commands to Zope, not a filesystem"
read_only
=
0
def
__init__
(
self
,
server
,
conn
,
addr
,
module
):
ftp_channel
.
__init__
(
self
,
server
,
conn
,
addr
)
self
.
module
=
module
self
.
userid
=
''
self
.
password
=
''
self
.
path
=
'/'
def
_get_env
(
self
):
"Returns a CGI style environment"
env
=
{}
env
[
'SCRIPT_NAME'
]
=
'/%s'
%
self
.
module
env
[
'PATH_INFO'
]
=
self
.
path
env
[
'REQUEST_METHOD'
]
=
'GET'
# XXX what should this be?
env
[
'SERVER_SOFTWARE'
]
=
self
.
server
.
SERVER_IDENT
if
self
.
userid
!=
'anonymous'
:
env
[
'HTTP_AUTHORIZATION'
]
=
'Basic %s'
%
gsub
(
'
\
012
'
,
''
,
encodestring
(
'%s:%s'
%
(
self
.
userid
,
self
.
password
)))
env
[
'BOBO_DEBUG_MODE'
]
=
'1'
env
[
'SERVER_NAME'
]
=
self
.
server
.
hostname
env
[
'SERVER_PORT'
]
=
str
(
self
.
server
.
port
)
env
[
'REMOTE_ADDR'
]
=
self
.
client_addr
[
0
]
env
[
'GATEWAY_INTERFACE'
]
=
'CGI/1.1'
# that's stretching it ;-)
# XXX etcetera -- probably set many of these at the start
return
env
def
_join_paths
(
self
,
*
args
):
path
=
apply
(
os
.
path
.
join
,
args
)
path
=
os
.
path
.
normpath
(
path
)
if
os
.
sep
!=
'/'
:
path
=
string
.
replace
(
path
,
os
.
sep
,
'/'
)
return
path
def
make_response
(
self
,
resp
):
self
.
log
(
'==> %s'
%
resp
)
return
resp
+
'
\
r
\
n
'
# Overriden async_chat methods
writable
=
asynchat
.
async_chat
.
writable_future
# Overriden ftp_channel methods
def
cmd_nlst
(
self
,
line
):
'give name list of files in directory'
self
.
push_with_producer
(
self
.
get_dir_list
(
line
,
0
))
def
cmd_list
(
self
,
line
):
'give list files in a directory'
# XXX should handle listing a file, not just a directory
# also should maybe glob, but this is hard to do...
self
.
push_with_producer
(
self
.
get_dir_list
(
line
,
1
))
def
get_dir_list
(
self
,
line
,
long
=
0
):
# we need to scan the command line for arguments to '/bin/ls'...
# XXX clean this up
if
len
(
line
)
>
1
:
args
=
string
.
split
(
line
[
1
])
else
:
args
=
[]
path_args
=
[]
for
arg
in
args
:
if
arg
[
0
]
!=
'-'
:
path_args
.
append
(
arg
)
else
:
if
'l'
in
arg
:
long
=
1
if
len
(
path_args
)
<
1
:
dir
=
'.'
else
:
dir
=
path_args
[
0
]
return
self
.
listdir
(
dir
,
long
)
def
listdir
(
self
,
path
,
long
=
0
):
env
=
self
.
_get_env
()
env
[
'PATH_INFO'
]
=
self
.
_join_paths
(
self
.
path
,
path
,
'manage_FTPlist'
)
outpipe
=
handle
(
self
.
module
,
env
,
StringIO
())
return
ResponseProducer
(
outpipe
,
self
.
_do_listdir
,(
long
,))
def
_do_listdir
(
self
,
long
,
response
):
#print "do list response", response.__dict__
code
=
response
.
headers
[
'Status'
][:
3
]
if
code
==
'200'
:
dir_list
=
''
file_infos
=
marshal
.
loads
(
response
.
content
)
if
type
(
file_infos
[
0
])
==
type
(
''
):
file_infos
=
(
file_infos
,)
if
long
:
for
id
,
stat_info
in
file_infos
:
dir_list
=
dir_list
+
filesys
.
unix_longify
(
id
,
stat_info
)
+
'
\
r
\
n
'
else
:
for
id
,
stat_info
in
file_infos
:
dir_list
=
dir_list
+
id
+
'
\
r
\
n
'
self
.
make_xmit_channel
()
self
.
client_dc
.
push
(
dir_list
)
self
.
client_dc
.
close_when_done
()
return
self
.
make_response
(
'150 Opening %s mode data connection for file list'
%
(
self
.
type_map
[
self
.
current_mode
]
)
)
elif
code
==
'401'
:
return
self
.
make_response
(
'530 Unauthorized.'
)
else
:
return
self
.
make_response
(
'550 Could not list directory.'
)
def
cmd_cwd
(
self
,
line
):
'change working directory'
# try to call manage_FTPlist on the path
env
=
self
.
_get_env
()
path
=
line
[
1
]
path
=
self
.
_join_paths
(
self
.
path
,
path
,
'manage_FTPlist'
)
env
[
'PATH_INFO'
]
=
path
outpipe
=
handle
(
self
.
module
,
env
,
StringIO
())
self
.
push_with_producer
(
ResponseProducer
(
outpipe
,
self
.
_cmd_cwd
,(
path
[:
-
15
],)))
def
_cmd_cwd
(
self
,
path
,
response
):
code
=
response
.
headers
[
'Status'
][:
3
]
if
code
==
'200'
:
listing
=
marshal
.
loads
(
response
.
content
)
# check to see if we are cding to a non-foldoid object
if
type
(
listing
[
0
])
==
type
(
''
):
return
self
.
make_response
(
'550 No such directory.'
)
self
.
path
=
path
or
'/'
return
self
.
make_response
(
'250 CWD command successful.'
)
elif
code
==
'401'
:
return
self
.
make_response
(
'530 Unauthorized.'
)
else
:
return
self
.
make_response
(
'550 No such directory.'
)
def
cmd_cdup
(
self
,
line
):
'change to parent of current working directory'
self
.
cmd_cwd
((
None
,
'..'
))
def
cmd_pwd
(
self
,
line
):
'print the current working directory'
self
.
respond
(
'257 "%s" is the current directory.'
%
(
self
.
path
)
)
cmd_xpwd
=
cmd_pwd
def
cmd_mdtm
(
self
,
line
):
'show last modification time of file'
if
len
(
line
)
!=
2
:
self
.
command
.
not_understood
(
string
.
join
(
line
))
return
env
=
self
.
_get_env
()
env
[
'PATH_INFO'
]
=
self
.
_join_paths
(
self
.
path
,
line
[
1
],
'manage_FTPstat'
)
outpipe
=
handle
(
self
.
module
,
env
,
StringIO
())
self
.
push_with_producer
(
ResponseProducer
(
outpipe
,
self
.
_cmd_mdtm
))
def
_cmd_mdtm
(
self
,
response
):
#print "mdtm response", response.__dict__
code
=
response
.
headers
[
'Status'
][:
3
]
if
code
==
'200'
:
mtime
=
marshal
.
loads
(
response
.
content
)[
stat
.
ST_MTIME
]
mtime
=
time
.
gmtime
(
mtime
)
return
self
.
make_response
(
'213 %4d%02d%02d%02d%02d%02d'
%
(
mtime
[
0
],
mtime
[
1
],
mtime
[
2
],
mtime
[
3
],
mtime
[
4
],
mtime
[
5
]
))
elif
code
==
'401'
:
return
self
.
make_response
(
'530 Unauthorized.'
)
else
:
return
self
.
make_response
(
'550 Error getting file modification time.'
)
def
cmd_size
(
self
,
line
):
'return size of file'
if
len
(
line
)
!=
2
:
self
.
command
.
not_understood
(
string
.
join
(
line
))
return
env
=
self
.
_get_env
()
env
[
'PATH_INFO'
]
=
self
.
_join_paths
(
self
.
path
,
line
[
1
],
'manage_FTPstat'
)
outpipe
=
handle
(
self
.
module
,
env
,
StringIO
())
self
.
push_with_producer
(
ResponseProducer
(
outpipe
,
self
.
_cmd_size
))
def
_cmd_size
(
self
,
response
):
#print "size response", response.__dict__
code
=
response
.
headers
[
'Status'
][:
3
]
if
code
==
'200'
:
return
self
.
make_response
(
'213 %d'
%
marshal
.
loads
(
response
.
content
)[
stat
.
ST_SIZE
])
elif
code
==
'401'
:
return
self
.
make_response
(
'530 Unauthorized.'
)
else
:
return
self
.
make_response
(
'550 Error getting file size.'
)
self
.
client_dc
.
close_when_done
()
def
cmd_retr
(
self
,
line
):
if
len
(
line
)
<
2
:
self
.
command_not_understood
(
string
.
join
(
line
))
return
env
=
self
.
_get_env
()
path
,
id
=
os
.
path
.
split
(
line
[
1
])
env
[
'PATH_INFO'
]
=
self
.
_join_paths
(
self
.
path
,
line
[
1
],
'manage_FTPget'
)
outpipe
=
handle
(
self
.
module
,
env
,
StringIO
())
self
.
push_with_producer
(
ResponseProducer
(
outpipe
,
self
.
_cmd_retr
,
(
line
[
1
],)))
def
_cmd_retr
(
self
,
file
,
response
):
#print "retr response\n", response.__dict__
code
=
response
.
headers
[
'Status'
][:
3
]
if
code
==
'200'
:
#fd=StringIO(response.content)
self
.
make_xmit_channel
()
#if self.restart_position:
# # try to position the file as requested, but
# # give up silently on failure (the 'file object'
# # may not support seek())
# try:
# fd.seek (self.restart_position)
# except:
# pass
# self.restart_position = 0
self
.
client_dc
.
push
(
response
.
content
)
self
.
client_dc
.
close_when_done
()
return
self
.
make_response
(
"150 Opening %s mode data connection for file '%s'"
%
(
self
.
type_map
[
self
.
current_mode
],
file
))
elif
code
==
'401'
:
return
self
.
make_response
(
'530 Unauthorized.'
)
else
:
return
self
.
make_response
(
'550 Error opening file.'
)
def
cmd_stor
(
self
,
line
,
mode
=
'wb'
):
'store a file'
if
len
(
line
)
<
2
:
self
.
command_not_understood
(
string
.
join
(
line
))
return
elif
self
.
restart_position
:
restart_position
=
0
self
.
respond
(
'553 restart on STOR not yet supported'
)
return
# XXX Check for possible problems first? Like authorization...
# But how? Once we agree to receive the file, can we still
# bail later?
fd
=
ContentReceiver
(
self
.
_do_cmd_stor
,
(
self
.
_join_paths
(
self
.
path
,
line
[
1
]),))
self
.
respond
(
'150 Opening %s connection for %s'
%
(
self
.
type_map
[
self
.
current_mode
],
line
[
1
]
)
)
self
.
make_recv_channel
(
fd
)
def
_do_cmd_stor
(
self
,
path
,
data
):
'callback to do the STOR, after we have the input'
#print "stor callback", path, data
env
=
self
.
_get_env
()
env
[
'PATH_INFO'
]
=
path
env
[
'REQUEST_METHOD'
]
=
'PUT'
ctype
=
guess_type
(
path
)[
0
]
if
ctype
is
not
None
:
env
[
'CONTENT_TYPE'
]
=
ctype
env
[
'CONTENT_LENGTH'
]
=
len
(
data
.
getvalue
())
outpipe
=
handle
(
self
.
module
,
env
,
data
)
self
.
push_with_producer
(
ResponseProducer
(
outpipe
,
self
.
_cmd_stor
))
def
_cmd_stor
(
self
,
response
):
code
=
response
.
headers
[
'Status'
][:
3
]
if
code
in
(
'200'
,
'204'
,
'302'
):
return
self
.
make_response
(
'257 STOR command successful.'
)
elif
code
==
'401'
:
return
self
.
make_response
(
'530 Unauthorized.'
)
else
:
return
self
.
make_response
(
'550 Error creating file.'
)
def
cmd_dele
(
self
,
line
):
if
len
(
line
)
!=
2
:
self
.
command
.
not_understood
(
string
.
join
(
line
))
return
path
,
id
=
os
.
path
.
split
(
line
[
1
])
env
=
self
.
_get_env
()
env
[
'PATH_INFO'
]
=
self
.
_join_paths
(
self
.
path
,
path
,
'manage_delObjects'
)
env
[
'QUERY_STRING'
]
=
'ids=%s'
%
id
outpipe
=
handle
(
self
.
module
,
env
,
StringIO
())
self
.
push_with_producer
(
ResponseProducer
(
outpipe
,
self
.
_cmd_dele
))
def
_cmd_dele
(
self
,
response
):
code
=
response
.
headers
[
'Status'
][:
3
]
if
code
==
'200'
and
string
.
find
(
response
.
content
,
'Not Deletable'
)
==-
1
:
return
self
.
make_response
(
'250 DELE command successful.'
)
elif
code
==
'401'
:
return
self
.
make_response
(
'530 Unauthorized.'
)
else
:
return
self
.
make_response
(
'550 Error deleting file.'
)
def
cmd_mkd
(
self
,
line
):
if
len
(
line
)
!=
2
:
self
.
command
.
not_understood
(
string
.
join
(
line
))
return
env
=
self
.
_get_env
()
path
,
id
=
os
.
path
.
split
(
line
[
1
])
env
[
'PATH_INFO'
]
=
self
.
_join_paths
(
self
.
path
,
path
,
'manage_addFolder'
)
env
[
'QUERY_STRING'
]
=
'id=%s'
%
id
outpipe
=
handle
(
self
.
module
,
env
,
StringIO
())
self
.
push_with_producer
(
ResponseProducer
(
outpipe
,
self
.
_cmd_mkd
))
cmd_xmkd
=
cmd_mkd
def
_cmd_mkd
(
self
,
response
):
#print "mkd response", response.__dict__
code
=
response
.
headers
[
'Status'
][:
3
]
if
code
==
'200'
:
return
self
.
make_response
(
'257 MKD command successful.'
)
elif
code
==
'401'
:
return
self
.
make_response
(
'530 Unauthorized.'
)
else
:
return
self
.
make_response
(
'550 Error creating directory.'
)
def
cmd_rmd
(
self
,
line
):
# XXX should object be checked to see if it's folderish
# before we allow it to be RMD'd?
if
len
(
line
)
!=
2
:
self
.
command
.
not_understood
(
string
.
join
(
line
))
return
path
,
id
=
os
.
path
.
split
(
line
[
1
])
env
=
self
.
_get_env
()
env
[
'PATH_INFO'
]
=
self
.
_join_paths
(
self
.
path
,
path
,
'manage_delObjects'
)
env
[
'QUERY_STRING'
]
=
'ids=%s'
%
id
outpipe
=
handle
(
self
.
module
,
env
,
StringIO
())
self
.
push_with_producer
(
ResponseProducer
(
outpipe
,
self
.
_cmd_rmd
))
cmd_xrmd
=
cmd_rmd
def
_cmd_rmd
(
self
,
response
):
#print "rmd response", response.__dict__
code
=
response
.
headers
[
'Status'
][:
3
]
if
code
==
'200'
and
string
.
find
(
response
.
content
,
'Not Deletable'
)
==-
1
:
return
self
.
make_response
(
'250 RMD command successful.'
)
elif
code
==
'401'
:
return
self
.
make_response
(
'530 Unauthorized.'
)
else
:
return
self
.
make_response
(
'550 Error removing directory.'
)
def
cmd_user
(
self
,
line
):
'specify user name'
if
len
(
line
)
>
1
:
self
.
userid
=
line
[
1
]
self
.
respond
(
'331 Password required.'
)
else
:
self
.
command_not_understood
(
string
.
join
(
line
))
def
cmd_pass
(
self
,
line
):
'specify password'
if
len
(
line
)
<
2
:
pw
=
''
else
:
pw
=
line
[
1
]
self
.
password
=
pw
self
.
respond
(
'230 Login successful.'
)
self
.
authorized
=
1
self
.
log
(
'Successful login.'
)
class
ZResponseReceiver
:
"""Given an output pipe reads response and parses it.
After a call to ready returns true, you can read
the headers as a dictiony and the content as a string."""
def
__init__
(
self
,
pipe
):
self
.
pipe
=
pipe
self
.
data
=
''
self
.
headers
=
{}
self
.
content
=
''
def
ready
(
self
):
if
self
.
pipe
is
None
:
return
1
if
self
.
pipe
.
ready
():
data
=
self
.
pipe
.
read
()
if
data
:
self
.
data
=
self
.
data
+
data
else
:
self
.
parse
()
return
1
def
parse
(
self
):
headers
,
html
=
string
.
split
(
self
.
data
,
'
\
n
\
n
'
,
1
)
self
.
data
=
''
for
header
in
string
.
split
(
headers
,
'
\
n
'
):
k
,
v
=
string
.
split
(
header
,
': '
,
1
)
self
.
headers
[
k
]
=
v
self
.
content
=
html
self
.
pipe
=
None
class
ResponseProducer
:
"Allows responses which need to make Zope requests first."
def
__init__
(
self
,
pipe
,
callback
,
args
=
None
):
self
.
response
=
ZResponseReceiver
(
pipe
)
self
.
callback
=
callback
self
.
args
=
args
or
()
self
.
done
=
None
def
ready
(
self
):
if
self
.
response
is
not
None
:
return
self
.
response
.
ready
()
else
:
return
1
def
more
(
self
):
if
not
self
.
done
:
if
not
self
.
response
.
ready
():
raise
NotReady
()
self
.
done
=
1
r
=
self
.
response
c
=
self
.
callback
args
=
self
.
args
+
(
r
,)
self
.
response
=
None
self
.
callback
=
None
self
.
args
=
None
return
apply
(
c
,
args
)
else
:
return
''
class
ContentReceiver
:
"Write-only file object used to receive data from FTP"
def
__init__
(
self
,
callback
,
args
=
None
):
self
.
data
=
StringIO
()
self
.
callback
=
callback
self
.
args
=
args
or
()
def
write
(
self
,
data
):
self
.
data
.
write
(
data
)
def
close
(
self
):
self
.
data
.
seek
(
0
)
args
=
self
.
args
+
(
self
.
data
,)
c
=
self
.
callback
self
.
callback
=
None
self
.
args
=
None
apply
(
c
,
args
)
class
zope_ftp_server
(
ftp_server
):
ftp_channel_class
=
zope_ftp_channel
def
__init__
(
self
,
module
,
hostname
,
port
,
resolver
,
logger_object
):
ftp_server
.
__init__
(
self
,
None
,
hostname
,
port
,
resolver
,
logger_object
)
self
.
module
=
module
def
handle_accept
(
self
):
conn
,
addr
=
self
.
accept
()
self
.
total_sessions
.
increment
()
print
'Incoming connection from %s:%d'
%
(
addr
[
0
],
addr
[
1
])
self
.
ftp_channel_class
(
self
,
conn
,
addr
,
self
.
module
)
\ No newline at end of file
lib/python/ZServer/CHANGES.txt
0 → 100644
View file @
2d1d87c9
ZServer Changes
This file gives information on changes made to ZServer over time,
things that need to be done before the next release and future
plans.
Releases
Zserver 1.0a1
New Features
Inital release. Includes new threaded architecture--currently
limited to a thread pool of 1. Includes preliminary FTP
support. Includes HTTP support.
Bugs Fixed
lib/python/ZServer/README.txt
0 → 100644
View file @
2d1d87c9
ZServer Release 1.0a1
---------------------
Welcome to the first Zope ZServer alpha release. This release provides
a first look at Zope/Medusa integration, and introduces FTP support in
Zope.
What is ZServer?
ZServer is an integration of the Zope application server and the
Medusa information server. See the ZServer architecture document for
more information.::
http://www.zope.org/Documentation/Reference/ZServer
ZServer gives you HTTP and FTP access. In later releases it will
probably offer more protocols such as PCGI, WebDAV, etc.
What is Medusa?
Medusa is a Python server framework with uses a single threaded
asynchronous sockets approach. For more information see::
http://www.nightmare.com/medusa
There's also an interesting Medusa tutorial at::
http://www.nightmare.com:8080/nm/apps/medusa/docs/programming.html
ZServer FTP support
FTP access to Zope allows you to FTP to the Zope object hierarchy in
order to perform managerial tasks. You can:
* Navigate the object hierarchy with 'cd'
* Replace the content of Documents, Images, and Files
* Create Documents, Images, Files, Folders
* Delete any sort of object
So basically you can do more than is possible with PUT. Also, unlike
PUT, FTP gives you access to Document content. So when you download
a Document you are getting its content, not what it looks like when
it rendered.
FTP permissions
FTP support is provided for Folders, Documents, Images, and Files.
You can control access to FTP via the new 'FTP access' permission.
This permission controls the ability to 'cd' to a Folder and to
download objects. Uploading and deleting and creating objects are
controlled by existing permissions.
Properties and FTP: The next step
The next phase of FTP support will allow you to edit properties of
all Zope objects. Probably properties will be exposed via special
files which will contain an XML representation of the object's
properties. You could then download the file, edit the XML and
upload it to change the object's properties.
We do not currently have a target date for FTP property support.
It will probably need to wait until Zope has property sheets.
Differences between ZopeHTTPServer and ZServer
Both ZopeHTTPServer and ZServer are Python HTTP servers.
ZopeHTTPServer is built on the standard Python SimpleHTTPServer
framework. ZServer is built on Medusa.
ZopeHTTPServer is very limited. It can only publish one module at a
time. It can only publish via HTTP. It has no support for thread
pools. And more importantly, it is no longer being actively
developed, since its author has moved on the ZServer.
ZServer on the other hand is more complex and supports publishing
multiple modules, thread pools, and it uses a new threaded
architecture for accessing ZPublisher. Right now the thread pool is
limited to one thread, since the object database cannot yet support
concurrent access. This should change within the next few months.
How does FTP work?
The ZServer's FTP channel object translates FTP requests into
ZPublisher requests. The FTP channel then analyses the response and
formulates an appropriate FTP response. The FTP channel stores some
state such as the current working directory and the username and
password.
On the Zope side of things, the 'FTPSupport.py' module provides a
few mix-in classes for Document, Folder, and File (which Image
inherits from). These mix in classes provide directory listing and
stat-like methods. All the other FTP functions are handled by
existing methods like 'manage_delObjects', and 'PUT', etc.
Who should use ZServer?
This release is *alpha* quality. It should be used by Zope hackers.
If you are not inquisitive and self-reliant, this release will
frustrate you.
Installation
To install ZServer you need to do two things: edit the start script
and update some Zope files to introduce FTP support.
To edit the start up script, open 'start.py' in your favorite editor
and change the configuration variables. If you understand Medusa,
you can also change the rest of the script.
To enable FTP support in Zope you need to update some files in
'lib/python/OFS'. You should probably first back up your 'OFS'
directory, or at least make copies of the files that will be
replaced ('Document.py', 'Image.py', 'Folder.py'). Then copy the
contents of ZServer's 'OFS' directory to your Zope 'lib/python/OFS'
directory.
Finally make sure the shebang line is right on the start script. You
can use your own copy of Python, or the copy that came with Zope (if
you are using a binary distribution).
Now your ready to go.
Usage
To start ZServer run the start script::
./start.py
To stop the server type 'control-c'.
You should see some logging information come up on the screen.
Once you start ZServer is will publish Zope (or any Python module)
on HTTP and/or FTP. To access Zope via HTTP point your browser at
the server like so::
http://www.example.com:9673/Main
This assumes that you have chosen to put HTTP on port 9673 and that
you are publishing a module named 'Main'. Note: to publish Zope
normally you publish the 'lib/python/Main.py' module.
To access Zope via FTP you need to FTP to it at the port you set FTP
to run on. For example::
ftp www.example.com 8021
This starts and FTP session to your machine on port 8021, ZServer's
default FTP port. When you are prompted to log in you should supply
a Zope username and password. (Probably you should use an account
with the 'Manager' role, unless you have configured Zope to allow
FTP access to the 'Anonymous' role.) You can also enter 'anonymous'
and any password for anonymous FTP access. Once you have logged in
you can start issuing normal FTP commands. Right now ZServer only
supports basic FTP commands. Note: When you log in your working
directory is set to '/'. If you do not have FTP permissions in this
directory, you will need to 'cd' to a directory where you have
permissions before you can do anything.
Support
Questions and comments should go to 'support@digicool.com'.
You can report bugs and check on the status of bugs using the Zope
bug collector.::
http://www.zope.org/Collector/
License
ZServer is covered by the ZPL despite the fact that it comes with
much of the Medusa source code. The portions of Medusa that come
with ZServer are licensed under the ZPL.
Outstanding issues
PCGI support is not done yet. The FTP interface for Zope objects may
be changed, i.e. 'manage_FTPlist' and 'manage_FTPstat' maybe changed
and/or renamed. When FTP support for properties is added, this may
change a lot. WebDAV support will also probably cause a little
reorganization.
Currently ZServer's Medusa files are a bit modified from the
originals. It would be good idea to try and keep deviations to a
minimum. For this reason we have been feeding bug reports and change
requests back to Sam Rushing for inclusion in the official Medusa
souces. It is possible, however, that Medusa and ZServer will
diverge in some small respects despite our best efforts to keep them
unified. One area in particular where ZServer currently departs from
Medusa is in the use of future producers. Change is likely in
ZServer's use of future producers.
lib/python/ZServer/ZServerFTP.py
0 → 100644
View file @
2d1d87c9
"""ZServer FTP Channel for use the medusa's ftp server.
"""
from
PubCore
import
handle
from
medusa.ftp_server
import
ftp_channel
,
ftp_server
from
medusa
import
asynchat
,
filesys
from
medusa.producers
import
NotReady
from
cStringIO
import
StringIO
import
string
import
os
from
regsub
import
gsub
from
base64
import
encodestring
from
mimetypes
import
guess_type
import
marshal
import
stat
import
time
class
zope_ftp_channel
(
ftp_channel
):
"Passes its commands to Zope, not a filesystem"
read_only
=
0
def
__init__
(
self
,
server
,
conn
,
addr
,
module
):
ftp_channel
.
__init__
(
self
,
server
,
conn
,
addr
)
self
.
module
=
module
self
.
userid
=
''
self
.
password
=
''
self
.
path
=
'/'
def
_get_env
(
self
):
"Returns a CGI style environment"
env
=
{}
env
[
'SCRIPT_NAME'
]
=
'/%s'
%
self
.
module
env
[
'PATH_INFO'
]
=
self
.
path
env
[
'REQUEST_METHOD'
]
=
'GET'
# XXX what should this be?
env
[
'SERVER_SOFTWARE'
]
=
self
.
server
.
SERVER_IDENT
if
self
.
userid
!=
'anonymous'
:
env
[
'HTTP_AUTHORIZATION'
]
=
'Basic %s'
%
gsub
(
'
\
012
'
,
''
,
encodestring
(
'%s:%s'
%
(
self
.
userid
,
self
.
password
)))
env
[
'BOBO_DEBUG_MODE'
]
=
'1'
env
[
'SERVER_NAME'
]
=
self
.
server
.
hostname
env
[
'SERVER_PORT'
]
=
str
(
self
.
server
.
port
)
env
[
'REMOTE_ADDR'
]
=
self
.
client_addr
[
0
]
env
[
'GATEWAY_INTERFACE'
]
=
'CGI/1.1'
# that's stretching it ;-)
# XXX etcetera -- probably set many of these at the start
return
env
def
_join_paths
(
self
,
*
args
):
path
=
apply
(
os
.
path
.
join
,
args
)
path
=
os
.
path
.
normpath
(
path
)
if
os
.
sep
!=
'/'
:
path
=
string
.
replace
(
path
,
os
.
sep
,
'/'
)
return
path
def
make_response
(
self
,
resp
):
self
.
log
(
'==> %s'
%
resp
)
return
resp
+
'
\
r
\
n
'
# Overriden async_chat methods
writable
=
asynchat
.
async_chat
.
writable_future
# Overriden ftp_channel methods
def
cmd_nlst
(
self
,
line
):
'give name list of files in directory'
self
.
push_with_producer
(
self
.
get_dir_list
(
line
,
0
))
def
cmd_list
(
self
,
line
):
'give list files in a directory'
# XXX should handle listing a file, not just a directory
# also should maybe glob, but this is hard to do...
self
.
push_with_producer
(
self
.
get_dir_list
(
line
,
1
))
def
get_dir_list
(
self
,
line
,
long
=
0
):
# we need to scan the command line for arguments to '/bin/ls'...
# XXX clean this up
if
len
(
line
)
>
1
:
args
=
string
.
split
(
line
[
1
])
else
:
args
=
[]
path_args
=
[]
for
arg
in
args
:
if
arg
[
0
]
!=
'-'
:
path_args
.
append
(
arg
)
else
:
if
'l'
in
arg
:
long
=
1
if
len
(
path_args
)
<
1
:
dir
=
'.'
else
:
dir
=
path_args
[
0
]
return
self
.
listdir
(
dir
,
long
)
def
listdir
(
self
,
path
,
long
=
0
):
env
=
self
.
_get_env
()
env
[
'PATH_INFO'
]
=
self
.
_join_paths
(
self
.
path
,
path
,
'manage_FTPlist'
)
outpipe
=
handle
(
self
.
module
,
env
,
StringIO
())
return
ResponseProducer
(
outpipe
,
self
.
_do_listdir
,(
long
,))
def
_do_listdir
(
self
,
long
,
response
):
#print "do list response", response.__dict__
code
=
response
.
headers
[
'Status'
][:
3
]
if
code
==
'200'
:
dir_list
=
''
file_infos
=
marshal
.
loads
(
response
.
content
)
if
type
(
file_infos
[
0
])
==
type
(
''
):
file_infos
=
(
file_infos
,)
if
long
:
for
id
,
stat_info
in
file_infos
:
dir_list
=
dir_list
+
filesys
.
unix_longify
(
id
,
stat_info
)
+
'
\
r
\
n
'
else
:
for
id
,
stat_info
in
file_infos
:
dir_list
=
dir_list
+
id
+
'
\
r
\
n
'
self
.
make_xmit_channel
()
self
.
client_dc
.
push
(
dir_list
)
self
.
client_dc
.
close_when_done
()
return
self
.
make_response
(
'150 Opening %s mode data connection for file list'
%
(
self
.
type_map
[
self
.
current_mode
]
)
)
elif
code
==
'401'
:
return
self
.
make_response
(
'530 Unauthorized.'
)
else
:
return
self
.
make_response
(
'550 Could not list directory.'
)
def
cmd_cwd
(
self
,
line
):
'change working directory'
# try to call manage_FTPlist on the path
env
=
self
.
_get_env
()
path
=
line
[
1
]
path
=
self
.
_join_paths
(
self
.
path
,
path
,
'manage_FTPlist'
)
env
[
'PATH_INFO'
]
=
path
outpipe
=
handle
(
self
.
module
,
env
,
StringIO
())
self
.
push_with_producer
(
ResponseProducer
(
outpipe
,
self
.
_cmd_cwd
,(
path
[:
-
15
],)))
def
_cmd_cwd
(
self
,
path
,
response
):
code
=
response
.
headers
[
'Status'
][:
3
]
if
code
==
'200'
:
listing
=
marshal
.
loads
(
response
.
content
)
# check to see if we are cding to a non-foldoid object
if
type
(
listing
[
0
])
==
type
(
''
):
return
self
.
make_response
(
'550 No such directory.'
)
self
.
path
=
path
or
'/'
return
self
.
make_response
(
'250 CWD command successful.'
)
elif
code
==
'401'
:
return
self
.
make_response
(
'530 Unauthorized.'
)
else
:
return
self
.
make_response
(
'550 No such directory.'
)
def
cmd_cdup
(
self
,
line
):
'change to parent of current working directory'
self
.
cmd_cwd
((
None
,
'..'
))
def
cmd_pwd
(
self
,
line
):
'print the current working directory'
self
.
respond
(
'257 "%s" is the current directory.'
%
(
self
.
path
)
)
cmd_xpwd
=
cmd_pwd
def
cmd_mdtm
(
self
,
line
):
'show last modification time of file'
if
len
(
line
)
!=
2
:
self
.
command
.
not_understood
(
string
.
join
(
line
))
return
env
=
self
.
_get_env
()
env
[
'PATH_INFO'
]
=
self
.
_join_paths
(
self
.
path
,
line
[
1
],
'manage_FTPstat'
)
outpipe
=
handle
(
self
.
module
,
env
,
StringIO
())
self
.
push_with_producer
(
ResponseProducer
(
outpipe
,
self
.
_cmd_mdtm
))
def
_cmd_mdtm
(
self
,
response
):
#print "mdtm response", response.__dict__
code
=
response
.
headers
[
'Status'
][:
3
]
if
code
==
'200'
:
mtime
=
marshal
.
loads
(
response
.
content
)[
stat
.
ST_MTIME
]
mtime
=
time
.
gmtime
(
mtime
)
return
self
.
make_response
(
'213 %4d%02d%02d%02d%02d%02d'
%
(
mtime
[
0
],
mtime
[
1
],
mtime
[
2
],
mtime
[
3
],
mtime
[
4
],
mtime
[
5
]
))
elif
code
==
'401'
:
return
self
.
make_response
(
'530 Unauthorized.'
)
else
:
return
self
.
make_response
(
'550 Error getting file modification time.'
)
def
cmd_size
(
self
,
line
):
'return size of file'
if
len
(
line
)
!=
2
:
self
.
command
.
not_understood
(
string
.
join
(
line
))
return
env
=
self
.
_get_env
()
env
[
'PATH_INFO'
]
=
self
.
_join_paths
(
self
.
path
,
line
[
1
],
'manage_FTPstat'
)
outpipe
=
handle
(
self
.
module
,
env
,
StringIO
())
self
.
push_with_producer
(
ResponseProducer
(
outpipe
,
self
.
_cmd_size
))
def
_cmd_size
(
self
,
response
):
#print "size response", response.__dict__
code
=
response
.
headers
[
'Status'
][:
3
]
if
code
==
'200'
:
return
self
.
make_response
(
'213 %d'
%
marshal
.
loads
(
response
.
content
)[
stat
.
ST_SIZE
])
elif
code
==
'401'
:
return
self
.
make_response
(
'530 Unauthorized.'
)
else
:
return
self
.
make_response
(
'550 Error getting file size.'
)
self
.
client_dc
.
close_when_done
()
def
cmd_retr
(
self
,
line
):
if
len
(
line
)
<
2
:
self
.
command_not_understood
(
string
.
join
(
line
))
return
env
=
self
.
_get_env
()
path
,
id
=
os
.
path
.
split
(
line
[
1
])
env
[
'PATH_INFO'
]
=
self
.
_join_paths
(
self
.
path
,
line
[
1
],
'manage_FTPget'
)
outpipe
=
handle
(
self
.
module
,
env
,
StringIO
())
self
.
push_with_producer
(
ResponseProducer
(
outpipe
,
self
.
_cmd_retr
,
(
line
[
1
],)))
def
_cmd_retr
(
self
,
file
,
response
):
#print "retr response\n", response.__dict__
code
=
response
.
headers
[
'Status'
][:
3
]
if
code
==
'200'
:
#fd=StringIO(response.content)
self
.
make_xmit_channel
()
#if self.restart_position:
# # try to position the file as requested, but
# # give up silently on failure (the 'file object'
# # may not support seek())
# try:
# fd.seek (self.restart_position)
# except:
# pass
# self.restart_position = 0
self
.
client_dc
.
push
(
response
.
content
)
self
.
client_dc
.
close_when_done
()
return
self
.
make_response
(
"150 Opening %s mode data connection for file '%s'"
%
(
self
.
type_map
[
self
.
current_mode
],
file
))
elif
code
==
'401'
:
return
self
.
make_response
(
'530 Unauthorized.'
)
else
:
return
self
.
make_response
(
'550 Error opening file.'
)
def
cmd_stor
(
self
,
line
,
mode
=
'wb'
):
'store a file'
if
len
(
line
)
<
2
:
self
.
command_not_understood
(
string
.
join
(
line
))
return
elif
self
.
restart_position
:
restart_position
=
0
self
.
respond
(
'553 restart on STOR not yet supported'
)
return
# XXX Check for possible problems first? Like authorization...
# But how? Once we agree to receive the file, can we still
# bail later?
fd
=
ContentReceiver
(
self
.
_do_cmd_stor
,
(
self
.
_join_paths
(
self
.
path
,
line
[
1
]),))
self
.
respond
(
'150 Opening %s connection for %s'
%
(
self
.
type_map
[
self
.
current_mode
],
line
[
1
]
)
)
self
.
make_recv_channel
(
fd
)
def
_do_cmd_stor
(
self
,
path
,
data
):
'callback to do the STOR, after we have the input'
#print "stor callback", path, data
env
=
self
.
_get_env
()
env
[
'PATH_INFO'
]
=
path
env
[
'REQUEST_METHOD'
]
=
'PUT'
ctype
=
guess_type
(
path
)[
0
]
if
ctype
is
not
None
:
env
[
'CONTENT_TYPE'
]
=
ctype
env
[
'CONTENT_LENGTH'
]
=
len
(
data
.
getvalue
())
outpipe
=
handle
(
self
.
module
,
env
,
data
)
self
.
push_with_producer
(
ResponseProducer
(
outpipe
,
self
.
_cmd_stor
))
def
_cmd_stor
(
self
,
response
):
code
=
response
.
headers
[
'Status'
][:
3
]
if
code
in
(
'200'
,
'204'
,
'302'
):
return
self
.
make_response
(
'257 STOR command successful.'
)
elif
code
==
'401'
:
return
self
.
make_response
(
'530 Unauthorized.'
)
else
:
return
self
.
make_response
(
'550 Error creating file.'
)
def
cmd_dele
(
self
,
line
):
if
len
(
line
)
!=
2
:
self
.
command
.
not_understood
(
string
.
join
(
line
))
return
path
,
id
=
os
.
path
.
split
(
line
[
1
])
env
=
self
.
_get_env
()
env
[
'PATH_INFO'
]
=
self
.
_join_paths
(
self
.
path
,
path
,
'manage_delObjects'
)
env
[
'QUERY_STRING'
]
=
'ids=%s'
%
id
outpipe
=
handle
(
self
.
module
,
env
,
StringIO
())
self
.
push_with_producer
(
ResponseProducer
(
outpipe
,
self
.
_cmd_dele
))
def
_cmd_dele
(
self
,
response
):
code
=
response
.
headers
[
'Status'
][:
3
]
if
code
==
'200'
and
string
.
find
(
response
.
content
,
'Not Deletable'
)
==-
1
:
return
self
.
make_response
(
'250 DELE command successful.'
)
elif
code
==
'401'
:
return
self
.
make_response
(
'530 Unauthorized.'
)
else
:
return
self
.
make_response
(
'550 Error deleting file.'
)
def
cmd_mkd
(
self
,
line
):
if
len
(
line
)
!=
2
:
self
.
command
.
not_understood
(
string
.
join
(
line
))
return
env
=
self
.
_get_env
()
path
,
id
=
os
.
path
.
split
(
line
[
1
])
env
[
'PATH_INFO'
]
=
self
.
_join_paths
(
self
.
path
,
path
,
'manage_addFolder'
)
env
[
'QUERY_STRING'
]
=
'id=%s'
%
id
outpipe
=
handle
(
self
.
module
,
env
,
StringIO
())
self
.
push_with_producer
(
ResponseProducer
(
outpipe
,
self
.
_cmd_mkd
))
cmd_xmkd
=
cmd_mkd
def
_cmd_mkd
(
self
,
response
):
#print "mkd response", response.__dict__
code
=
response
.
headers
[
'Status'
][:
3
]
if
code
==
'200'
:
return
self
.
make_response
(
'257 MKD command successful.'
)
elif
code
==
'401'
:
return
self
.
make_response
(
'530 Unauthorized.'
)
else
:
return
self
.
make_response
(
'550 Error creating directory.'
)
def
cmd_rmd
(
self
,
line
):
# XXX should object be checked to see if it's folderish
# before we allow it to be RMD'd?
if
len
(
line
)
!=
2
:
self
.
command
.
not_understood
(
string
.
join
(
line
))
return
path
,
id
=
os
.
path
.
split
(
line
[
1
])
env
=
self
.
_get_env
()
env
[
'PATH_INFO'
]
=
self
.
_join_paths
(
self
.
path
,
path
,
'manage_delObjects'
)
env
[
'QUERY_STRING'
]
=
'ids=%s'
%
id
outpipe
=
handle
(
self
.
module
,
env
,
StringIO
())
self
.
push_with_producer
(
ResponseProducer
(
outpipe
,
self
.
_cmd_rmd
))
cmd_xrmd
=
cmd_rmd
def
_cmd_rmd
(
self
,
response
):
#print "rmd response", response.__dict__
code
=
response
.
headers
[
'Status'
][:
3
]
if
code
==
'200'
and
string
.
find
(
response
.
content
,
'Not Deletable'
)
==-
1
:
return
self
.
make_response
(
'250 RMD command successful.'
)
elif
code
==
'401'
:
return
self
.
make_response
(
'530 Unauthorized.'
)
else
:
return
self
.
make_response
(
'550 Error removing directory.'
)
def
cmd_user
(
self
,
line
):
'specify user name'
if
len
(
line
)
>
1
:
self
.
userid
=
line
[
1
]
self
.
respond
(
'331 Password required.'
)
else
:
self
.
command_not_understood
(
string
.
join
(
line
))
def
cmd_pass
(
self
,
line
):
'specify password'
if
len
(
line
)
<
2
:
pw
=
''
else
:
pw
=
line
[
1
]
self
.
password
=
pw
self
.
respond
(
'230 Login successful.'
)
self
.
authorized
=
1
self
.
log
(
'Successful login.'
)
class
ZResponseReceiver
:
"""Given an output pipe reads response and parses it.
After a call to ready returns true, you can read
the headers as a dictiony and the content as a string."""
def
__init__
(
self
,
pipe
):
self
.
pipe
=
pipe
self
.
data
=
''
self
.
headers
=
{}
self
.
content
=
''
def
ready
(
self
):
if
self
.
pipe
is
None
:
return
1
if
self
.
pipe
.
ready
():
data
=
self
.
pipe
.
read
()
if
data
:
self
.
data
=
self
.
data
+
data
else
:
self
.
parse
()
return
1
def
parse
(
self
):
headers
,
html
=
string
.
split
(
self
.
data
,
'
\
n
\
n
'
,
1
)
self
.
data
=
''
for
header
in
string
.
split
(
headers
,
'
\
n
'
):
k
,
v
=
string
.
split
(
header
,
': '
,
1
)
self
.
headers
[
k
]
=
v
self
.
content
=
html
self
.
pipe
=
None
class
ResponseProducer
:
"Allows responses which need to make Zope requests first."
def
__init__
(
self
,
pipe
,
callback
,
args
=
None
):
self
.
response
=
ZResponseReceiver
(
pipe
)
self
.
callback
=
callback
self
.
args
=
args
or
()
self
.
done
=
None
def
ready
(
self
):
if
self
.
response
is
not
None
:
return
self
.
response
.
ready
()
else
:
return
1
def
more
(
self
):
if
not
self
.
done
:
if
not
self
.
response
.
ready
():
raise
NotReady
()
self
.
done
=
1
r
=
self
.
response
c
=
self
.
callback
args
=
self
.
args
+
(
r
,)
self
.
response
=
None
self
.
callback
=
None
self
.
args
=
None
return
apply
(
c
,
args
)
else
:
return
''
class
ContentReceiver
:
"Write-only file object used to receive data from FTP"
def
__init__
(
self
,
callback
,
args
=
None
):
self
.
data
=
StringIO
()
self
.
callback
=
callback
self
.
args
=
args
or
()
def
write
(
self
,
data
):
self
.
data
.
write
(
data
)
def
close
(
self
):
self
.
data
.
seek
(
0
)
args
=
self
.
args
+
(
self
.
data
,)
c
=
self
.
callback
self
.
callback
=
None
self
.
args
=
None
apply
(
c
,
args
)
class
zope_ftp_server
(
ftp_server
):
ftp_channel_class
=
zope_ftp_channel
def
__init__
(
self
,
module
,
hostname
,
port
,
resolver
,
logger_object
):
ftp_server
.
__init__
(
self
,
None
,
hostname
,
port
,
resolver
,
logger_object
)
self
.
module
=
module
def
handle_accept
(
self
):
conn
,
addr
=
self
.
accept
()
self
.
total_sessions
.
increment
()
print
'Incoming connection from %s:%d'
%
(
addr
[
0
],
addr
[
1
])
self
.
ftp_channel_class
(
self
,
conn
,
addr
,
self
.
module
)
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment