# (c) 2009-2018 Martin Wendt and contributors; see WsgiDAV https://github.com/mar10/wsgidav
# Original PyFileServer (c) 2005 Ho Chun Wei.
# Licensed under the MIT license:
# http://www.opensource.org/licenses/mit-license.php
"""
WSGI middleware that handles GET requests on collections to display directories.
"""
import os
import sys
from wsgidav import __version__, compat, util
from wsgidav.dav_error import HTTP_MEDIATYPE_NOT_SUPPORTED, HTTP_OK, DAVError
from wsgidav.middleware import BaseMiddleware
from wsgidav.util import safeReEncode
__docformat__ = "reStructuredText"
_logger = util.getModuleLogger(__name__)
msOfficeTypeToExtMap = {
"excel": ("xls", "xlt", "xlm", "xlsm", "xlsx", "xltm", "xltx"),
"powerpoint": ("pps", "ppt", "pptm", "pptx", "potm", "potx", "ppsm", "ppsx"),
"word": ("doc", "dot", "docm", "docx", "dotm", "dotx"),
"visio": ("vsd", "vsdm", "vsdx", "vstm", "vstx"),
}
msOfficeExtToTypeMap = {}
for t, el in msOfficeTypeToExtMap.items():
for e in el:
msOfficeExtToTypeMap[e] = t
PAGE_CSS = """\
img { border: 0; padding: 0 2px; vertical-align: text-bottom; }
th, td { padding: 2px 20px 2px 2px; }
th { text-align: left; }
th.right { text-align: right; }
td { font-family: monospace; vertical-align: bottom; white-space: pre; }
td.right { text-align: right; }
table { border: 0; }
a.symlink { font-style: italic; }
p.trailer { font-size: smaller; }
"""
PAGE_SCRIPT = """\
function onLoad() {
// console.log("loaded.");
}
/* Event delegation handler for clicks on a-tags with class 'msoffice'. */
function onClickTable(event) {
var target = event.target || event.srcElement,
href = target.href;
if( href && target.className === "msoffice" ){
if( openWithSharePointPlugin(href) ){
// prevent default processing
return false;
}
}
}
function openWithSharePointPlugin(url) {
var res = false,
control = null,
isFF = false;
// Get the most recent version of the SharePoint plugin
if( window.ActiveXObject ){
try {
control = new ActiveXObject("SharePoint.OpenDocuments.3"); // Office 2007
} catch(e) {
try {
control = new ActiveXObject("SharePoint.OpenDocuments.2"); // Office 2003
} catch(e2) {
try {
control = new ActiveXObject("SharePoint.OpenDocuments.1"); // Office 2000/XP
} catch(e3) {
window.console && console.warn("Could not create ActiveXObject('SharePoint.OpenDocuments'). Check your browsers security settings.");
return false;
}
}
}
if( !control ){
window.console && console.warn("Cannot instantiate the required ActiveX control to open the document. This is most likely because you do not have Office installed or you have an older version of Office.");
}
} else {
window.console && console.log("Non-IE: using FFWinPlugin Plug-in...");
control = document.getElementById("winFirefoxPlugin");
isFF = true;
}
try {
// window.console && console.log("SharePoint.OpenDocuments.EditDocument('" + url + "')...");
res = control.EditDocument(url);
// window.console && console.log("SharePoint.OpenDocuments.EditDocument('" + url + "')... res = ", res);
if( !res ){
window.console && console.warn("SharePoint.OpenDocuments.EditDocument('" + url + "') returned false.");
}
} catch (e){
window.console && console.warn("SharePoint.OpenDocuments.EditDocument('" + url + "') failed.", e);
}
return res;
}
""" # noqa
[docs]class WsgiDavDirBrowser(BaseMiddleware):
"""WSGI middleware that handles GET requests on collections to display directories."""
def __init__(self, application, config):
self._application = application
self._verbose = 2
[docs] def __call__(self, environ, start_response):
path = environ["PATH_INFO"]
davres = None
if environ["wsgidav.provider"]:
davres = environ["wsgidav.provider"].getResourceInst(path, environ)
if environ["REQUEST_METHOD"] in ("GET", "HEAD") and davres and davres.isCollection:
# if "mozilla" not in environ.get("HTTP_USER_AGENT").lower():
# # issue 14: Nautilus sends GET on collections
# # http://code.google.com/p/wsgidav/issues/detail?id=14
# util.status("Directory browsing disabled for agent '{}'"
# .format(environ.get("HTTP_USER_AGENT")))
# self._fail(HTTP_NOT_IMPLEMENTED)
# return self._application(environ, start_response)
if util.getContentLength(environ) != 0:
self._fail(HTTP_MEDIATYPE_NOT_SUPPORTED,
"The server does not handle any body content.")
if environ["REQUEST_METHOD"] == "HEAD":
return util.sendStatusResponse(environ, start_response, HTTP_OK)
# Support DAV mount (http://www.ietf.org/rfc/rfc4709.txt)
dirConfig = environ["wsgidav.config"].get("dir_browser", {})
if dirConfig.get("davmount") and "davmount" in environ.get("QUERY_STRING", ""):
# collectionUrl = davres.getHref()
collectionUrl = util.makeCompleteUrl(environ)
collectionUrl = collectionUrl.split("?")[0]
res = """
<dm:mount xmlns:dm="http://purl.org/NET/webdav/mount">
<dm:url>{}</dm:url>
</dm:mount>""".format(collectionUrl)
# TODO: support <dm:open>%s</dm:open>
start_response("200 OK", [("Content-Type", "application/davmount+xml"),
("Content-Length", str(len(res))),
("Cache-Control", "private"),
("Date", util.getRfc1123Time()),
])
return [res]
# Profile calls
# if True:
# from cProfile import Profile
# profile = Profile()
# profile.runcall(self._listDirectory, environ, start_response)
# # sort: 0:"calls",1:"time", 2: "cumulative"
# profile.print_stats(sort=2)
return self._listDirectory(davres, environ, start_response)
return self._application(environ, start_response)
[docs] @staticmethod
def isSuitable(config):
return config.get("dir_browser") and config["dir_browser"].get("enable", True)
def _fail(self, value, contextinfo=None, srcexception=None, errcondition=None):
"""Wrapper to raise (and log) DAVError."""
e = DAVError(value, contextinfo, srcexception, errcondition)
if self._verbose >= 2:
_logger.error("Raising DAVError {}".format(
safeReEncode(e.getUserInfo(), sys.stdout.encoding)))
raise e
def _listDirectory(self, davres, environ, start_response):
"""
@see: http://www.webdav.org/specs/rfc4918.html#rfc.section.9.4
"""
assert davres.isCollection
dirConfig = environ["wsgidav.config"].get("dir_browser", {})
displaypath = compat.unquote(davres.getHref())
isReadOnly = environ["wsgidav.provider"].isReadOnly()
trailer = dirConfig.get("response_trailer")
if trailer:
trailer = trailer.replace(
"${version}",
"<a href='https://github.com/mar10/wsgidav/'>WsgiDAV/{}</a>".format(__version__))
trailer = trailer.replace("${time}", util.getRfc1123Time())
else:
trailer = ("<a href='https://github.com/mar10/wsgidav/'>WsgiDAV/{}</a> - {}"
.format(__version__, util.getRfc1123Time()))
html = []
html.append(
"<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01//EN' "
"'http://www.w3.org/TR/html4/strict.dtd'>")
html.append("<html>")
html.append("<head>")
html.append(
"<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>")
html.append("<meta name='generator' content='WsgiDAV {}'>".format(__version__))
html.append("<title>WsgiDAV - Index of {} </title>".format(displaypath))
html.append("<script type='text/javascript'>{}</script>".format(PAGE_SCRIPT))
html.append("<style type='text/css'>{}</style>".format(PAGE_CSS))
# Special CSS to enable MS Internet Explorer behaviour
if dirConfig.get("ms_mount"):
html.append(
"<style type='text/css'> A {behavior: url(#default#AnchorClick);} </style>")
if dirConfig.get("ms_sharepoint_plugin"):
html.append(
"<object id='winFirefoxPlugin' type='application/x-sharepoint' width='0' "
"height='0' style=''visibility: hidden;'></object>")
html.append("</head>")
html.append("<body onload='onLoad()'>")
# Title
html.append("<h1>Index of {}</h1>".format(displaypath))
# Add DAV-Mount link and Web-Folder link
links = []
if dirConfig.get("davmount"):
links.append("<a title='Open this folder in a WebDAV client.' "
"href='{}?davmount'>Mount</a>".format(util.makeCompleteUrl(environ)))
if dirConfig.get("ms_mount"):
links.append("<a title='Open as Web Folder (requires Microsoft Internet Explorer)' "
"href='' FOLDER='{}'>Open as Web Folder</a>"
.format(util.makeCompleteUrl(environ)))
# html.append("<a href='' FOLDER='{}setup.py'>Open setup.py as WebDAV</a>"
# .format(util.makeCompleteUrl(environ)))
if links:
html.append("<p>{}</p>".format(" – ".join(links)))
html.append("<hr>")
# Listing
html.append("<table onclick='return onClickTable(event)'>")
html.append("<thead>")
html.append(
"<tr><th>Name</th> <th>Type</th> <th class='right'>Size</th> "
"<th class='right'>Last modified</th> </tr>")
html.append("</thead>")
html.append("<tbody>")
if davres.path in ("", "/"):
html.append(
"<tr><td>Top level share</td> <td></td> <td></td> <td></td> </tr>")
else:
parentUrl = util.getUriParent(davres.getHref())
html.append("<tr><td><a href='" + parentUrl +
"'>Parent Directory</a></td> <td></td> <td></td> <td></td> </tr>")
# Ask collection for member info list
dirInfoList = davres.getDirectoryInfo()
if dirInfoList is None:
# No pre-build info: traverse members
dirInfoList = []
childList = davres.getDescendants(depth="1", addSelf=False)
for res in childList:
di = res.getDisplayInfo()
href = res.getHref()
infoDict = {"href": href,
"class": "",
"displayName": res.getDisplayName(),
"lastModified": res.getLastModified(),
"isCollection": res.isCollection,
"contentLength": res.getContentLength(),
"displayType": di.get("type"),
"displayTypeComment": di.get("typeComment"),
}
if not isReadOnly and not res.isCollection:
ext = os.path.splitext(href)[1].lstrip(".").lower()
officeType = msOfficeExtToTypeMap.get(ext)
if officeType:
if dirConfig.get("ms_sharepoint_plugin"):
infoDict["class"] = "msoffice"
elif dirConfig.get("ms_sharepoint_urls"):
infoDict[
"href"] = "ms-{}:ofe|u|{}".format(officeType, href)
dirInfoList.append(infoDict)
#
for infoDict in dirInfoList:
lastModified = infoDict.get("lastModified")
if lastModified is None:
infoDict["strModified"] = ""
else:
infoDict["strModified"] = util.getRfc1123Time(lastModified)
infoDict["strSize"] = "-"
if not infoDict.get("isCollection"):
contentLength = infoDict.get("contentLength")
if contentLength is not None:
infoDict["strSize"] = util.byteNumberString(contentLength)
html.append("""\
<tr><td><a href="{href}" class="{class}">{displayName}</a></td>
<td>{displayType}</td>
<td class='right'>{strSize}</td>
<td class='right'>{strModified}</td></tr>""".format(**infoDict))
# html.append("""\
# <tr><td><a href="%(href)s" class="%(class)s">%(displayName)s</a></td>
# <td>%(displayType)s</td>
# <td class='right'>%(strSize)s</td>
# <td class='right'>%(strModified)s</td></tr>""" % infoDict)
html.append("</tbody>")
html.append("</table>")
html.append("<hr>")
if "http_authenticator.username" in environ:
if environ.get("http_authenticator.username"):
html.append("<p>Authenticated user: '{}', realm: '{}'.</p>"
.format(environ.get("http_authenticator.username"),
environ.get("http_authenticator.realm")))
# else:
# html.append("<p>Anonymous</p>")
if trailer:
html.append("<p class='trailer'>{}</p>".format(trailer))
html.append("</body></html>")
body = "\n".join(html)
body = compat.to_bytes(body)
start_response("200 OK", [("Content-Type", "text/html"),
("Content-Length", str(len(body))),
("Date", util.getRfc1123Time()),
])
return [body]