def ref_url_to_path(self, ref_url): """Convert a refUrl to a path, by stripping the share prefix. Used to calculate the <path> from a storage key by inverting get_ref_url(). """ print("/" + compat.unquote(util.lstripstr( ref_url, self.share_path)).lstrip("/")) return "/" + compat.unquote(util.lstripstr( ref_url, self.share_path)).lstrip("/")
def __call__(self, environ, start_response): # util.log("SCRIPT_NAME='{}', PATH_INFO='{}'".format( # environ.get("SCRIPT_NAME"), environ.get("PATH_INFO"))) path = environ["PATH_INFO"] # (#73) Failed on processing non-iso-8859-1 characters on Python 3 # # Note: we encode using UTF-8 here (falling back to ISO-8859-1)! # This seems to be wrong, since per PEP 3333 PATH_INFO is always ISO-8859-1 encoded # (see https://www.python.org/dev/peps/pep-3333/#unicode-issues). # But also seems to resolve errors when accessing resources with Chinese characters, for # example. # This is done by default for Python 3, but can be turned off in settings. re_encode_path_info = self.config.get("re_encode_path_info") if re_encode_path_info is None: re_encode_path_info = compat.PY3 if re_encode_path_info: path = environ["PATH_INFO"] = compat.wsgi_to_bytes(path).decode() # We optionally unquote PATH_INFO here, although this should already be # done by the server (#8). if self.config.get("unquote_path_info", False): path = compat.unquote(environ["PATH_INFO"]) # GC issue 22: Pylons sends root as u'/' if not compat.is_native(path): _logger.warn("Got non-native PATH_INFO: {!r}".format(path)) # path = path.encode("utf8") path = compat.to_native(path) # Always adding these values to environ: environ["wsgidav.config"] = self.config environ["wsgidav.provider"] = None environ["wsgidav.verbose"] = self.verbose # Find DAV provider that matches the share share = None lower_path = path.lower() for r in self.sortedShareList: # @@: Case sensitivity should be an option of some sort here; # os.path.normpath might give the preferred case for a filename. if r == "/": share = r break elif lower_path == r or lower_path.startswith(r + "/"): share = r break # Note: we call the next app, even if provider is None, because OPTIONS # must still be handled. # All other requests will result in '404 Not Found' if share is not None: share_data = self.providerMap.get(share) environ["wsgidav.provider"] = share_data["provider"] # TODO: test with multi-level realms: 'aa/bb' # TODO: test security: url contains '..' # Transform SCRIPT_NAME and PATH_INFO # (Since path and share are unquoted, this also fixes quoted values.) if share == "/" or not share: environ["PATH_INFO"] = path else: environ["SCRIPT_NAME"] += share environ["PATH_INFO"] = path[len(share) :] # assert isinstance(path, str) assert compat.is_native(path) # See http://mail.python.org/pipermail/web-sig/2007-January/002475.html # for some clarification about SCRIPT_NAME/PATH_INFO format # SCRIPT_NAME starts with '/' or is empty assert environ["SCRIPT_NAME"] == "" or environ["SCRIPT_NAME"].startswith("/") # SCRIPT_NAME must not have a trailing '/' assert environ["SCRIPT_NAME"] in ("", "/") or not environ[ "SCRIPT_NAME" ].endswith("/") # PATH_INFO starts with '/' assert environ["PATH_INFO"] == "" or environ["PATH_INFO"].startswith("/") start_time = time.time() def _start_response_wrapper(status, response_headers, exc_info=None): # Postprocess response headers headerDict = {} for header, value in response_headers: if header.lower() in headerDict: _logger.error("Duplicate header in response: {}".format(header)) headerDict[header.lower()] = value # Check if we should close the connection after this request. # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4 forceCloseConnection = False currentContentLength = headerDict.get("content-length") statusCode = int(status.split(" ", 1)[0]) contentLengthRequired = ( environ["REQUEST_METHOD"] != "HEAD" and statusCode >= 200 and statusCode not in (204, 304) ) # _logger.info(environ["REQUEST_METHOD"], statusCode, contentLengthRequired) if contentLengthRequired and currentContentLength in (None, ""): # A typical case: a GET request on a virtual resource, for which # the provider doesn't know the length _logger.error( "Missing required Content-Length header in {}-response: closing connection".format( statusCode ) ) forceCloseConnection = True elif not type(currentContentLength) is str: _logger.error( "Invalid Content-Length header in response ({!r}): closing connection".format( headerDict.get("content-length") ) ) forceCloseConnection = True # HOTFIX for Vista and Windows 7 (GC issue 13, issue 23) # It seems that we must read *all* of the request body, otherwise # clients may miss the response. # For example Vista MiniRedir didn't understand a 401 response, # when trying an anonymous PUT of big files. As a consequence, it # doesn't retry with credentials and the file copy fails. # (XP is fine however). util.read_and_discard_input(environ) # Make sure the socket is not reused, unless we are 100% sure all # current input was consumed if util.get_content_length(environ) != 0 and not environ.get( "wsgidav.all_input_read" ): _logger.warn("Input stream not completely consumed: closing connection") forceCloseConnection = True if forceCloseConnection and headerDict.get("connection") != "close": _logger.warn("Adding 'Connection: close' header") response_headers.append(("Connection", "close")) # Log request if self.verbose >= 3: userInfo = environ.get("http_authenticator.username") if not userInfo: userInfo = "(anonymous)" extra = [] if "HTTP_DESTINATION" in environ: extra.append('dest="{}"'.format(environ.get("HTTP_DESTINATION"))) if environ.get("CONTENT_LENGTH", "") != "": extra.append("length={}".format(environ.get("CONTENT_LENGTH"))) if "HTTP_DEPTH" in environ: extra.append("depth={}".format(environ.get("HTTP_DEPTH"))) if "HTTP_RANGE" in environ: extra.append("range={}".format(environ.get("HTTP_RANGE"))) if "HTTP_OVERWRITE" in environ: extra.append("overwrite={}".format(environ.get("HTTP_OVERWRITE"))) if self.verbose >= 3 and "HTTP_EXPECT" in environ: extra.append('expect="{}"'.format(environ.get("HTTP_EXPECT"))) if self.verbose >= 4 and "HTTP_CONNECTION" in environ: extra.append( 'connection="{}"'.format(environ.get("HTTP_CONNECTION")) ) if self.verbose >= 4 and "HTTP_USER_AGENT" in environ: extra.append('agent="{}"'.format(environ.get("HTTP_USER_AGENT"))) if self.verbose >= 4 and "HTTP_TRANSFER_ENCODING" in environ: extra.append( "transfer-enc={}".format(environ.get("HTTP_TRANSFER_ENCODING")) ) if self.verbose >= 3: extra.append("elap={:.3f}sec".format(time.time() - start_time)) extra = ", ".join(extra) # This is the CherryPy format: # 127.0.0.1 - - [08/Jul/2009:17:25:23] "GET /loginPrompt?redirect=/renderActionList%3Frelation%3Dpersonal%26key%3D%26filter%3DprivateSchedule&reason=0 HTTP/1.1" 200 1944 "http://127.0.0.1:8002/command?id=CMD_Schedule" "Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.1) Gecko/20090624 Firefox/3.5" # noqa _logger.info( '{addr} - {user} - [{time}] "{method} {path}" {extra} -> {status}'.format( addr=environ.get("REMOTE_ADDR", ""), user=userInfo, time=util.get_log_time(), method=environ.get("REQUEST_METHOD"), path=safe_re_encode( environ.get("PATH_INFO", ""), sys.stdout.encoding ), extra=extra, status=status, # response_headers.get(""), # response Content-Length # referer ) ) return start_response(status, response_headers, exc_info) # Call first middleware app_iter = self.application(environ, _start_response_wrapper) try: for v in app_iter: yield v finally: if hasattr(app_iter, "close"): app_iter.close() return
def _get_context(self, environ, davres): """ @see: http://www.webdav.org/specs/rfc4918.html#rfc.section.9.4 """ assert davres.is_collection dirConfig = environ["wsgidav.config"].get("dir_browser", {}) isReadOnly = environ["wsgidav.provider"].is_readonly() context = { "htdocs": (self.config.get("mount_path") or "") + ASSET_SHARE, "rows": [], "version": __version__, "displaypath": compat.unquote(davres.get_href()), "url": davres.get_href(), # util.make_complete_url(environ), "parentUrl": util.get_uri_parent(davres.get_href()), "config": dirConfig, "is_readonly": isReadOnly, } trailer = dirConfig.get("response_trailer") if trailer is True: trailer = "${version} - ${time}" if trailer: trailer = trailer.replace( "${version}", "<a href='https://github.com/mar10/wsgidav/'>WsgiDAV/{}</a>".format( __version__ ), ) trailer = trailer.replace("${time}", util.get_rfc1123_time()) context["trailer"] = trailer rows = context["rows"] # Ask collection for member info list dirInfoList = davres.get_directory_info() if dirInfoList is None: # No pre-build info: traverse members dirInfoList = [] childList = davres.get_descendants(depth="1", addSelf=False) for res in childList: di = res.get_display_info() href = res.get_href() classes = [] if res.is_collection: classes.append("directory") if not isReadOnly and not res.is_collection: ext = os.path.splitext(href)[1].lstrip(".").lower() officeType = msOfficeExtToTypeMap.get(ext) if officeType: if dirConfig.get("ms_sharepoint_plugin"): classes.append("msoffice") elif dirConfig.get("ms_sharepoint_urls"): href = "ms-{}:ofe|u|{}".format(officeType, href) entry = { "href": href, "class": " ".join(classes), "displayName": res.get_display_name(), "lastModified": res.get_last_modified(), "is_collection": res.is_collection, "contentLength": res.get_content_length(), "displayType": di.get("type"), "displayTypeComment": di.get("typeComment"), } dirInfoList.append(entry) # ignore_patterns = dirConfig.get("ignore", []) if compat.is_basestring(ignore_patterns): ignore_patterns = ignore_patterns.split(",") for entry in dirInfoList: # Skip ignore patterns ignore = False for pat in ignore_patterns: if fnmatch(entry["displayName"], pat): _logger.debug("Ignore {}".format(entry["displayName"])) ignore = True break if ignore: continue # lastModified = entry.get("lastModified") if lastModified is None: entry["strModified"] = "" else: entry["strModified"] = util.get_rfc1123_time(lastModified) entry["strSize"] = "-" if not entry.get("is_collection"): contentLength = entry.get("contentLength") if contentLength is not None: entry["strSize"] = util.byte_number_string(contentLength) rows.append(entry) # sort sort = "name" if sort == "name": rows.sort( key=lambda v: "{}{}".format( not v["is_collection"], v["displayName"].lower() ) ) if "http_authenticator.username" in environ: context["username"] = ( environ.get("http_authenticator.username") or "anonymous" ) context["realm"] = environ.get("http_authenticator.realm") return context
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/%s</a>" % __version__) trailer = trailer.replace("${time}", util.getRfc1123Time()) else: trailer = ("<a href='https://github.com/mar10/wsgidav/'>WsgiDAV/%s</a> - %s" % (__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 %s'>" % __version__) html.append("<title>WsgiDAV - Index of %s </title>" % displaypath) html.append("<script type='text/javascript'>%s</script>" % PAGE_SCRIPT) html.append("<style type='text/css'>%s</style>" % 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 %s</h1>" % 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='%s?davmount'>Mount</a>" % util.makeCompleteUrl(environ)) if dirConfig.get("ms_mount"): links.append("<a title='Open as Web Folder (requires Microsoft Internet Explorer)' " "href='' FOLDER='%s'>Open as Web Folder</a>" % util.makeCompleteUrl(environ)) # html.append("<a href='' FOLDER='%ssetup.py'>Open setup.py as WebDAV</a>" % util.makeCompleteUrl(environ)) if links: html.append("<p>%s</p>" % " – ".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: # print "OT", officeType # print "OT", dirConfig if dirConfig.get("ms_sharepoint_plugin"): infoDict["class"] = "msoffice" elif dirConfig.get("ms_sharepoint_urls"): infoDict[ "href"] = "ms-%s:ofe|u|%s" % (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)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: '%s'.</p>" % (environ.get("http_authenticator.username"), environ.get("http_authenticator.realm"))) # else: # html.append("<p>Anonymous</p>") if trailer: html.append("<p class='trailer'>%s</p>" % 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]
def __call__(self, environ, start_response): # util.log("SCRIPT_NAME='{}', PATH_INFO='{}'".format( # environ.get("SCRIPT_NAME"), environ.get("PATH_INFO"))) path = environ["PATH_INFO"] # (#73) Failed on processing non-iso-8859-1 characters on Python 3 # # Note: we encode using UTF-8 here (falling back to ISO-8859-1)! # This seems to be wrong, since per PEP 3333 PATH_INFO is always ISO-8859-1 encoded # (see https://www.python.org/dev/peps/pep-3333/#unicode-issues). # But also seems to resolve errors when accessing resources with Chinese characters, for # example. # This is done by default for Python 3, but can be turned off in settings. if self.re_encode_path_info: path = environ["PATH_INFO"] = compat.wsgi_to_bytes(path).decode() # We optionally unquote PATH_INFO here, although this should already be # done by the server (#8). if self.unquote_path_info: path = compat.unquote(environ["PATH_INFO"]) # GC issue 22: Pylons sends root as u'/' if not compat.is_native(path): _logger.warning("Got non-native PATH_INFO: {!r}".format(path)) # path = path.encode("utf8") path = compat.to_native(path) # Always adding these values to environ: environ["wsgidav.config"] = self.config environ["wsgidav.provider"] = None environ["wsgidav.verbose"] = self.verbose # Find DAV provider that matches the share share, provider = self.resolve_provider(path) # share = None # lower_path = path.lower() # for r in self.sorted_share_list: # # @@: Case sensitivity should be an option of some sort here; # # os.path.normpath might give the preferred case for a filename. # if r == "/": # share = r # break # elif lower_path == r or lower_path.startswith(r + "/"): # share = r # break # Note: we call the next app, even if provider is None, because OPTIONS # must still be handled. # All other requests will result in '404 Not Found' # if share is not None: # share_data = self.provider_map.get(share) # environ["wsgidav.provider"] = share_data["provider"] environ["wsgidav.provider"] = provider # TODO: test with multi-level realms: 'aa/bb' # TODO: test security: url contains '..' # Transform SCRIPT_NAME and PATH_INFO # (Since path and share are unquoted, this also fixes quoted values.) if share == "/" or not share: environ["PATH_INFO"] = path else: environ["SCRIPT_NAME"] += share environ["PATH_INFO"] = path[len(share) :] # assert isinstance(path, str) assert compat.is_native(path) # See http://mail.python.org/pipermail/web-sig/2007-January/002475.html # for some clarification about SCRIPT_NAME/PATH_INFO format # SCRIPT_NAME starts with '/' or is empty assert environ["SCRIPT_NAME"] == "" or environ["SCRIPT_NAME"].startswith("/") # SCRIPT_NAME must not have a trailing '/' assert environ["SCRIPT_NAME"] in ("", "/") or not environ[ "SCRIPT_NAME" ].endswith("/") # PATH_INFO starts with '/' assert environ["PATH_INFO"] == "" or environ["PATH_INFO"].startswith("/") start_time = time.time() def _start_response_wrapper(status, response_headers, exc_info=None): # Postprocess response headers headerDict = {} for header, value in response_headers: if header.lower() in headerDict: _logger.error("Duplicate header in response: {}".format(header)) headerDict[header.lower()] = value # Check if we should close the connection after this request. # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4 forceCloseConnection = False currentContentLength = headerDict.get("content-length") statusCode = int(status.split(" ", 1)[0]) contentLengthRequired = ( environ["REQUEST_METHOD"] != "HEAD" and statusCode >= 200 and statusCode not in (204, 304) ) # _logger.info(environ["REQUEST_METHOD"], statusCode, contentLengthRequired) if contentLengthRequired and currentContentLength in (None, ""): # A typical case: a GET request on a virtual resource, for which # the provider doesn't know the length _logger.error( "Missing required Content-Length header in {}-response: closing connection".format( statusCode ) ) forceCloseConnection = True elif not type(currentContentLength) is str: _logger.error( "Invalid Content-Length header in response ({!r}): closing connection".format( headerDict.get("content-length") ) ) forceCloseConnection = True # HOTFIX for Vista and Windows 7 (GC issue 13, issue 23) # It seems that we must read *all* of the request body, otherwise # clients may miss the response. # For example Vista MiniRedir didn't understand a 401 response, # when trying an anonymous PUT of big files. As a consequence, it # doesn't retry with credentials and the file copy fails. # (XP is fine however). util.read_and_discard_input(environ) # Make sure the socket is not reused, unless we are 100% sure all # current input was consumed if util.get_content_length(environ) != 0 and not environ.get( "wsgidav.all_input_read" ): _logger.warning( "Input stream not completely consumed: closing connection." ) forceCloseConnection = True if forceCloseConnection and headerDict.get("connection") != "close": _logger.warning("Adding 'Connection: close' header.") response_headers.append(("Connection", "close")) # Log request if self.verbose >= 3: userInfo = environ.get("wsgidav.auth.user_name") if not userInfo: userInfo = "(anonymous)" extra = [] if "HTTP_DESTINATION" in environ: extra.append('dest="{}"'.format(environ.get("HTTP_DESTINATION"))) if environ.get("CONTENT_LENGTH", "") != "": extra.append("length={}".format(environ.get("CONTENT_LENGTH"))) if "HTTP_DEPTH" in environ: extra.append("depth={}".format(environ.get("HTTP_DEPTH"))) if "HTTP_RANGE" in environ: extra.append("range={}".format(environ.get("HTTP_RANGE"))) if "HTTP_OVERWRITE" in environ: extra.append("overwrite={}".format(environ.get("HTTP_OVERWRITE"))) if self.verbose >= 3 and "HTTP_EXPECT" in environ: extra.append('expect="{}"'.format(environ.get("HTTP_EXPECT"))) if self.verbose >= 4 and "HTTP_CONNECTION" in environ: extra.append( 'connection="{}"'.format(environ.get("HTTP_CONNECTION")) ) if self.verbose >= 4 and "HTTP_USER_AGENT" in environ: extra.append('agent="{}"'.format(environ.get("HTTP_USER_AGENT"))) if self.verbose >= 4 and "HTTP_TRANSFER_ENCODING" in environ: extra.append( "transfer-enc={}".format(environ.get("HTTP_TRANSFER_ENCODING")) ) if self.verbose >= 3: extra.append("elap={:.3f}sec".format(time.time() - start_time)) extra = ", ".join(extra) # This is the CherryPy format: # 127.0.0.1 - - [08/Jul/2009:17:25:23] "GET /loginPrompt?redirect=/renderActionList%3Frelation%3Dpersonal%26key%3D%26filter%3DprivateSchedule&reason=0 HTTP/1.1" 200 1944 "http://127.0.0.1:8002/command?id=CMD_Schedule" "Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.1) Gecko/20090624 Firefox/3.5" # noqa _logger.info( '{addr} - {user} - [{time}] "{method} {path}" {extra} -> {status}'.format( addr=environ.get("REMOTE_ADDR", ""), user=userInfo, time=util.get_log_time(), method=environ.get("REQUEST_METHOD"), path=safe_re_encode( environ.get("PATH_INFO", ""), sys.stdout.encoding if sys.stdout.encoding else "utf-8", ), extra=extra, status=status, # response_headers.get(""), # response Content-Length # referer ) ) return start_response(status, response_headers, exc_info) # Call first middleware app_iter = self.application(environ, _start_response_wrapper) try: for v in app_iter: yield v finally: if hasattr(app_iter, "close"): app_iter.close() return
def __call__(self, environ, start_response): # util.log("SCRIPT_NAME='%s', PATH_INFO='%s'" % ( # environ.get("SCRIPT_NAME"), environ.get("PATH_INFO"))) # We optionall unquote PATH_INFO here, although this should already be # done by the server (#8). path = environ["PATH_INFO"] if self.config.get("unquote_path_info", False): path = compat.unquote(environ["PATH_INFO"]) # GC issue 22: Pylons sends root as u'/' # if isinstance(path, unicode): if not compat.is_native(path): util.log("Got non-native PATH_INFO: %r" % path) # path = path.encode("utf8") path = compat.to_native(path) # Always adding these values to environ: environ["wsgidav.config"] = self.config environ["wsgidav.provider"] = None environ["wsgidav.verbose"] = self._verbose # Find DAV provider that matches the share # sorting share list by reverse length # shareList = self.providerMap.keys() # shareList.sort(key=len, reverse=True) shareList = sorted(self.providerMap.keys(), key=len, reverse=True) share = None for r in shareList: # @@: Case sensitivity should be an option of some sort here; # os.path.normpath might give the preferred case for a filename. if r == "/": share = r break elif path.upper() == r.upper() or path.upper().startswith( r.upper() + "/"): share = r break # Note: we call the next app, even if provider is None, because OPTIONS # must still be handled. # All other requests will result in '404 Not Found' if share is not None: share_data = self.providerMap.get(share) environ["wsgidav.provider"] = share_data['provider'] # TODO: test with multi-level realms: 'aa/bb' # TODO: test security: url contains '..' # Transform SCRIPT_NAME and PATH_INFO # (Since path and share are unquoted, this also fixes quoted values.) if share == "/" or not share: environ["PATH_INFO"] = path else: environ["SCRIPT_NAME"] += share environ["PATH_INFO"] = path[len(share):] # util.log("--> SCRIPT_NAME='%s', PATH_INFO='%s'" % (environ.get("SCRIPT_NAME"), environ.get("PATH_INFO"))) # assert isinstance(path, str) assert compat.is_native(path) # See http://mail.python.org/pipermail/web-sig/2007-January/002475.html # for some clarification about SCRIPT_NAME/PATH_INFO format # SCRIPT_NAME starts with '/' or is empty assert environ["SCRIPT_NAME"] == "" or environ[ "SCRIPT_NAME"].startswith("/") # SCRIPT_NAME must not have a trailing '/' assert environ["SCRIPT_NAME"] in ( "", "/") or not environ["SCRIPT_NAME"].endswith("/") # PATH_INFO starts with '/' assert environ["PATH_INFO"] == "" or environ["PATH_INFO"].startswith( "/") start_time = time.time() def _start_response_wrapper(status, response_headers, exc_info=None): # Postprocess response headers headerDict = {} for header, value in response_headers: if header.lower() in headerDict: util.warn("Duplicate header in response: %s" % header) headerDict[header.lower()] = value # Check if we should close the connection after this request. # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4 forceCloseConnection = False currentContentLength = headerDict.get("content-length") statusCode = int(status.split(" ", 1)[0]) contentLengthRequired = (environ["REQUEST_METHOD"] != "HEAD" and statusCode >= 200 and not statusCode in (204, 304)) # print(environ["REQUEST_METHOD"], statusCode, contentLengthRequired) if contentLengthRequired and currentContentLength in (None, ""): # A typical case: a GET request on a virtual resource, for which # the provider doesn't know the length util.warn( "Missing required Content-Length header in %s-response: closing connection" % statusCode) forceCloseConnection = True elif not type(currentContentLength) is str: util.warn( "Invalid Content-Length header in response (%r): closing connection" % headerDict.get("content-length")) forceCloseConnection = True # HOTFIX for Vista and Windows 7 (GC issue 13, issue 23) # It seems that we must read *all* of the request body, otherwise # clients may miss the response. # For example Vista MiniRedir didn't understand a 401 response, # when trying an anonymous PUT of big files. As a consequence, it # doesn't retry with credentials and the file copy fails. # (XP is fine however). util.readAndDiscardInput(environ) # Make sure the socket is not reused, unless we are 100% sure all # current input was consumed if (util.getContentLength(environ) != 0 and not environ.get("wsgidav.all_input_read")): util.warn( "Input stream not completely consumed: closing connection") forceCloseConnection = True if forceCloseConnection and headerDict.get( "connection") != "close": util.warn("Adding 'Connection: close' header") response_headers.append(("Connection", "close")) # Log request if self._verbose >= 1: userInfo = environ.get("http_authenticator.username") if not userInfo: userInfo = "(anonymous)" threadInfo = "" if self._verbose >= 1: threadInfo = "<%s> " % threading.currentThread().ident extra = [] if "HTTP_DESTINATION" in environ: extra.append('dest="%s"' % environ.get("HTTP_DESTINATION")) if environ.get("CONTENT_LENGTH", "") != "": extra.append("length=%s" % environ.get("CONTENT_LENGTH")) if "HTTP_DEPTH" in environ: extra.append("depth=%s" % environ.get("HTTP_DEPTH")) if "HTTP_RANGE" in environ: extra.append("range=%s" % environ.get("HTTP_RANGE")) if "HTTP_OVERWRITE" in environ: extra.append("overwrite=%s" % environ.get("HTTP_OVERWRITE")) if self._verbose >= 1 and "HTTP_EXPECT" in environ: extra.append('expect="%s"' % environ.get("HTTP_EXPECT")) if self._verbose >= 2 and "HTTP_CONNECTION" in environ: extra.append('connection="%s"' % environ.get("HTTP_CONNECTION")) if self._verbose >= 2 and "HTTP_USER_AGENT" in environ: extra.append('agent="%s"' % environ.get("HTTP_USER_AGENT")) if self._verbose >= 2 and "HTTP_TRANSFER_ENCODING" in environ: extra.append('transfer-enc=%s' % environ.get("HTTP_TRANSFER_ENCODING")) if self._verbose >= 1: extra.append('elap=%.3fsec' % (time.time() - start_time)) extra = ", ".join(extra) # This is the CherryPy format: # 127.0.0.1 - - [08/Jul/2009:17:25:23] "GET /loginPrompt?redirect=/renderActionList%3Frelation%3Dpersonal%26key%3D%26filter%3DprivateSchedule&reason=0 HTTP/1.1" 200 1944 "http://127.0.0.1:8002/command?id=CMD_Schedule" "Mozilla/5.0 (Windows; U; Windows NT 6.0; de; rv:1.9.1) Gecko/20090624 Firefox/3.5" # print >>sys.stderr, '%s - %s - [%s] "%s" %s -> %s' % ( print( '%s - %s - [%s] "%s" %s -> %s' % ( threadInfo + environ.get("REMOTE_ADDR", ""), userInfo, util.getLogTime(), environ.get("REQUEST_METHOD") + " " + environ.get("PATH_INFO", ""), extra, status, # response_headers.get(""), # response Content-Length # referer ), file=sys.stdout) return start_response(status, response_headers, exc_info) # Call next middleware app_iter = self._application(environ, _start_response_wrapper) for v in app_iter: yield v if hasattr(app_iter, "close"): app_iter.close() return
def _get_context(self, environ, dav_res): """ @see: http://www.webdav.org/specs/rfc4918.html#rfc.section.9.4 """ assert dav_res.is_collection is_readonly = environ["wsgidav.provider"].is_readonly() context = { "htdocs": (self.config.get("mount_path") or "") + ASSET_SHARE, "rows": [], "version": __version__, "display_path": compat.unquote(dav_res.get_href()), "url": dav_res.get_href(), # util.make_complete_url(environ), "parent_url": util.get_uri_parent(dav_res.get_href()), "config": self.dir_config, "is_readonly": is_readonly, "access": "read-only" if is_readonly else "read-write", "is_authenticated": False, } trailer = self.dir_config.get("response_trailer") if trailer is True: #trailer = "${version} - ${time}" trailer = "Shihira Fung - ${time}" if trailer: trailer = trailer.replace( "${version}", "<a href='https://github.com/mar10/wsgidav/'>WsgiDAV/{}</a>".format( __version__ ), ) trailer = trailer.replace("${time}", util.get_rfc1123_time()) context["trailer"] = trailer rows = context["rows"] # Ask collection for member info list dirInfoList = dav_res.get_directory_info() if dirInfoList is None: # No pre-build info: traverse members dirInfoList = [] childList = dav_res.get_descendants(depth="1", add_self=False) for res in childList: di = res.get_display_info() href = res.get_href() ofe_prefix = None tr_classes = [] a_classes = [] if res.is_collection: tr_classes.append("directory") if not is_readonly and not res.is_collection: ext = os.path.splitext(href)[1].lstrip(".").lower() officeType = msOfficeExtToTypeMap.get(ext) if officeType: if self.dir_config.get("ms_sharepoint_support"): ofe_prefix = "ms-{}:ofe|u|".format(officeType) a_classes.append("msoffice") # elif self.dir_config.get("ms_sharepoint_plugin"): # a_classes.append("msoffice") # elif self.dir_config.get("ms_sharepoint_urls"): # href = "ms-{}:ofe|u|{}".format(officeType, href) entry = { "href": href, "ofe_prefix": ofe_prefix, "a_class": " ".join(a_classes), "tr_class": " ".join(tr_classes), "display_name": res.get_display_name(), "last_modified": res.get_last_modified(), "is_collection": res.is_collection, "content_length": res.get_content_length(), "display_type": di.get("type"), "display_type_comment": di.get("typeComment"), } dirInfoList.append(entry) # ignore_patterns = self.dir_config.get("ignore", []) if compat.is_basestring(ignore_patterns): ignore_patterns = ignore_patterns.split(",") ignored_list = [] for entry in dirInfoList: # Skip ignore patterns ignore = False for pat in ignore_patterns: if fnmatch(entry["display_name"], pat): ignored_list.append(entry["display_name"]) # _logger.debug("Ignore {}".format(entry["display_name"])) ignore = True break if ignore: continue # last_modified = entry.get("last_modified") if last_modified is None: entry["str_modified"] = "" else: import time entry["str_modified"] = time.strftime("%b %d %Y, %H:%M:%S", time.localtime(last_modified)) entry["str_size"] = "-" if not entry.get("is_collection"): content_length = entry.get("content_length") if content_length is not None: for unit in ['Bytes', 'KiB', 'MiB', 'GiB']: if content_length < 1024: content_length = "%.2f %s" % (content_length, unit) break content_length /= 1024.0 entry["str_size"] = content_length rows.append(entry) if ignored_list: _logger.debug( "Dir browser ignored {} entries: {}".format( len(ignored_list), ignored_list ) ) # sort sort = "name" if sort == "name": rows.sort( key=lambda v: "{}{}".format( not v["is_collection"], v["display_name"].lower() ) ) if "wsgidav.auth.user_name" in environ: context.update( { "is_authenticated": True, "user_name": (environ.get("wsgidav.auth.user_name") or "anonymous"), "realm": environ.get("wsgidav.auth.realm"), "user_roles": ", ".join(environ.get("wsgidav.auth.roles") or []), "user_permissions": ", ".join( environ.get("wsgidav.auth.permissions") or [] ), } ) return context
def _get_context(self, environ, dav_res): """ @see: http://www.webdav.org/specs/rfc4918.html#rfc.section.9.4 """ assert dav_res.is_collection is_readonly = environ["wsgidav.provider"].is_readonly() context = { "htdocs": (self.config.get("mount_path") or "") + ASSET_SHARE, "rows": [], "version": __version__, "display_path": compat.unquote(dav_res.get_href()), "url": dav_res.get_href(), # util.make_complete_url(environ), "parent_url": util.get_uri_parent(dav_res.get_href()), "config": self.dir_config, "is_readonly": is_readonly, "access": "read-only" if is_readonly else "read-write", "is_authenticated": False, } trailer = self.dir_config.get("response_trailer") if trailer is True: trailer = "${version} - ${time}" if trailer: trailer = trailer.replace( "${version}", "<a href='https://github.com/mar10/wsgidav/'>WsgiDAV/{}</a>".format( __version__ ), ) trailer = trailer.replace("${time}", util.get_rfc1123_time()) context["trailer"] = trailer rows = context["rows"] # Ask collection for member info list dirInfoList = dav_res.get_directory_info() if dirInfoList is None: # No pre-build info: traverse members dirInfoList = [] childList = dav_res.get_descendants(depth="1", add_self=False) for res in childList: di = res.get_display_info() href = res.get_href() ofe_prefix = None tr_classes = [] a_classes = [] if res.is_collection: tr_classes.append("directory") if not is_readonly and not res.is_collection: ext = os.path.splitext(href)[1].lstrip(".").lower() officeType = msOfficeExtToTypeMap.get(ext) if officeType: if self.dir_config.get("ms_sharepoint_support"): ofe_prefix = "ms-{}:ofe|u|".format(officeType) a_classes.append("msoffice") # elif self.dir_config.get("ms_sharepoint_plugin"): # a_classes.append("msoffice") # elif self.dir_config.get("ms_sharepoint_urls"): # href = "ms-{}:ofe|u|{}".format(officeType, href) entry = { "href": href, "ofe_prefix": ofe_prefix, "a_class": " ".join(a_classes), "tr_class": " ".join(tr_classes), "display_name": res.get_display_name(), "last_modified": res.get_last_modified(), "is_collection": res.is_collection, "content_length": res.get_content_length(), "display_type": di.get("type"), "display_type_comment": di.get("typeComment"), } dirInfoList.append(entry) # ignore_patterns = self.dir_config.get("ignore", []) if compat.is_basestring(ignore_patterns): ignore_patterns = ignore_patterns.split(",") ignored_list = [] for entry in dirInfoList: # Skip ignore patterns ignore = False for pat in ignore_patterns: if fnmatch(entry["display_name"], pat): ignored_list.append(entry["display_name"]) # _logger.debug("Ignore {}".format(entry["display_name"])) ignore = True break if ignore: continue # last_modified = entry.get("last_modified") if last_modified is None: entry["str_modified"] = "" else: entry["str_modified"] = util.get_rfc1123_time(last_modified) entry["str_size"] = "-" if not entry.get("is_collection"): content_length = entry.get("content_length") if content_length is not None: entry["str_size"] = util.byte_number_string(content_length) rows.append(entry) if ignored_list: _logger.debug( "Dir browser ignored {} entries: {}".format( len(ignored_list), ignored_list ) ) # sort sort = "name" if sort == "name": rows.sort( key=lambda v: "{}{}".format( not v["is_collection"], v["display_name"].lower() ) ) if "wsgidav.auth.user_name" in environ: context.update( { "is_authenticated": True, "user_name": (environ.get("wsgidav.auth.user_name") or "anonymous"), "realm": environ.get("wsgidav.auth.realm"), "user_roles": ", ".join(environ.get("wsgidav.auth.roles") or []), "user_permissions": ", ".join( environ.get("wsgidav.auth.permissions") or [] ), } ) return context