Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
F
flaskdav
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
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
flaskdav
Commits
7e4fdca9
Commit
7e4fdca9
authored
Dec 09, 2015
by
iv
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add PROPFIND support.
parent
5ef4c869
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
436 additions
and
6 deletions
+436
-6
README.md
README.md
+5
-2
flaskdav.py
flaskdav.py
+15
-3
utils.py
utils.py
+416
-1
No files found.
README.md
View file @
7e4fdca9
...
...
@@ -21,8 +21,11 @@ The HMAC key is reset each time you restart the server.
flaskdav is under the GPL2 license.
utils.py is a modified version of PyWebDAV's server/fshandler.py under GPL2 License
original file: https://code.google.com/p/pywebdav/source/browse/pywebdav/server/fshandler.py
utils.py is a modified version of PyWebDAV's files that are under GPL2 License
original files:
*
https://code.google.com/p/pywebdav/source/browse/pywebdav/server/fshandler.py
*
https://code.google.com/p/pywebdav/source/browse/pywebdav/lib/propfind.py
*
https://code.google.com/p/pywebdav/source/browse/pywebdav/lib/utils.py
## TODO
-
LOCK/UNLOCK support
...
...
flaskdav.py
View file @
7e4fdca9
...
...
@@ -195,11 +195,23 @@ class WebDAV(MethodView):
return
response
def
propfind
(
self
,
pathname
):
"""
PROPFIND:
return informations about the properties of a resource/collection
into a XML body response
"""
response
=
g
.
response
# currently unsupported
response
.
status
=
'501'
pf
=
utils
.
PropfindProcessor
(
request
.
path
,
app
.
fs_handler
,
request
.
headers
.
get
(
'Depth'
,
'infinity'
),
self
.
get_body
())
try
:
response
.
data
=
pf
.
create_response
()
+
'
\
n
'
except
IOError
:
response
.
status
=
'404'
return
response
def
proppatch
(
self
,
pathname
):
...
...
utils.py
View file @
7e4fdca9
import
urllib
import
sys
import
urlparse
import
os
import
time
from
string
import
joinfields
,
split
,
lower
import
types
import
shutil
from
string
import
joinfields
,
split
,
lower
from
xml.dom
import
minidom
domimpl
=
minidom
.
getDOMImplementation
()
BUFFER_SIZE
=
128
*
1000
class
Resource
(
object
):
...
...
@@ -194,3 +198,414 @@ class FilesystemHandler():
return
507
except
:
return
403
def
parse_propfind
(
xml_doc
):
"""
Parse an propfind xml file and return a list of props
"""
doc
=
minidom
.
parseString
(
xml_doc
)
request_type
=
None
props
=
{}
namespaces
=
[]
if
doc
.
getElementsByTagNameNS
(
"DAV:"
,
"allprop"
):
request_type
=
RT_ALLPROP
elif
doc
.
getElementsByTagNameNS
(
"DAV:"
,
"propname"
):
request_type
=
RT_PROPNAME
else
:
request_type
=
RT_PROP
for
i
in
doc
.
getElementsByTagNameNS
(
"DAV:"
,
"prop"
):
for
e
in
i
.
childNodes
:
if
e
.
nodeType
!=
minidom
.
Node
.
ELEMENT_NODE
:
continue
ns
=
e
.
namespaceURI
ename
=
e
.
localName
if
props
.
has_key
(
ns
):
props
[
ns
].
append
(
ename
)
else
:
props
[
ns
]
=
[
ename
]
namespaces
.
append
(
ns
)
return
request_type
,
props
,
namespaces
class
PropfindProcessor
:
""" parse a propfind xml element and extract props
It will set the following instance vars:
request_class : ALLPROP | PROPNAME | PROP
proplist : list of properties
nsmap : map of namespaces
The list of properties will contain tuples of the form
(element name, ns_prefix, ns_uri)
"""
def
__init__
(
self
,
uri
,
dataclass
,
depth
,
body
):
self
.
request_type
=
None
self
.
nsmap
=
{}
self
.
proplist
=
{}
self
.
default_ns
=
None
self
.
_depth
=
str
(
depth
)
self
.
_uri
=
uri
.
rstrip
(
'/'
)
self
.
_has_body
=
None
# did we parse a body?
self
.
_dataclass
=
dataclass
if
body
:
self
.
request_type
,
self
.
proplist
,
self
.
namespaces
=
\
parse_propfind
(
body
)
self
.
_has_body
=
True
def
create_response
(
self
):
""" Create the multistatus response
This will be delegated to the specific method
depending on which request (allprop, propname, prop)
was found.
If we get a PROPNAME then we simply return the list with empty
values which we get from the interface class
If we get an ALLPROP we first get the list of properties and then
we do the same as with a PROP method.
"""
# check if resource exists
localpath
=
self
.
_dataclass
.
uri2local
(
self
.
_uri
)
if
not
os
.
path
.
exists
(
localpath
):
raise
IOError
if
self
.
request_type
==
2
:
# propname
df
=
self
.
create_propname
()
elif
self
.
request_type
==
3
:
# prop
df
=
self
.
create_prop
()
else
:
# allprop
df
=
self
.
create_allprop
()
return
df
def
get_propnames
(
self
):
""" return the property names allowed """
# defined properties ; format is namespace: [list of properties]
return
{
"DAV:"
:
(
'creationdate'
,
#'displayname',
#'getcontentlanguage',
'getcontentlength'
,
#'getcontenttype',
#'getetag',
'getlastmodified'
,
#'lockdiscovery',
'resourcetype'
,
#'source',
#'supportedlock'
)
}
def
get_prop
(
self
,
path
,
ns
,
propname
):
""" return the value of a given property
uri -- uri of the object to get the property of
ns -- namespace of the property
pname -- name of the property
"""
info
=
os
.
stat
(
path
)
if
propname
==
'creationdate'
:
response
=
info
[
9
]
elif
propname
==
'getlastmodified'
:
response
=
info
[
8
]
elif
propname
==
'getcontentlength'
:
response
=
'0'
if
not
os
.
path
.
isfile
(
path
)
else
str
(
info
[
6
])
elif
propname
==
'resourcetype'
:
response
=
int
(
os
.
path
.
isfile
(
path
))
return
str
(
response
)
def
create_propname
(
self
):
""" create a multistatus response for the prop names """
dc
=
self
.
_dataclass
# create the document generator
doc
=
domimpl
.
createDocument
(
None
,
"multistatus"
,
None
)
ms
=
doc
.
documentElement
ms
.
setAttribute
(
"xmlns:D"
,
"DAV:"
)
ms
.
tagName
=
'D:multistatus'
if
self
.
_depth
==
"0"
:
pnames
=
self
.
get_propnames
()
re
=
self
.
mk_propname_response
(
self
.
_uri
,
pnames
,
doc
)
ms
.
appendChild
(
re
)
elif
self
.
_depth
==
"1"
:
pnames
=
self
.
get_propnames
()
re
=
self
.
mk_propname_response
(
self
.
_uri
,
pnames
,
doc
)
ms
.
appendChild
(
re
)
for
newuri
in
dc
.
get_children
(
self
.
_uri
):
pnames
=
self
.
get_propnames
()
re
=
self
.
mk_propname_response
(
newuri
,
pnames
,
doc
)
ms
.
appendChild
(
re
)
else
:
uri_list
=
[
self
.
_uri
]
while
uri_list
:
uri
=
uri_list
.
pop
()
pnames
=
self
.
get_propnames
()
re
=
self
.
mk_propname_response
(
uri
,
pnames
,
doc
)
ms
.
appendChild
(
re
)
uri_childs
=
self
.
_dataclass
.
get_children
(
uri
)
if
uri_childs
:
uri_list
.
extend
(
uri_childs
)
return
doc
.
toxml
(
encoding
=
"utf-8"
)
def
create_allprop
(
self
):
""" return a list of all properties """
self
.
proplist
=
{}
self
.
namespaces
=
[]
for
ns
,
plist
in
self
.
get_propnames
().
items
():
self
.
proplist
[
ns
]
=
plist
self
.
namespaces
.
append
(
ns
)
return
self
.
create_prop
()
def
create_prop
(
self
):
""" handle a <prop> request
This will
1. set up the <multistatus>-Framework
2. read the property values for each URI
(which is dependant on the Depth header)
This is done by the get_propvalues() method.
3. For each URI call the append_result() method
to append the actual <result>-Tag to the result
document.
We differ between "good" properties, which have been
assigned a value by the interface class and "bad"
properties, which resulted in an error, either 404
(Not Found) or 403 (Forbidden).
"""
# create the document generator
doc
=
domimpl
.
createDocument
(
None
,
"multistatus"
,
None
)
ms
=
doc
.
documentElement
ms
.
setAttribute
(
"xmlns:D"
,
"DAV:"
)
ms
.
tagName
=
'D:multistatus'
if
self
.
_depth
==
"0"
:
gp
,
bp
=
self
.
get_propvalues
(
self
.
_uri
)
res
=
self
.
mk_prop_response
(
self
.
_uri
,
gp
,
bp
,
doc
)
ms
.
appendChild
(
res
)
elif
self
.
_depth
==
"1"
:
gp
,
bp
=
self
.
get_propvalues
(
self
.
_uri
)
res
=
self
.
mk_prop_response
(
self
.
_uri
,
gp
,
bp
,
doc
)
ms
.
appendChild
(
res
)
for
newuri
in
self
.
_dataclass
.
get_children
(
self
.
_uri
):
gp
,
bp
=
self
.
get_propvalues
(
newuri
)
res
=
self
.
mk_prop_response
(
newuri
,
gp
,
bp
,
doc
)
ms
.
appendChild
(
res
)
elif
self
.
_depth
==
'infinity'
:
uri_list
=
[
self
.
_uri
]
while
uri_list
:
uri
=
uri_list
.
pop
()
gp
,
bp
=
self
.
get_propvalues
(
uri
)
res
=
self
.
mk_prop_response
(
uri
,
gp
,
bp
,
doc
)
ms
.
appendChild
(
res
)
uri_childs
=
self
.
_dataclass
.
get_children
(
uri
)
if
uri_childs
:
uri_list
.
extend
(
uri_childs
)
return
doc
.
toxml
(
encoding
=
"utf-8"
)
def
mk_propname_response
(
self
,
uri
,
propnames
,
doc
):
""" make a new <prop> result element for a PROPNAME request
This will simply format the propnames list.
propnames should have the format {NS1 : [prop1, prop2, ...], NS2: ...}
"""
re
=
doc
.
createElement
(
"D:response"
)
if
self
.
_dataclass
.
baseuri
:
uri
=
self
.
_dataclass
.
baseuri
+
'/'
+
'/'
.
join
(
uri
.
split
(
'/'
)[
3
:])
# write href information
uparts
=
urlparse
.
urlparse
(
uri
)
fileloc
=
uparts
[
2
]
href
=
doc
.
createElement
(
"D:href"
)
huri
=
doc
.
createTextNode
(
uparts
[
0
]
+
'://'
+
'/'
.
join
(
uparts
[
1
:
2
])
+
urllib
.
quote
(
fileloc
))
href
.
appendChild
(
huri
)
re
.
appendChild
(
href
)
ps
=
doc
.
createElement
(
"D:propstat"
)
nsnum
=
0
for
ns
,
plist
in
propnames
.
items
():
# write prop element
pr
=
doc
.
createElement
(
"D:prop"
)
nsp
=
"ns"
+
str
(
nsnum
)
pr
.
setAttribute
(
"xmlns:"
+
nsp
,
ns
)
nsnum
+=
1
# write propertynames
for
p
in
plist
:
pe
=
doc
.
createElement
(
nsp
+
":"
+
p
)
pr
.
appendChild
(
pe
)
ps
.
appendChild
(
pr
)
re
.
appendChild
(
ps
)
return
re
def
mk_prop_response
(
self
,
uri
,
good_props
,
bad_props
,
doc
):
""" make a new <prop> result element
We differ between the good props and the bad ones for
each generating an extra <propstat>-Node (for each error
one, that means).
"""
re
=
doc
.
createElement
(
"D:response"
)
# append namespaces to response
nsnum
=
0
for
nsname
in
self
.
namespaces
:
if
nsname
!=
'DAV:'
:
re
.
setAttribute
(
"xmlns:ns"
+
str
(
nsnum
),
nsname
)
nsnum
+=
1
if
self
.
_dataclass
.
baseuri
:
uri
=
urlparse
.
urljoin
(
self
.
_dataclass
.
baseuri
,
uri
)
# write href information
uparts
=
urlparse
.
urlparse
(
uri
)
fileloc
=
uparts
[
2
]
href
=
doc
.
createElement
(
"D:href"
)
huri
=
doc
.
createTextNode
(
uparts
[
0
]
+
'://'
+
'/'
.
join
(
uparts
[
1
:
2
])
+
urllib
.
quote
(
fileloc
))
href
.
appendChild
(
huri
)
re
.
appendChild
(
href
)
# write good properties
ps
=
doc
.
createElement
(
"D:propstat"
)
if
good_props
:
re
.
appendChild
(
ps
)
gp
=
doc
.
createElement
(
"D:prop"
)
for
ns
in
good_props
.
keys
():
if
ns
!=
'DAV:'
:
ns_prefix
=
"ns"
+
str
(
self
.
namespaces
.
index
(
ns
))
+
":"
else
:
ns_prefix
=
'D:'
for
p
,
v
in
good_props
[
ns
].
items
():
pe
=
doc
.
createElement
(
ns_prefix
+
str
(
p
))
if
isinstance
(
v
,
minidom
.
Element
):
pe
.
appendChild
(
v
)
elif
isinstance
(
v
,
list
):
for
val
in
v
:
pe
.
appendChild
(
val
)
else
:
if
p
==
"resourcetype"
:
if
v
==
1
:
ve
=
doc
.
createElement
(
"D:collection"
)
pe
.
appendChild
(
ve
)
else
:
ve
=
doc
.
createTextNode
(
v
)
pe
.
appendChild
(
ve
)
gp
.
appendChild
(
pe
)
ps
.
appendChild
(
gp
)
s
=
doc
.
createElement
(
"D:status"
)
t
=
doc
.
createTextNode
(
"HTTP/1.1 200 OK"
)
s
.
appendChild
(
t
)
ps
.
appendChild
(
s
)
re
.
appendChild
(
ps
)
# now write the errors!
if
len
(
bad_props
.
items
()):
# write a propstat for each error code
for
ecode
in
bad_props
.
keys
():
ps
=
doc
.
createElement
(
"D:propstat"
)
re
.
appendChild
(
ps
)
bp
=
doc
.
createElement
(
"D:prop"
)
ps
.
appendChild
(
bp
)
for
ns
in
bad_props
[
ecode
].
keys
():
if
ns
!=
'DAV:'
:
ns_prefix
=
"ns"
+
str
(
self
.
namespaces
.
index
(
ns
))
+
":"
else
:
ns_prefix
=
'D:'
for
p
in
bad_props
[
ecode
][
ns
]:
pe
=
doc
.
createElement
(
ns_prefix
+
str
(
p
))
bp
.
appendChild
(
pe
)
s
=
doc
.
createElement
(
"D:status"
)
t
=
doc
.
createTextNode
(
"HTTP/1.1 %s"
%
(
ecode
))
s
.
appendChild
(
t
)
ps
.
appendChild
(
s
)
re
.
appendChild
(
ps
)
# return the new response element
return
re
def
get_propvalues
(
self
,
uri
):
""" create lists of property values for an URI
We create two lists for an URI: the properties for
which we found a value and the ones for which we
only got an error, either because they haven't been
found or the user is not allowed to read them.
"""
good_props
=
{}
bad_props
=
{}
path
=
self
.
_dataclass
.
uri2local
(
uri
)
ddc
=
self
.
_dataclass
for
ns
,
plist
in
self
.
proplist
.
items
():
good_props
[
ns
]
=
{}
for
prop
in
plist
:
ec
=
0
try
:
r
=
self
.
get_prop
(
path
,
ns
,
prop
)
good_props
[
ns
][
prop
]
=
r
except
UnboundLocalError
,
error_code
:
ec
=
error_code
[
0
]
# ignore props with error_code if 0 (invisible)
if
ec
==
0
:
continue
if
ec
in
bad_props
:
if
ns
in
bad_props
[
ec
]:
bad_props
[
ec
][
ns
].
append
(
prop
)
else
:
bad_props
[
ec
][
ns
]
=
[
prop
]
else
:
bad_props
[
ec
]
=
{
ns
:
[
prop
]}
return
good_props
,
bad_props
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