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
d7712680
Commit
d7712680
authored
Apr 23, 2001
by
Martijn Pieters
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
- Merge the mj-http_range_support-branch HTTP Range functionality.
- Update the CHANGES.txt file.
parent
c47aeb40
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
1083 additions
and
7 deletions
+1083
-7
ZServer/FTPServer.py
ZServer/FTPServer.py
+3
-0
doc/CHANGES.txt
doc/CHANGES.txt
+5
-0
lib/python/OFS/Image.py
lib/python/OFS/Image.py
+182
-6
lib/python/OFS/tests/__init__.py
lib/python/OFS/tests/__init__.py
+1
-0
lib/python/OFS/tests/testRanges.py
lib/python/OFS/tests/testRanges.py
+429
-0
lib/python/ZPublisher/HTTPRangeSupport.py
lib/python/ZPublisher/HTTPRangeSupport.py
+229
-0
lib/python/ZPublisher/tests/__init__.py
lib/python/ZPublisher/tests/__init__.py
+1
-0
lib/python/ZPublisher/tests/testHTTPRangeSupport.py
lib/python/ZPublisher/tests/testHTTPRangeSupport.py
+222
-0
lib/python/ZServer/FTPServer.py
lib/python/ZServer/FTPServer.py
+3
-0
lib/python/webdav/Resource.py
lib/python/webdav/Resource.py
+8
-1
No files found.
ZServer/FTPServer.py
View file @
d7712680
...
...
@@ -369,6 +369,9 @@ class zope_ftp_channel(ftp_channel):
response
=
make_response
(
self
,
self
.
retr_completion
,
line
[
1
])
self
.
_response_producers
=
response
.
stdout
.
_producers
request
=
FTPRequest
(
line
[
1
],
'RETR'
,
self
,
response
)
# Support download restarts if possible.
if
self
.
restart_position
>
0
:
request
.
environ
[
'HTTP_RANGE'
]
=
'bytes=%d-'
%
self
.
restart_position
handle
(
self
.
module
,
request
,
response
)
def
retr_completion
(
self
,
file
,
response
):
...
...
doc/CHANGES.txt
View file @
d7712680
...
...
@@ -42,6 +42,11 @@ Zope changes
- StructuredText now takes care of your locale settings.
- Image and File objects now support the HTTP Range and If-Range
headers, enabeling partial downloads of such objects. This can be
used by clients to restart a broken download. Downloads of these
objects through FTP can also be restarted.
Zope 2.3.0 beta 3
Features Added
...
...
lib/python/OFS/Image.py
View file @
d7712680
...
...
@@ -84,11 +84,11 @@
##############################################################################
"""Image object"""
__version__
=
'$Revision: 1.12
8
$'
[
11
:
-
2
]
__version__
=
'$Revision: 1.12
9
$'
[
11
:
-
2
]
import
Globals
,
string
,
struct
,
content_types
import
Globals
,
string
,
struct
from
OFS.content_types
import
guess_content_type
from
Globals
import
DTMLFile
,
MessageDialog
from
Globals
import
DTMLFile
from
PropertyManager
import
PropertyManager
from
AccessControl.Role
import
RoleManager
from
webdav.common
import
rfc1123_date
...
...
@@ -100,10 +100,10 @@ from Globals import Persistent
from
Acquisition
import
Implicit
from
DateTime
import
DateTime
from
Cache
import
Cacheable
from
mimetools
import
choose_boundary
from
ZPublisher
import
HTTPRangeSupport
StringType
=
type
(
''
)
manage_addFileForm
=
DTMLFile
(
'dtml/imageAdd'
,
globals
(),
Kind
=
'File'
,
kind
=
'file'
)
def
manage_addFile
(
self
,
id
,
file
=
''
,
title
=
''
,
precondition
=
''
,
content_type
=
''
,
REQUEST
=
None
):
...
...
@@ -137,7 +137,7 @@ class File(Persistent, Implicit, PropertyManager,
RoleManager
,
Item_w__name__
,
Cacheable
):
"""A File object is a content object for arbitrary files."""
__implements__
=
(
WriteLockInterface
,)
__implements__
=
(
WriteLockInterface
,
HTTPRangeSupport
.
HTTPRangeInterface
)
meta_type
=
'File'
...
...
@@ -212,6 +212,8 @@ class File(Persistent, Implicit, PropertyManager,
# with common servers such as Apache (which can usually
# understand the screwy date string as a lucky side effect
# of the way they parse it).
# This happens to be what RFC2616 tells us to do in the face of an
# invalid date.
try
:
mod_since
=
long
(
DateTime
(
header
).
timeTime
())
except
:
mod_since
=
None
if
mod_since
is
not
None
:
...
...
@@ -225,6 +227,7 @@ class File(Persistent, Implicit, PropertyManager,
RESPONSE
.
setHeader
(
'Last-Modified'
,
rfc1123_date
(
self
.
_p_mtime
))
RESPONSE
.
setHeader
(
'Content-Type'
,
self
.
content_type
)
RESPONSE
.
setHeader
(
'Content-Length'
,
self
.
size
)
RESPONSE
.
setHeader
(
'Accept-Ranges'
,
'bytes'
)
RESPONSE
.
setStatus
(
304
)
return
''
...
...
@@ -237,9 +240,181 @@ class File(Persistent, Implicit, PropertyManager,
c
(
REQUEST
[
'PARENTS'
][
1
],
REQUEST
)
else
:
c
()
# HTTP Range header handling
range
=
REQUEST
.
get_header
(
'Range'
,
None
)
if_range
=
REQUEST
.
get_header
(
'If-Range'
,
None
)
if
range
is
not
None
:
ranges
=
HTTPRangeSupport
.
parseRange
(
range
)
if
if_range
is
not
None
:
# Only send ranges if the data isn't modified, otherwise send
# the whole object. Support both ETags and Last-Modified dates!
if
len
(
if_range
)
>
1
and
if_range
[:
2
]
==
'ts'
:
# ETag:
if
if_range
!=
self
.
http__etag
():
# Modified, so send a normal response. We delete
# the ranges, which causes us to skip to the 200
# response.
ranges
=
None
else
:
# Date
date
=
string
.
split
(
if_range
,
';'
)[
0
]
try
:
mod_since
=
long
(
DateTime
(
date
).
timeTime
())
except
:
mod_since
=
None
if
mod_since
is
not
None
:
if
self
.
_p_mtime
:
last_mod
=
long
(
self
.
_p_mtime
)
else
:
last_mod
=
long
(
0
)
if
last_mod
>
mod_since
:
# Modified, so send a normal response. We delete
# the ranges, which causes us to skip to the 200
# response.
ranges
=
None
if
ranges
:
# Search for satisfiable ranges.
satisfiable
=
0
for
start
,
end
in
ranges
:
if
start
<
self
.
size
:
satisfiable
=
1
break
if
not
satisfiable
:
RESPONSE
.
setHeader
(
'Content-Range'
,
'bytes */%d'
%
self
.
size
)
RESPONSE
.
setHeader
(
'Accept-Ranges'
,
'bytes'
)
RESPONSE
.
setHeader
(
'Last-Modified'
,
rfc1123_date
(
self
.
_p_mtime
))
RESPONSE
.
setHeader
(
'Content-Type'
,
self
.
content_type
)
RESPONSE
.
setHeader
(
'Content-Length'
,
self
.
size
)
RESPONSE
.
setStatus
(
416
)
return
''
# Can we optimize?
ranges
=
HTTPRangeSupport
.
optimizeRanges
(
ranges
,
self
.
size
)
if
len
(
ranges
)
==
1
:
# Easy case, set extra header and return partial set.
start
,
end
=
ranges
[
0
]
size
=
end
-
start
RESPONSE
.
setHeader
(
'Last-Modified'
,
rfc1123_date
(
self
.
_p_mtime
))
RESPONSE
.
setHeader
(
'Content-Type'
,
self
.
content_type
)
RESPONSE
.
setHeader
(
'Content-Length'
,
size
)
RESPONSE
.
setHeader
(
'Accept-Ranges'
,
'bytes'
)
RESPONSE
.
setHeader
(
'Content-Range'
,
'bytes %d-%d/%d'
%
(
start
,
end
-
1
,
self
.
size
))
RESPONSE
.
setStatus
(
206
)
# Partial content
data
=
self
.
data
if
type
(
data
)
is
StringType
:
return
data
[
start
:
end
]
# Linked Pdata objects. Urgh.
pos
=
0
while
data
is
not
None
:
l
=
len
(
data
.
data
)
pos
=
pos
+
l
if
pos
>
start
:
# We are within the range
lstart
=
l
-
(
pos
-
start
)
if
lstart
<
0
:
lstart
=
0
# find the endpoint
if
end
<=
pos
:
lend
=
l
-
(
pos
-
end
)
# Send and end transmission
RESPONSE
.
write
(
data
[
lstart
:
lend
])
break
# Not yet at the end, transmit what we have.
RESPONSE
.
write
(
data
[
lstart
:])
data
=
data
.
next
return
''
else
:
# Ignore multi-part ranges for now, pretend we don't know
# about ranges at all.
# When we get here, ranges have been optimized, so they are
# in order, non-overlapping, and start and end values are
# positive integers.
boundary
=
choose_boundary
()
# Calculate the content length
size
=
(
8
+
len
(
boundary
)
+
# End marker length
len
(
ranges
)
*
(
# Constant lenght per set
49
+
len
(
boundary
)
+
len
(
self
.
content_type
)
+
len
(
'%d'
%
self
.
size
)))
for
start
,
end
in
ranges
:
# Variable length per set
size
=
(
size
+
len
(
'%d%d'
%
(
start
,
end
-
1
))
+
end
-
start
)
RESPONSE
.
setHeader
(
'Content-Length'
,
size
)
RESPONSE
.
setHeader
(
'Accept-Ranges'
,
'bytes'
)
RESPONSE
.
setHeader
(
'Last-Modified'
,
rfc1123_date
(
self
.
_p_mtime
))
RESPONSE
.
setHeader
(
'Content-Type'
,
'multipart/byteranges; boundary=%s'
%
boundary
)
RESPONSE
.
setStatus
(
206
)
# Partial content
pos
=
0
data
=
self
.
data
for
start
,
end
in
ranges
:
RESPONSE
.
write
(
'
\
r
\
n
--%s
\
r
\
n
'
%
boundary
)
RESPONSE
.
write
(
'Content-Type: %s
\
r
\
n
'
%
self
.
content_type
)
RESPONSE
.
write
(
'Content-Range: bytes %d-%d/%d
\
r
\
n
\
r
\
n
'
%
(
start
,
end
-
1
,
self
.
size
))
if
type
(
data
)
is
StringType
:
RESPONSE
.
write
(
data
[
start
:
end
])
else
:
# Yippee. Linked Pdata objects.
while
data
is
not
None
:
l
=
len
(
data
.
data
)
pos
=
pos
+
l
if
pos
>
start
:
# We are within the range
lstart
=
l
-
(
pos
-
start
)
if
lstart
<
0
:
lstart
=
0
# find the endpoint
if
end
<=
pos
:
lend
=
l
-
(
pos
-
end
)
# Send and loop to next range
RESPONSE
.
write
(
data
[
lstart
:
lend
])
# Back up the position marker, it will
# be incremented again for the next
# part.
pos
=
pos
-
l
break
# Not yet at the end, transmit what we have.
RESPONSE
.
write
(
data
[
lstart
:])
data
=
data
.
next
RESPONSE
.
write
(
'
\
r
\
n
--%s--
\
r
\
n
'
%
boundary
)
return
''
RESPONSE
.
setHeader
(
'Last-Modified'
,
rfc1123_date
(
self
.
_p_mtime
))
RESPONSE
.
setHeader
(
'Content-Type'
,
self
.
content_type
)
RESPONSE
.
setHeader
(
'Content-Length'
,
self
.
size
)
RESPONSE
.
setHeader
(
'Accept-Ranges'
,
'bytes'
)
# Don't cache the data itself, but provide an opportunity
# for a cache manager to set response headers.
...
...
@@ -268,6 +443,7 @@ class File(Persistent, Implicit, PropertyManager,
self
.
size
=
size
self
.
data
=
data
self
.
ZCacheable_invalidate
()
self
.
http__refreshEtag
()
def
manage_edit
(
self
,
title
,
content_type
,
precondition
=
''
,
REQUEST
=
None
):
"""
...
...
lib/python/OFS/tests/__init__.py
0 → 100644
View file @
d7712680
# This helps debugging.
lib/python/OFS/tests/testRanges.py
0 → 100644
View file @
d7712680
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
import
os
,
sys
sys
.
path
.
insert
(
0
,
'.'
)
try
:
import
Testing
os
.
environ
[
'SOFTWARE_HOME'
]
=
os
.
environ
.
get
(
'SOFTWARE_HOME'
,
'.'
)
except
ImportError
:
sys
.
path
[
0
]
=
'../..'
import
Testing
os
.
environ
[
'SOFTWARE_HOME'
]
=
'../..'
os
.
environ
[
'INSTANCE_HOME'
]
=
os
.
environ
.
get
(
'INSTANCE_HOME'
,
os
.
path
.
join
(
os
.
environ
[
'SOFTWARE_HOME'
],
'..'
,
'..'
)
)
import
string
,
whrandom
,
cStringIO
,
time
,
re
import
Zope
import
unittest
from
Testing.makerequest
import
makerequest
from
webdav.common
import
rfc1123_date
from
mimetools
import
Message
from
multifile
import
MultiFile
def
createBigFile
():
# Create a file that is several 1<<16 blocks of data big, to force the
# use of chained Pdata objects.
size
=
(
1
<<
16
)
*
5
file
=
cStringIO
.
StringIO
()
def
addLetter
(
x
,
add
=
file
.
write
,
l
=
string
.
letters
,
c
=
whrandom
.
choice
):
add
(
c
(
l
))
filter
(
addLetter
,
range
(
size
))
return
file
TESTFOLDER_NAME
=
'RangesTestSuite_testFolder'
BIGFILE
=
createBigFile
()
class
TestRequestRange
(
unittest
.
TestCase
):
# Test case setup and teardown
def
setUp
(
self
):
self
.
responseOut
=
cStringIO
.
StringIO
()
self
.
app
=
makerequest
(
Zope
.
app
(),
stdout
=
self
.
responseOut
)
try
:
self
.
app
.
_delObject
(
TESTFOLDER_NAME
)
except
AttributeError
:
pass
self
.
app
.
manage_addFolder
(
TESTFOLDER_NAME
)
data
=
string
.
letters
self
.
app
[
TESTFOLDER_NAME
].
manage_addFile
(
'file'
,
file
=
data
,
content_type
=
'text/plain'
)
self
.
file
=
self
.
app
[
TESTFOLDER_NAME
].
file
self
.
data
=
data
# Hack, we need a _p_mtime for the file, so we make sure that it has
# one. We use a subtransaction, which means we can rol-back later and
# pretend we didn't touch the ZODB.
get_transaction
().
commit
()
def
tearDown
(
self
):
try
:
self
.
app
.
_delObject
(
TESTFOLDER_NAME
)
except
AttributeError
:
pass
get_transaction
().
commit
()
self
.
app
.
_p_jar
.
close
()
self
.
app
=
None
del
self
.
app
# Utility methods
def
uploadBigFile
(
self
):
self
.
file
.
manage_upload
(
BIGFILE
)
self
.
data
=
BIGFILE
.
getvalue
()
def
doGET
(
self
,
request
,
response
):
rv
=
self
.
file
.
index_html
(
request
,
response
)
# Large files are written to resposeOut directly, small ones are
# returned from the index_html method.
body
=
self
.
responseOut
.
getvalue
()
# Chop off any printed headers (only when response.write was used)
if
body
:
body
=
string
.
split
(
body
,
'
\
n
\
n
'
,
1
)[
1
]
return
body
+
rv
def
createLastModifiedDate
(
self
,
offset
=
0
):
return
rfc1123_date
(
self
.
file
.
_p_mtime
+
offset
)
def
expectUnsatisfiable
(
self
,
range
):
req
=
self
.
app
.
REQUEST
rsp
=
req
.
RESPONSE
# Add the Range header
req
.
environ
[
'HTTP_RANGE'
]
=
'bytes=%s'
%
range
body
=
self
.
doGET
(
req
,
rsp
)
self
.
failUnless
(
rsp
.
getStatus
()
==
416
,
'Expected a 416 status, got %s'
%
rsp
.
getStatus
())
expect_content_range
=
'bytes */%d'
%
len
(
self
.
data
)
content_range
=
rsp
.
getHeader
(
'content-range'
)
self
.
failIf
(
content_range
is
None
,
'No Content-Range header was set!'
)
self
.
failUnless
(
content_range
==
expect_content_range
,
'Received incorrect Content-Range header. Expected %s, got %s'
%
(
`expect_content_range`
,
`content_range`
))
self
.
failUnless
(
body
==
''
,
'index_html returned %s'
%
`body`
)
def
expectOK
(
self
,
rangeHeader
,
if_range
=
None
):
req
=
self
.
app
.
REQUEST
rsp
=
req
.
RESPONSE
# Add headers
req
.
environ
[
'HTTP_RANGE'
]
=
rangeHeader
if
if_range
is
not
None
:
req
.
environ
[
'HTTP_IF_RANGE'
]
=
if_range
body
=
self
.
doGET
(
req
,
rsp
)
self
.
failUnless
(
rsp
.
getStatus
()
==
200
,
'Expected a 200 status, got %s'
%
rsp
.
getStatus
())
def
expectSingleRange
(
self
,
range
,
start
,
end
,
if_range
=
None
):
req
=
self
.
app
.
REQUEST
rsp
=
req
.
RESPONSE
# Add headers
req
.
environ
[
'HTTP_RANGE'
]
=
'bytes=%s'
%
range
if
if_range
is
not
None
:
req
.
environ
[
'HTTP_IF_RANGE'
]
=
if_range
body
=
self
.
doGET
(
req
,
rsp
)
self
.
failUnless
(
rsp
.
getStatus
()
==
206
,
'Expected a 206 status, got %s'
%
rsp
.
getStatus
())
expect_content_range
=
'bytes %d-%d/%d'
%
(
start
,
end
-
1
,
len
(
self
.
data
))
content_range
=
rsp
.
getHeader
(
'content-range'
)
self
.
failIf
(
content_range
is
None
,
'No Content-Range header was set!'
)
self
.
failUnless
(
content_range
==
expect_content_range
,
'Received incorrect Content-Range header. Expected %s, got %s'
%
(
`expect_content_range`
,
`content_range`
))
self
.
failIf
(
rsp
.
getHeader
(
'content-length'
)
!=
len
(
body
),
'Incorrect Content-Length is set! Expected %d, got %d.'
%
(
rsp
.
getHeader
(
'content-length'
),
len
(
body
)))
self
.
failUnless
(
body
==
self
.
data
[
start
:
end
],
'Incorrect range returned, expected %s, got %s'
%
(
`self.data[start:end]`
,
`body`
))
def
expectMultipleRanges
(
self
,
range
,
sets
,
rangeParse
=
re
.
compile
(
'bytes
\
s*(
\
d+)-(
\
d+)/(
\
d+)'
)):
req
=
self
.
app
.
REQUEST
rsp
=
req
.
RESPONSE
# Add headers
req
.
environ
[
'HTTP_RANGE'
]
=
'bytes=%s'
%
range
body
=
self
.
doGET
(
req
,
rsp
)
self
.
failUnless
(
rsp
.
getStatus
()
==
206
,
'Expected a 206 status, got %s'
%
rsp
.
getStatus
())
self
.
failIf
(
rsp
.
getHeader
(
'content-range'
),
'The Content-Range header should not be set!'
)
ct
=
string
.
split
(
rsp
.
getHeader
(
'content-type'
),
';'
)[
0
]
self
.
failIf
(
ct
!=
'multipart/byteranges'
,
"Incorrect Content-Type set. Expected 'multipart/byteranges', "
"got %s"
%
ct
)
if
rsp
.
getHeader
(
'content-length'
):
self
.
failIf
(
rsp
.
getHeader
(
'content-length'
)
!=
len
(
body
),
'Incorrect Content-Length is set! Expected %d, got %d.'
%
(
len
(
body
),
rsp
.
getHeader
(
'content-length'
)))
# Decode the multipart message
bodyfile
=
cStringIO
.
StringIO
(
'Content-Type: %s
\
n
\
n
%s'
%
(
rsp
.
getHeader
(
'content-type'
),
body
))
bodymessage
=
Message
(
bodyfile
)
partfiles
=
MultiFile
(
bodyfile
)
partfiles
.
push
(
bodymessage
.
getparam
(
'boundary'
))
partmessages
=
[]
add
=
partmessages
.
append
while
partfiles
.
next
():
add
(
Message
(
cStringIO
.
StringIO
(
partfiles
.
read
())))
# Check the different parts
returnedRanges
=
[]
add
=
returnedRanges
.
append
for
part
in
partmessages
:
range
=
part
[
'content-range'
]
start
,
end
,
size
=
rangeParse
.
search
(
range
).
groups
()
start
,
end
,
size
=
int
(
start
),
int
(
end
),
int
(
size
)
end
=
end
+
1
self
.
failIf
(
size
!=
len
(
self
.
data
),
'Part Content-Range header reported incorrect length. '
'Expected %d, got %d.'
%
(
len
(
self
.
data
),
size
))
part
.
rewindbody
()
body
=
part
.
fp
.
read
()
# Whotcha! Bug in MultiFile; the CRLF that is part of the boundary
# is returned as part of the body.
if
body
[
-
2
:]
==
'
\
r
\
n
'
:
body
=
body
[:
-
2
]
self
.
failIf
(
len
(
body
)
!=
end
-
start
,
'Part (%d, %d) is of wrong length, expected %d, got %d.'
%
(
start
,
end
,
end
-
start
,
len
(
body
)))
self
.
failIf
(
body
!=
self
.
data
[
start
:
end
],
'Part (%d, %d) has incorrect data. Expected %s, got %s.'
%
(
start
,
end
,
`self.data[start:end]`
,
`body`
))
add
((
start
,
end
))
# Copmare the ranges used with the expected range sets.
self
.
failIf
(
returnedRanges
!=
sets
,
'Got unexpected sets, expected %s, got %s'
%
(
sets
,
returnedRanges
))
# Unsatisfiable requests
def
testNegativeZero
(
self
):
self
.
expectUnsatisfiable
(
'-0'
)
def
testStartBeyondLength
(
self
):
self
.
expectUnsatisfiable
(
'1000-'
)
def
testMultipleUnsatisfiable
(
self
):
self
.
expectUnsatisfiable
(
'1000-1001,2000-,-0'
)
# Malformed Range header
def
testGarbage
(
self
):
self
.
expectOK
(
'kjhdkjhd = ew;jkj h eewh ew'
)
def
testIllegalSpec
(
self
):
self
.
expectOK
(
'notbytes=0-1000'
)
# Single ranges
def
testSimpleRange
(
self
):
self
.
expectSingleRange
(
'3-7'
,
3
,
8
)
def
testOpenEndedRange
(
self
):
self
.
expectSingleRange
(
'3-'
,
3
,
len
(
self
.
data
))
def
testSuffixRange
(
self
):
l
=
len
(
self
.
data
)
self
.
expectSingleRange
(
'-3'
,
l
-
3
,
l
)
def
testWithNegativeZero
(
self
):
# A satisfiable and an unsatisfiable range
self
.
expectSingleRange
(
'-0,3-23'
,
3
,
24
)
def
testAdjacentRanges
(
self
):
self
.
expectSingleRange
(
'21-25,10-20'
,
10
,
26
)
def
testEndOverflow
(
self
):
l
=
len
(
self
.
data
)
start
,
end
=
l
-
10
,
l
+
10
range
=
'%d-%d'
%
(
start
,
end
)
self
.
expectSingleRange
(
range
,
start
,
len
(
self
.
data
))
def
testBigFile
(
self
):
# Files of size 1<<16 are stored in linked Pdata objects. They are
# treated seperately in the range code.
self
.
uploadBigFile
()
join
=
3
*
(
1
<<
16
)
# A join between two linked objects
start
=
join
-
1000
end
=
join
+
1000
range
=
'%d-%d'
%
(
start
,
end
-
1
)
self
.
expectSingleRange
(
range
,
start
,
end
)
def
testBigFileEndOverflow
(
self
):
self
.
uploadBigFile
()
l
=
len
(
self
.
data
)
start
,
end
=
l
-
100
,
l
+
100
range
=
'%d-%d'
%
(
start
,
end
)
self
.
expectSingleRange
(
range
,
start
,
len
(
self
.
data
))
# Multiple ranges
def
testMultipleRanges
(
self
):
self
.
expectMultipleRanges
(
'3-7,10-15'
,
[(
3
,
8
),
(
10
,
16
)])
def
testMultipleRangesBigFile
(
self
):
self
.
uploadBigFile
()
self
.
expectMultipleRanges
(
'3-700,10-15,-10000'
,
[(
3
,
701
),
(
len
(
self
.
data
)
-
10000
,
len
(
self
.
data
))])
def
testMultipleRangesBigFileEndOverflow
(
self
):
self
.
uploadBigFile
()
l
=
len
(
self
.
data
)
start
,
end
=
l
-
100
,
l
+
100
self
.
expectMultipleRanges
(
'3-700,%s-%s'
%
(
start
,
end
),
[(
3
,
701
),
(
len
(
self
.
data
)
-
100
,
len
(
self
.
data
))])
# If-Range headers
def
testIllegalIfRange
(
self
):
# We assume that an illegal if-range is to be ignored, just like an
# illegal if-modified since.
self
.
expectSingleRange
(
'21-25,10-20'
,
10
,
26
,
if_range
=
'garbage'
)
def
testEqualIfRangeDate
(
self
):
self
.
expectSingleRange
(
'21-25,10-20'
,
10
,
26
,
if_range
=
self
.
createLastModifiedDate
())
def
testIsModifiedIfRangeDate
(
self
):
self
.
expectOK
(
'21-25,10-20'
,
if_range
=
self
.
createLastModifiedDate
(
offset
=-
100
))
def
testIsNotModifiedIfRangeDate
(
self
):
self
.
expectSingleRange
(
'21-25,10-20'
,
10
,
26
,
if_range
=
self
.
createLastModifiedDate
(
offset
=
100
))
def
testEqualIfRangeEtag
(
self
):
self
.
expectSingleRange
(
'21-25,10-20'
,
10
,
26
,
if_range
=
self
.
file
.
http__etag
())
def
testNotEqualIfRangeEtag
(
self
):
self
.
expectOK
(
'21-25,10-20'
,
if_range
=
self
.
file
.
http__etag
()
+
'bar'
)
def
test_suite
():
return
unittest
.
makeSuite
(
TestRequestRange
,
'test'
)
def
main
():
unittest
.
TextTestRunner
().
run
(
test_suite
())
def
debug
():
test_suite
().
debug
()
def
pdebug
():
import
pdb
pdb
.
run
(
'debug()'
)
if
__name__
==
'__main__'
:
if
len
(
sys
.
argv
)
>
1
:
globals
()[
sys
.
argv
[
1
]]()
else
:
main
()
lib/python/ZPublisher/HTTPRangeSupport.py
0 → 100644
View file @
d7712680
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
"""HTTP Range support utilities.
The RFC 2616 specification defines the 'Range' and 'If-Range' headers for
enabeling partial download of published resources. This module provides a
flag-interface and some support functions for implementing this functionality.
For an implementation example, see the File class in OFS/Image.py.
"""
__version__
=
'$Revision'
[
11
:
-
2
]
import
re
,
string
,
sys
import
Interface
WHITESPACE
=
re
.
compile
(
'
\
s*
'
, re.MULTILINE)
def parseRange(header):
"""RFC 2616 (HTTP 1.1) Range header parsing.
Convert a range header to a list of slice indexes, returned as (start, end)
tuples. If no end was given, end is None. Note that the RFC specifies the
end offset to be inclusive, we return python convention indexes, where the
end is exclusive. Syntactically incorrect headers are to be ignored, so if
we encounter one we return None.
"""
ranges = []
add = ranges.append
# First, clean out *all* whitespace. This is slightly more tolerant
# than the spec asks for, but hey, it makes this function much easier.
header = WHITESPACE.sub('', header)
# A range header only can specify a byte range
try: spec, sets = string.split(header, '
=
')
except ValueError: return None
if spec != '
bytes
':
return None
# The sets are delimited by commas.
sets = string.split(sets, '
,
')
# Filter out empty values, things like '
,,
' are allowed in the spec
sets = filter(None, sets)
# We need at least one set
if not sets:
return None
for set in sets:
try: start, end = string.split(set, '
-
')
except ValueError: return None
# Catch empty sets
if not start and not end:
return None
# Convert to integers or None (which will raise errors if
# non-integers were used (which is what we want)).
try:
if start == '': start = None
else: start = int(start)
if end == '': end = None
else: end = int(end)
except ValueError:
return None
# Special case: No start means the suffix format was used, which
# means the end value is actually a negative start value.
# Convert this by making it absolute.
# A -0 range is converted to sys.maxint, which will result in a
# Unsatisfiable response if no other ranges can by satisfied either.
if start is None:
start, end = -end, None
if not start:
start = sys.maxint
elif end is not None:
end = end + 1 # Make the end of the range exclusive
if end is not None and end <= start:
return None
# And store
add((start, end))
return ranges
def optimizeRanges(ranges, size):
"""Optimize Range sets, given those sets and the length of the resource.
Optimisation is done by first expanding relative start values and open
ends, then sorting and combining overlapping or adjacent ranges. We also
remove unsatisfiable ranges (where the start lies beyond the size of the
resource).
"""
expanded = []
add = expanded.append
for start, end in ranges:
if start < 0:
start = size + start
end = end or size
if end > size: end = size
# Only use satisfiable ranges
if start < size:
add((start, end))
ranges = expanded
ranges.sort()
ranges.reverse()
optimized = []
add = optimized.append
start, end = ranges.pop()
while ranges:
nextstart, nextend = ranges.pop()
# If the next range overlaps or is adjacent
if nextstart <= end:
# If it falls within the current range, discard
if nextend <= end:
continue
# Overlap, adjust end
end = nextend
else:
add((start, end))
start, end = nextstart, nextend
# Add the remaining optimized range
add((start, end))
return optimized
class HTTPRangeInterface(Interface.Base):
"""Objects implementing this Interface support the HTTP Range header.
Objects implementing support for the HTTP Range header will return partial
content as specified in RFC 2616. Note that the'
If
-
Range
' header must
either be implemented correctly or result in a normal '
200
OK
' response at
all times.
This interface specifies no methods, as this functionality can either be
implemented in the index_html or __call__ methods of a published object.
"""
lib/python/ZPublisher/tests/__init__.py
0 → 100644
View file @
d7712680
# This helps debugging.
lib/python/ZPublisher/tests/testHTTPRangeSupport.py
0 → 100644
View file @
d7712680
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
import
sys
sys
.
path
.
insert
(
0
,
'.'
)
try
:
from
ZPublisher.HTTPRangeSupport
import
parseRange
,
optimizeRanges
except
ImportError
:
sys
.
path
[
0
]
=
'../..'
from
ZPublisher.HTTPRangeSupport
import
parseRange
,
optimizeRanges
import
unittest
class
TestRangeHeaderParse
(
unittest
.
TestCase
):
# Utility methods
def
expectNone
(
self
,
header
):
result
=
parseRange
(
header
)
self
.
failUnless
(
result
is
None
,
'Expected None, got %s'
%
`result`
)
def
expectSets
(
self
,
header
,
sets
):
result
=
parseRange
(
header
)
self
.
failUnless
(
result
==
sets
,
'Expected %s, got %s'
%
(
`sets`
,
`result`
))
# Syntactically incorrect headers
def
testGarbage
(
self
):
self
.
expectNone
(
'kjahskjhdfkgkjbnbb ehgdk dsahg wlkjew lew
\
n
=lkdskue'
)
def
testIllegalSpec
(
self
):
self
.
expectNone
(
'notbytes=0-1000'
)
def
testNoSets
(
self
):
self
.
expectNone
(
'bytes='
)
def
testEmptySets
(
self
):
self
.
expectNone
(
'bytes=,,,'
)
def
testIllegalRange
(
self
):
self
.
expectNone
(
'bytes=foo-bar'
)
def
testAlmostIntegers
(
self
):
self
.
expectNone
(
'bytes=1.0-2.0'
)
def
testEndLowerThanStart
(
self
):
self
.
expectNone
(
'bytes=5-4'
)
# Correct headers
def
testSimpleRange
(
self
):
self
.
expectSets
(
'bytes=2-20'
,
[(
2
,
21
)])
def
testSimpleRangeAndEmpty
(
self
):
self
.
expectSets
(
'bytes=,2-20,'
,
[(
2
,
21
)])
def
testSuffixRange
(
self
):
self
.
expectSets
(
'bytes=-100'
,
[(
-
100
,
None
)])
def
testOpenEnded
(
self
):
self
.
expectSets
(
'bytes=100-'
,
[(
100
,
None
)])
def
testStartEqualsEnd
(
self
):
self
.
expectSets
(
'bytes=100-100'
,
[(
100
,
101
)])
def
testMultiple
(
self
):
self
.
expectSets
(
'bytes=-100,,1-2,20-'
,
[(
-
100
,
None
),
(
1
,
3
),
(
20
,
None
)])
def
testFirstByte
(
self
):
self
.
expectSets
(
'bytes=0-0'
,
[(
0
,
1
)])
def
testNegativeZero
(
self
):
self
.
expectSets
(
'bytes=-0'
,
[(
sys
.
maxint
,
None
)])
class
TestOptimizeRanges
(
unittest
.
TestCase
):
def
expectSets
(
self
,
sets
,
size
,
expect
):
result
=
optimizeRanges
(
sets
,
size
)
self
.
failUnless
(
result
==
expect
,
'Expected %s, got %s'
%
(
`expect`
,
`result`
))
def
testExpandOpenEnd
(
self
):
self
.
expectSets
([(
1
,
2
),
(
5
,
None
)],
50
,
[(
1
,
2
),
(
5
,
50
)])
def
testMakeAbsolute
(
self
):
self
.
expectSets
([(
1
,
2
),
(
-
5
,
None
)],
50
,
[(
1
,
2
),
(
45
,
50
)])
def
testNoOverlapInOrder
(
self
):
self
.
expectSets
([(
1
,
5
),
(
1000
,
2000
),
(
3000
,
None
)],
5000
,
[(
1
,
5
),
(
1000
,
2000
),
(
3000
,
5000
)])
def
testNoOverlapOutOfOrder
(
self
):
self
.
expectSets
([(
1000
,
2000
),
(
3000
,
None
),
(
1
,
5
)],
5000
,
[(
1
,
5
),
(
1000
,
2000
),
(
3000
,
5000
)])
def
testOverlapInOrder
(
self
):
self
.
expectSets
([(
1
,
10
),
(
8
,
20
),
(
25
,
None
)],
5000
,
[(
1
,
20
),
(
25
,
5000
)])
def
testOverlapOutOfOrder
(
self
):
self
.
expectSets
([(
25
,
50
),
(
8
,
None
),
(
1
,
10
)],
5000
,
[(
1
,
5000
)])
def
testAdjacentInOrder
(
self
):
self
.
expectSets
([(
1
,
10
),
(
10
,
20
),
(
25
,
50
)],
5000
,
[(
1
,
20
),
(
25
,
50
)])
def
testAdjacentOutOfOrder
(
self
):
self
.
expectSets
([(
-
5
,
None
),
(
40
,
45
)],
50
,
[(
40
,
50
)])
def
testOverLapAndOverflow
(
self
):
# Note that one endpoint lies beyond the end.
self
.
expectSets
([(
-
5
,
None
),
(
40
,
100
)],
50
,
[(
40
,
50
)])
def
testRemoveUnsatisfiable
(
self
):
self
.
expectSets
([(
sys
.
maxint
,
None
),
(
10
,
20
)],
50
,
[(
10
,
20
)])
def
test_suite
():
suite
=
unittest
.
TestSuite
()
suite
.
addTest
(
unittest
.
makeSuite
(
TestRangeHeaderParse
,
'test'
))
suite
.
addTest
(
unittest
.
makeSuite
(
TestOptimizeRanges
,
'test'
))
return
suite
def
main
():
unittest
.
TextTestRunner
().
run
(
test_suite
())
def
debug
():
test_suite
().
debug
()
def
pdebug
():
import
pdb
pdb
.
run
(
'debug()'
)
if
__name__
==
'__main__'
:
if
len
(
sys
.
argv
)
>
1
:
globals
()[
sys
.
argv
[
1
]]()
else
:
main
()
lib/python/ZServer/FTPServer.py
View file @
d7712680
...
...
@@ -369,6 +369,9 @@ class zope_ftp_channel(ftp_channel):
response
=
make_response
(
self
,
self
.
retr_completion
,
line
[
1
])
self
.
_response_producers
=
response
.
stdout
.
_producers
request
=
FTPRequest
(
line
[
1
],
'RETR'
,
self
,
response
)
# Support download restarts if possible.
if
self
.
restart_position
>
0
:
request
.
environ
[
'HTTP_RANGE'
]
=
'bytes=%d-'
%
self
.
restart_position
handle
(
self
.
module
,
request
,
response
)
def
retr_completion
(
self
,
file
,
response
):
...
...
lib/python/webdav/Resource.py
View file @
d7712680
...
...
@@ -85,7 +85,7 @@
"""WebDAV support - resource objects."""
__version__
=
'$Revision: 1.4
2
$'
[
11
:
-
2
]
__version__
=
'$Revision: 1.4
3
$'
[
11
:
-
2
]
import
sys
,
os
,
string
,
mimetypes
,
davcmds
,
ExtensionClass
,
Lockable
from
common
import
absattr
,
aq_base
,
urlfix
,
rfc1123_date
,
tokenFinder
,
urlbase
...
...
@@ -94,6 +94,7 @@ from urllib import quote, unquote
from
AccessControl
import
getSecurityManager
from
WriteLockInterface
import
WriteLockInterface
import
Globals
,
time
from
ZPublisher.HTTPRangeSupport
import
HTTPRangeInterface
class
Resource
(
ExtensionClass
.
Base
,
Lockable
.
LockableItem
):
"""The Resource mixin class provides basic WebDAV support for
...
...
@@ -131,6 +132,12 @@ class Resource(ExtensionClass.Base, Lockable.LockableItem):
response
.
setHeader
(
'Date'
,
rfc1123_date
(),
1
)
response
.
setHeader
(
'MS-Author-Via'
,
'DAV'
)
# HTTP Range support
if
HTTPRangeInterface
.
isImplementedBy
(
self
):
response
.
setHeader
(
'Accept-Ranges'
,
'bytes'
)
else
:
response
.
setHeader
(
'Accept-Ranges'
,
'none'
)
def
dav__validate
(
self
,
object
,
methodname
,
REQUEST
):
msg
=
'<strong>You are not authorized to access this resource.</strong>'
method
=
None
...
...
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