def add_provider(self, share, provider, readonly=False): """Add a provider to the providerMap.""" # Make sure share starts with, or is '/' share = "/" + share.strip("/") assert share not in self.providerMap # We allow a simple string as 'provider'. In this case we interpret # it as a file system root folder that is published. if compat.is_basestring(provider): provider = FilesystemProvider(provider, readonly) elif type(provider) in (list, tuple): provider = FilesystemProvider(provider[0], provider[1]) if not isinstance(provider, DAVProvider): raise ValueError("Invalid provider {}".format(provider)) provider.set_share_path(share) if self.mount_path: provider.set_mount_path(self.mount_path) # TODO: someday we may want to configure different lock/prop # managers per provider provider.set_lock_manager(self.locksManager) provider.set_prop_manager(self.propsManager) self.providerMap[share] = {"provider": provider, "allow_anonymous": False} # Store the list of share paths, ordered by length, so route lookups # will return the most specific match self.sortedShareList = [s.lower() for s in self.providerMap.keys()] self.sortedShareList = sorted(self.sortedShareList, key=len, reverse=True) return provider
def __init__(self, path, environ, display_info, member_name_list): super(VirtualCollection, self).__init__(path, environ) if compat.is_basestring(display_info): display_info = {"type": display_info} assert type(display_info) is dict assert type(member_name_list) is list self.display_info = display_info self.member_name_list = member_name_list
def __init__(self, path, environ, displayInfo, memberNameList): DAVCollection.__init__(self, path, environ) if compat.is_basestring(displayInfo): displayInfo = {"type": displayInfo} assert type(displayInfo) is dict assert type(memberNameList) is list self.displayInfo = displayInfo self.memberNameList = memberNameList
def __init__(self, path, environ, display_info, member_name_list): super(VirtualCollection, self).__init__(path, environ) if compat.is_basestring(display_info): display_info = {"type": display_info} assert type(display_info) is dict assert type(member_name_list) is list self.display_info = display_info self.member_name_list = member_name_list
def add_provider(self, share, provider, readonly=False): """Add a provider to the provider_map routing table.""" # Make sure share starts with, or is '/' share = "/" + share.strip("/") assert share not in self.provider_map if compat.is_basestring(provider): # Syntax: # <mount_path>: <folder_path> # We allow a simple string as 'provider'. In this case we interpret # it as a file system root folder that is published. provider = FilesystemProvider(provider, readonly) elif type(provider) in (dict, ): if "provider" in provider: # Syntax: # <mount_path>: {"provider": <class_path>, "args": <pos_args>, "kwargs": <named_args} prov_class = dynamic_import_class(provider["provider"]) provider = prov_class(*provider.get("args", []), **provider.get("kwargs", {})) else: # Syntax: # <mount_path>: {"root": <path>, "redaonly": <bool>} provider = FilesystemProvider( provider["root"], bool(provider.get("readonly", False))) elif type(provider) in (list, tuple): raise ValueError( "Provider {}: tuple/list syntax is no longer supported".format( provider)) # provider = FilesystemProvider(provider[0], provider[1]) if not isinstance(provider, DAVProvider): raise ValueError("Invalid provider {}".format(provider)) provider.set_share_path(share) if self.mount_path: provider.set_mount_path(self.mount_path) # TODO: someday we may want to configure different lock/prop # managers per provider provider.set_lock_manager(self.lock_manager) provider.set_prop_manager(self.prop_manager) self.provider_map[share] = provider # self.provider_map[share] = {"provider": provider, "allow_anonymous": False} # Store the list of share paths, ordered by length, so route lookups # will return the most specific match self.sorted_share_list = [s.lower() for s in self.provider_map.keys()] self.sorted_share_list = sorted(self.sorted_share_list, key=len, reverse=True) return provider
def add_provider(self, share, provider, readonly=False): """Add a provider to the provider_map routing table.""" # Make sure share starts with, or is '/' share = "/" + share.strip("/") assert share not in self.provider_map if compat.is_basestring(provider): # Syntax: # <mount_path>: <folder_path> # We allow a simple string as 'provider'. In this case we interpret # it as a file system root folder that is published. provider = FilesystemProvider(provider, readonly) elif type(provider) in (dict,): if "provider" in provider: # Syntax: # <mount_path>: {"provider": <class_path>, "args": <pos_args>, "kwargs": <named_args} prov_class = dynamic_import_class(provider["provider"]) provider = prov_class( *provider.get("args", []), **provider.get("kwargs", {}) ) else: # Syntax: # <mount_path>: {"root": <path>, "redaonly": <bool>} provider = FilesystemProvider( provider["root"], bool(provider.get("readonly", False)) ) elif type(provider) in (list, tuple): raise ValueError( "Provider {}: tuple/list syntax is no longer supported".format(provider) ) # provider = FilesystemProvider(provider[0], provider[1]) if not isinstance(provider, DAVProvider): raise ValueError("Invalid provider {}".format(provider)) provider.set_share_path(share) if self.mount_path: provider.set_mount_path(self.mount_path) # TODO: someday we may want to configure different lock/prop # managers per provider provider.set_lock_manager(self.lock_manager) provider.set_prop_manager(self.prop_manager) self.provider_map[share] = provider # self.provider_map[share] = {"provider": provider, "allow_anonymous": False} # Store the list of share paths, ordered by length, so route lookups # will return the most specific match self.sorted_share_list = [s.lower() for s in self.provider_map.keys()] self.sorted_share_list = sorted(self.sorted_share_list, key=len, reverse=True) return provider
def write(self, chunk): """Put a chunk of bytes (or an iterable) to the queue. May block if max_size number of chunks is reached. """ if self.is_closed: raise ValueError("Cannot write to closed object") # print("FileLikeQueue.write(), n={}".format(len(chunk))) # Add chunk to queue (blocks if queue is full) if compat.is_basestring(chunk): self.queue.put(chunk) else: # if not a string, assume an iterable for o in chunk: self.queue.put(o)
def write(self, chunk): """Put a chunk of bytes (or an iterable) to the queue. May block if max_size number of chunks is reached. """ if self.is_closed: raise ValueError("Cannot write to closed object") # print("FileLikeQueue.write(), n={}".format(len(chunk))) # Add chunk to queue (blocks if queue is full) if compat.is_basestring(chunk): self.queue.put(chunk) else: # if not a string, assume an iterable for o in chunk: self.queue.put(o)
def make_domain_controller(config): dc = config.get("http_authenticator", {}).get("domain_controller") if dc is True or not dc: # True or null: dc = SimpleDomainController if compat.is_basestring(dc): # If a plain string is passed, try to import it as class dc = dynamic_import_class(dc) if inspect.isclass(dc): # If a class is passed, instantiate that dc = dc(config) # print("make_domain_controller", dc) return dc
def make_domain_controller(wsgidav_app, config): dc = config.get("http_authenticator", {}).get("domain_controller") org_dc = dc if dc is True or not dc: # True or null: dc = SimpleDomainController elif compat.is_basestring(dc): # If a plain string is passed, try to import it as class dc = dynamic_import_class(dc) if inspect.isclass(dc): # If a class is passed, instantiate that dc = dc(wsgidav_app, config) else: raise RuntimeError( "Could not resolve domain controller class (got {})".format(org_dc) ) # print("make_domain_controller", dc) return dc
def make_domain_controller(wsgidav_app, config): dc = config.get("http_authenticator", {}).get("domain_controller") org_dc = dc if dc is True or not dc: # True or null: dc = SimpleDomainController elif compat.is_basestring(dc): # If a plain string is passed, try to import it as class dc = dynamic_import_class(dc) if inspect.isclass(dc): # If a class is passed, instantiate that dc = dc(wsgidav_app, config) else: raise RuntimeError( "Could not resolve domain controller class (got {})".format(org_dc) ) # print("make_domain_controller", dc) return dc
def _expand(v): """Replace some string templates with defined values.""" if expand and compat.is_basestring(v) and v.lower() in expand: return expand[v] return v
def __init__(self, config): self.config = config util.initLogging(config["verbose"], config.get("enable_loggers", [])) util.log("Default encoding: %s (file system: %s)" % (sys.getdefaultencoding(), sys.getfilesystemencoding())) # Evaluate configuration and set defaults _checkConfig(config) provider_mapping = self.config["provider_mapping"] # response_trailer = config.get("response_trailer", "") self._verbose = config.get("verbose", 2) lockStorage = config.get("locksmanager") if lockStorage is True: lockStorage = LockStorageDict() if not lockStorage: locksManager = None else: locksManager = LockManager(lockStorage) propsManager = config.get("propsmanager") if not propsManager: # Normalize False, 0 to None propsManager = None elif propsManager is True: propsManager = PropertyManager() mount_path = config.get("mount_path") # Instantiate DAV resource provider objects for every share self.providerMap = {} for (share, provider) in provider_mapping.items(): # Make sure share starts with, or is, '/' share = "/" + share.strip("/") # We allow a simple string as 'provider'. In this case we interpret # it as a file system root folder that is published. if compat.is_basestring(provider): provider = FilesystemProvider(provider) assert isinstance(provider, DAVProvider) provider.setSharePath(share) if mount_path: provider.setMountPath(mount_path) # TODO: someday we may want to configure different lock/prop # managers per provider provider.setLockManager(locksManager) provider.setPropManager(propsManager) self.providerMap[share] = { "provider": provider, "allow_anonymous": False} # Define WSGI application stack application = RequestResolver() domain_controller = None dir_browser = config.get("dir_browser", {}) middleware_stack = config.get("middleware_stack", []) # Replace WsgiDavDirBrowser to custom class for backward compatibility only # In normal way you should insert it into middleware_stack if dir_browser.get("enable", True) and "app_class" in dir_browser.keys(): config["middleware_stack"] = [m if m != WsgiDavDirBrowser else dir_browser[ 'app_class'] for m in middleware_stack] for mw in middleware_stack: if mw.isSuitable(config): if self._verbose >= 2: print("Middleware %s is suitable" % mw) application = mw(application, config) if issubclass(mw, HTTPAuthenticator): domain_controller = application.getDomainController() # check anonymous access for share, data in self.providerMap.items(): if application.allowAnonymousAccess(share): data['allow_anonymous'] = True else: if self._verbose >= 2: print("Middleware %s is not suitable" % mw) # Print info if self._verbose >= 2: print("Using lock manager: %r" % locksManager) print("Using property manager: %r" % propsManager) print("Using domain controller: %s" % domain_controller) print("Registered DAV providers:") for share, data in self.providerMap.items(): hint = " (anonymous)" if data['allow_anonymous'] else "" print(" Share '%s': %s%s" % (share, provider, hint)) if self._verbose >= 1: for share, data in self.providerMap.items(): if data['allow_anonymous']: # TODO: we should only warn here, if --no-auth is not given print("WARNING: share '%s' will allow anonymous access." % share) self._application = application
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 __init__(self, config): self.config = copy.deepcopy(DEFAULT_CONFIG) util.deep_update(self.config, config) config = self.config # Evaluate configuration and set defaults _check_config(config) self.verbose = config.get("verbose", 3) hotfixes = config.get("hotfixes", {}) self.re_encode_path_info = hotfixes.get("re_encode_path_info", None) if self.re_encode_path_info is None: self.re_encode_path_info = compat.PY3 self.unquote_path_info = hotfixes.get("unquote_path_info", False) lock_storage = config.get("lock_manager") if lock_storage is True: lock_storage = LockStorageDict() if not lock_storage: self.lock_manager = None else: self.lock_manager = LockManager(lock_storage) self.prop_manager = config.get("property_manager") if not self.prop_manager: # Normalize False, 0 to None self.prop_manager = None elif self.prop_manager is True: self.prop_manager = PropertyManager() self.mount_path = config.get("mount_path") auth_conf = config.get("http_authenticator", {}) # Instantiate DAV resource provider objects for every share. # provider_mapping may contain the args that are passed to a # `FilesystemProvider` instance: # <mount_path>: <folder_path> # or # <mount_path>: { "root": <folder_path>, "readonly": True } # or contain a complete new instance: # <mount_path>: <DAVProvider Instance> provider_mapping = self.config["provider_mapping"] self.provider_map = {} self.sorted_share_list = None for share, provider in provider_mapping.items(): self.add_provider(share, provider) self.http_authenticator = None domain_controller = None # Define WSGI application stack middleware_stack = config.get("middleware_stack", []) mw_list = [] # This is the 'outer' application, i.e. the WSGI application object that # is eventually called by the server. self.application = self # The `middleware_stack` is configured such that the first app in the # list should be called first. Since every app wraps its predecessor, we # iterate in reverse order: for mw in reversed(middleware_stack): # The middleware stack configuration may contain plain strings, dicts, # classes, or objects app = None if compat.is_basestring(mw): # If a plain string is passed, try to import it, assuming # `BaseMiddleware` signature app_class = dynamic_import_class(mw) app = app_class(self, self.application, config) elif type(mw) is dict: # If a dict with one entry is passed, use the key as module/class name # and the value as constructor arguments (positional or kwargs). assert len(mw) == 1 name, args = list(mw.items())[0] expand = {"${application}": self.application} app = dynamic_instantiate_middleware(name, args, expand) elif inspect.isclass(mw): # If a class is passed, assume BaseMiddleware (or compatible) assert issubclass( mw, BaseMiddleware ) # TODO: remove this assert with 3.0 app = mw(self, self.application, config) else: # Otherwise assume an initialized middleware instance app = mw # Remember if isinstance(app, HTTPAuthenticator): self.http_authenticator = app domain_controller = app.get_domain_controller() # Add middleware to the stack if app: if callable(getattr(app, "is_disabled", None)) and app.is_disabled(): _logger.warning( "App {}.is_disabled() returned True: skipping.".format(app) ) else: mw_list.append(app) self.application = app else: _logger.error("Could not add middleware {}.".format(mw)) domain_controller # Print info _logger.info( "WsgiDAV/{} Python/{} {}".format( __version__, util.PYTHON_VERSION, platform.platform(aliased=True) ) ) if self.verbose >= 4: _logger.info( "Default encoding: {!r} (file system: {!r})".format( sys.getdefaultencoding(), sys.getfilesystemencoding() ) ) if self.verbose >= 3: _logger.info("Lock manager: {}".format(self.lock_manager)) _logger.info("Property manager: {}".format(self.prop_manager)) _logger.info("Domain controller: {}".format(domain_controller)) if self.verbose >= 4: # We traversed the stack in reverse order. Now revert again, so # we see the order that was configured: _logger.info("Middleware stack:") for mw in reversed(mw_list): _logger.info(" - {}".format(mw)) if self.verbose >= 3: _logger.info("Registered DAV providers by route:") for share in self.sorted_share_list: provider = self.provider_map[share] hint = ( " (anonymous)" if domain_controller.is_share_anonymous(share) else "" ) _logger.info(" - '{}': {}{}".format(share, provider, hint)) if auth_conf.get("accept_basic") and not config.get("ssl_certificate"): _logger.warning( "Basic authentication is enabled: It is highly recommended to enable SSL." ) if domain_controller: for share, provider in self.provider_map.items(): if domain_controller.is_share_anonymous(share): _logger.warning( "Share '{}' will allow anonymous {} access.".format( share, "read" if provider.is_readonly() else "write" ) ) return
def __init__(self, config): self.config = copy.deepcopy(DEFAULT_CONFIG) util.deep_update(self.config, config) config = self.config # Evaluate configuration and set defaults _check_config(config) self.verbose = config.get("verbose", 3) hotfixes = config.get("hotfixes", {}) self.re_encode_path_info = hotfixes.get("re_encode_path_info", None) if self.re_encode_path_info is None: self.re_encode_path_info = compat.PY3 self.unquote_path_info = hotfixes.get("unquote_path_info", False) lock_storage = config.get("lock_manager") if lock_storage is True: lock_storage = LockStorageDict() if not lock_storage: self.lock_manager = None else: self.lock_manager = LockManager(lock_storage) self.prop_manager = config.get("property_manager") if not self.prop_manager: # Normalize False, 0 to None self.prop_manager = None elif self.prop_manager is True: self.prop_manager = PropertyManager() self.mount_path = config.get("mount_path") auth_conf = config.get("http_authenticator", {}) # Instantiate DAV resource provider objects for every share. # provider_mapping may contain the args that are passed to a # `FilesystemProvider` instance: # <mount_path>: <folder_path> # or # <mount_path>: { "root": <folder_path>, "readonly": True } # or contain a complete new instance: # <mount_path>: <DAVProvider Instance> provider_mapping = self.config["provider_mapping"] self.provider_map = {} self.sorted_share_list = None for share, provider in provider_mapping.items(): self.add_provider(share, provider) self.http_authenticator = None domain_controller = None # Define WSGI application stack middleware_stack = config.get("middleware_stack", []) mw_list = [] # This is the 'outer' application, i.e. the WSGI application object that # is eventually called by the server. self.application = self # The `middleware_stack` is configured such that the first app in the # list should be called first. Since every app wraps its predecessor, we # iterate in reverse order: for mw in reversed(middleware_stack): # The middleware stack configuration may contain plain strings, dicts, # classes, or objects app = None if compat.is_basestring(mw): # If a plain string is passed, try to import it, assuming # `BaseMiddleware` signature app_class = dynamic_import_class(mw) app = app_class(self, self.application, config) elif type(mw) is dict: # If a dict with one entry is passed, use the key as module/class name # and the value as constructor arguments (positional or kwargs). assert len(mw) == 1 name, args = list(mw.items())[0] expand = {"${application}": self.application} app = dynamic_instantiate_middleware(name, args, expand) elif inspect.isclass(mw): # If a class is passed, assume BaseMiddleware (or compatible) assert issubclass( mw, BaseMiddleware) # TODO: remove this assert with 3.0 app = mw(self, self.application, config) else: # Otherwise assume an initialized middleware instance app = mw # Remember if isinstance(app, HTTPAuthenticator): self.http_authenticator = app domain_controller = app.get_domain_controller() # Add middleware to the stack if app: if callable(getattr(app, "is_disabled", None)) and app.is_disabled(): _logger.warning( "App {}.is_disabled() returned True: skipping.".format( app)) else: mw_list.append(app) self.application = app else: _logger.error("Could not add middleware {}.".format(mw)) domain_controller # Print info _logger.info("WsgiDAV/{} Python/{} {}".format( __version__, util.PYTHON_VERSION, platform.platform(aliased=True))) if self.verbose >= 4: _logger.info("Default encoding: {!r} (file system: {!r})".format( sys.getdefaultencoding(), sys.getfilesystemencoding())) if self.verbose >= 3: _logger.info("Lock manager: {}".format(self.lock_manager)) _logger.info("Property manager: {}".format(self.prop_manager)) _logger.info("Domain controller: {}".format(domain_controller)) if self.verbose >= 4: # We traversed the stack in reverse order. Now revert again, so # we see the order that was configured: _logger.info("Middleware stack:") for mw in reversed(mw_list): _logger.info(" - {}".format(mw)) if self.verbose >= 3: _logger.info("Registered DAV providers by route:") for share in self.sorted_share_list: provider = self.provider_map[share] hint = (" (anonymous)" if domain_controller.is_share_anonymous(share) else "") _logger.info(" - '{}': {}{}".format(share, provider, hint)) if auth_conf.get("accept_basic") and not config.get("ssl_certificate"): _logger.warning( "Basic authentication is enabled: It is highly recommended to enable SSL." ) if domain_controller: for share, provider in self.provider_map.items(): if domain_controller.is_share_anonymous(share): _logger.warning( "Share '{}' will allow anonymous {} access.".format( share, "read" if provider.is_readonly() else "write")) return
def __call__(self, environ, start_response): """""" verbose = self._config.get("verbose", 2) self.last_request_time = "{0}_{1}".format( datetime.utcnow().strftime("%Y-%m-%d_%H-%M-%S"), int(round(time.time() * 1000))) method = environ["REQUEST_METHOD"] debugBreak = False dumpRequest = False dumpResponse = False if verbose >= 3 or self._config.get("dump_requests"): dumpRequest = dumpResponse = True # Process URL commands if "dump_storage" in environ.get("QUERY_STRING"): dav = environ.get("wsgidav.provider") if dav.lockManager: dav.lockManager._dump() if dav.propManager: dav.propManager._dump() # Turn on max. debugging for selected litmus tests litmusTag = environ.get("HTTP_X_LITMUS", environ.get("HTTP_X_LITMUS_SECOND")) if litmusTag and verbose >= 2: print("----\nRunning litmus test '%s'..." % litmusTag, file=self.out) for litmusSubstring in self.debug_litmus: if litmusSubstring in litmusTag: verbose = 3 debugBreak = True dumpRequest = True dumpResponse = True break for litmusSubstring in self.break_after_litmus: if litmusSubstring in self.passedLitmus and litmusSubstring not in litmusTag: print(" *** break after litmus %s" % litmusTag, file=self.out) sys.exit(-1) if litmusSubstring in litmusTag: self.passedLitmus[litmusSubstring] = True # Turn on max. debugging for selected request methods if verbose >= 2 and method in self.debug_methods: verbose = 3 debugBreak = True dumpRequest = True dumpResponse = True # Set debug options to environment environ["wsgidav.verbose"] = verbose # environ["wsgidav.debug_methods"] = self.debug_methods environ["wsgidav.debug_break"] = debugBreak environ["wsgidav.dump_request_body"] = dumpRequest environ["wsgidav.dump_response_body"] = dumpResponse # Dump request headers if dumpRequest: print("<%s> --- %s Request ---" % (threading.currentThread().ident, method), file=self.out) for k, v in environ.items(): if k == k.upper(): print("%20s: '%s'" % (k, v), file=self.out) print("\n", file=self.out) self._dump_request(environ, xml=None) # Intercept start_response # sub_app_start_response = util.SubAppStartResponse() nbytes = 0 first_yield = True app_iter = self._application(environ, sub_app_start_response) for v in app_iter: # Start response (the first time) if first_yield: # Success! start_response( sub_app_start_response.status, sub_app_start_response.response_headers, sub_app_start_response.exc_info, ) # Dump response headers if first_yield and dumpResponse: print( "<%s> --- %s Response(%s): ---" % (threading.currentThread().ident, method, sub_app_start_response.status), file=self.out, ) headersdict = dict(sub_app_start_response.response_headers) for envitem in headersdict.keys(): print("%s: %s" % (envitem, repr(headersdict[envitem])), file=self.out) print("", file=self.out) # Check, if response is a binary string, otherwise we probably have # calculated a wrong content-length assert compat.is_bytes(v), v # Dump response body drb = environ.get("wsgidav.dump_response_body") if compat.is_basestring(drb): # Middleware provided a formatted body representation print(drb, file=self.out) elif drb is True: # Else dump what we get, (except for long GET responses) if method == "GET": if first_yield: print(v[:50], "...", file=self.out) elif len(v) > 0: print(v, file=self.out) if dumpResponse: self._dump_response(sub_app_start_response, drb) drb = environ["wsgidav.dump_response_body"] = None nbytes += len(v) first_yield = False yield v if hasattr(app_iter, "close"): app_iter.close() # Start response (if it hasn't been done yet) if first_yield: start_response( # Success! sub_app_start_response.status, sub_app_start_response.response_headers, sub_app_start_response.exc_info, ) if dumpResponse: print( "\n<%s> --- End of %s Response (%i bytes) ---" % (threading.currentThread().ident, method, nbytes), file=self.out, ) return
def __call__(self, environ, start_response): """""" # srvcfg = environ["wsgidav.config"] verbose = self._config.get("verbose", 3) method = environ["REQUEST_METHOD"] debugBreak = False dumpRequest = False dumpResponse = False if verbose >= 5: dumpRequest = dumpResponse = True # Process URL commands if "dump_storage" in environ.get("QUERY_STRING", ""): dav = environ.get("wsgidav.provider") if dav.lockManager: dav.lockManager._dump() if dav.propManager: dav.propManager._dump() # Turn on max. debugging for selected litmus tests litmusTag = environ.get("HTTP_X_LITMUS", environ.get("HTTP_X_LITMUS_SECOND")) if litmusTag and verbose >= 3: _logger.info("----\nRunning litmus test '{}'...".format(litmusTag)) for litmusSubstring in self.debug_litmus: if litmusSubstring in litmusTag: verbose = 5 debugBreak = True dumpRequest = True dumpResponse = True break for litmusSubstring in self.break_after_litmus: if litmusSubstring in self.passedLitmus and litmusSubstring not in litmusTag: _logger.info( " *** break after litmus {}".format(litmusTag)) sys.exit(-1) if litmusSubstring in litmusTag: self.passedLitmus[litmusSubstring] = True # Turn on max. debugging for selected request methods if verbose >= 3 and method in self.debug_methods: verbose = 5 debugBreak = True dumpRequest = True dumpResponse = True # Set debug options to environment environ["wsgidav.verbose"] = verbose # environ["wsgidav.debug_methods"] = self.debug_methods environ["wsgidav.debug_break"] = debugBreak environ["wsgidav.dump_request_body"] = dumpRequest environ["wsgidav.dump_response_body"] = dumpResponse # Dump request headers if dumpRequest: _logger.info("{} Request ---".format(method)) # _logger.info("<{}> --- {} Request ---".format( # threading.currentThread().ident, method)) for k, v in environ.items(): if k == k.upper(): _logger.info("{:<20}: '{}'".format( k, safe_re_encode(v, "utf8"))) _logger.info("\n") # Intercept start_response # sub_app_start_response = util.SubAppStartResponse() nbytes = 0 first_yield = True app_iter = self.next_app(environ, sub_app_start_response) for v in app_iter: # Start response (the first time) if first_yield: # Success! start_response(sub_app_start_response.status, sub_app_start_response.response_headers, sub_app_start_response.exc_info) # Dump response headers if first_yield and dumpResponse: _logger.info("<{}> ---{} Response({}): ---".format( threading.currentThread().ident, method, sub_app_start_response.status)) headersdict = dict(sub_app_start_response.response_headers) for envitem in headersdict.keys(): _logger.info("{}: {}".format(envitem, repr(headersdict[envitem]))) _logger.info("") # Check, if response is a binary string, otherwise we probably have # calculated a wrong content-length assert compat.is_bytes(v), v # Dump response body drb = environ.get("wsgidav.dump_response_body") if compat.is_basestring(drb): # Middleware provided a formatted body representation _logger.info(drb) drb = environ["wsgidav.dump_response_body"] = None elif drb is True: # Else dump what we get, (except for long GET responses) if method == "GET": if first_yield: _logger.info("{}...".format(v[:50])) elif len(v) > 0: _logger.info(v) nbytes += len(v) first_yield = False yield v if hasattr(app_iter, "close"): app_iter.close() # Start response (if it hasn't been done yet) if first_yield: # Success! start_response(sub_app_start_response.status, sub_app_start_response.response_headers, sub_app_start_response.exc_info) if dumpResponse: _logger.info("<{}> --- End of {} Response ({:d} bytes) ---".format( threading.currentThread().ident, method, nbytes)) 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 __call__(self, environ, start_response): """""" # srvcfg = environ["wsgidav.config"] verbose = self._config.get("verbose", 2) self.last_request_time = '{0}_{1}'.format( datetime.utcnow().strftime('%Y-%m-%d_%H-%M-%S'), int(round(time.time() * 1000)), ) method = environ["REQUEST_METHOD"] debugBreak = False dumpRequest = False dumpResponse = False if verbose >= 3 or self._config.get("dump_requests"): dumpRequest = dumpResponse = True # Process URL commands if "dump_storage" in environ.get("QUERY_STRING"): dav = environ.get("wsgidav.provider") if dav.lockManager: dav.lockManager._dump() if dav.propManager: dav.propManager._dump() # Turn on max. debugging for selected litmus tests litmusTag = environ.get("HTTP_X_LITMUS", environ.get("HTTP_X_LITMUS_SECOND")) if litmusTag and verbose >= 2: print("----\nRunning litmus test '%s'..." % litmusTag, file=self.out) for litmusSubstring in self.debug_litmus: if litmusSubstring in litmusTag: verbose = 3 debugBreak = True dumpRequest = True dumpResponse = True break for litmusSubstring in self.break_after_litmus: if litmusSubstring in self.passedLitmus and litmusSubstring not in litmusTag: print(" *** break after litmus %s" % litmusTag, file=self.out) sys.exit(-1) if litmusSubstring in litmusTag: self.passedLitmus[litmusSubstring] = True # Turn on max. debugging for selected request methods if verbose >= 2 and method in self.debug_methods: verbose = 3 debugBreak = True dumpRequest = True dumpResponse = True # Set debug options to environment environ["wsgidav.verbose"] = verbose # environ["wsgidav.debug_methods"] = self.debug_methods environ["wsgidav.debug_break"] = debugBreak environ["wsgidav.dump_request_body"] = dumpRequest environ["wsgidav.dump_response_body"] = dumpResponse # Dump request headers if dumpRequest: print("<%s> --- %s Request ---" % ( threading.currentThread().ident, method), file=self.out) for k, v in environ.items(): if k == k.upper(): print("%20s: '%s'" % (k, v), file=self.out) print("\n", file=self.out) self._dump_request(environ, xml=None) # Intercept start_response # sub_app_start_response = util.SubAppStartResponse() nbytes = 0 first_yield = True app_iter = self._application(environ, sub_app_start_response) for v in app_iter: # Start response (the first time) if first_yield: # Success! start_response(sub_app_start_response.status, sub_app_start_response.response_headers, sub_app_start_response.exc_info) # Dump response headers if first_yield and dumpResponse: print("<%s> --- %s Response(%s): ---" % ( threading.currentThread().ident, method, sub_app_start_response.status), file=self.out) headersdict = dict(sub_app_start_response.response_headers) for envitem in headersdict.keys(): print("%s: %s" % (envitem, repr(headersdict[envitem])), file=self.out) print("", file=self.out) # Check, if response is a binary string, otherwise we probably have # calculated a wrong content-length assert compat.is_bytes(v), v # Dump response body drb = environ.get("wsgidav.dump_response_body") if compat.is_basestring(drb): # Middleware provided a formatted body representation print(drb, file=self.out) elif drb is True: # Else dump what we get, (except for long GET responses) if method == "GET": if first_yield: print(v[:50], "...", file=self.out) elif len(v) > 0: print(v, file=self.out) if dumpResponse: self._dump_response(sub_app_start_response, drb) drb = environ["wsgidav.dump_response_body"] = None nbytes += len(v) first_yield = False yield v if hasattr(app_iter, "close"): app_iter.close() # Start response (if it hasn't been done yet) if first_yield: # Success! start_response(sub_app_start_response.status, sub_app_start_response.response_headers, sub_app_start_response.exc_info) if dumpResponse: print("\n<%s> --- End of %s Response (%i bytes) ---" % ( threading.currentThread().ident, method, nbytes), file=self.out) return
def __init__(self, config): self.config = copy.deepcopy(DEFAULT_CONFIG) util.deep_update(self.config, config) # self.config.update(config) config = self.config # Evaluate configuration and set defaults _check_config(config) # response_trailer = config.get("response_trailer", "") self.verbose = config.get("verbose", 3) lockStorage = config.get("lock_manager") if lockStorage is True: lockStorage = LockStorageDict() if not lockStorage: self.locksManager = None else: self.locksManager = LockManager(lockStorage) self.propsManager = config.get("property_manager") if not self.propsManager: # Normalize False, 0 to None self.propsManager = None elif self.propsManager is True: self.propsManager = PropertyManager() self.mount_path = config.get("mount_path") # Instantiate DAV resource provider objects for every share # provider_mapping may contain the args that are passed to a `FilesystemProvider` # instance: # <mount_path>: <folder_path> # or # <mount_path>: [folder_path, is_readonly] # or contain a complete new instance: # <mount_path>: <DAVProvider Instance> provider_mapping = self.config["provider_mapping"] self.providerMap = {} self.sortedShareList = None for share, provider in provider_mapping.items(): self.add_provider(share, provider) domain_controller = None # Define WSGI application stack middleware_stack = config.get("middleware_stack", []) mw_list = [] # This is the 'outer' application, i.e. the WSGI application object that # is eventually called by the server. self.application = self # When building up the middleware stack, this app will be wrapped and replaced by the # next middleware in the list. # The `middleware_stack` is configured such that the first app in the list should be # called first. But since every app wraps its predecessor, we iterate in reverse # order: for mw in reversed(middleware_stack): # The middleware stack configuration may contain plain strings, dicts, # classes, or objects app = None if compat.is_basestring(mw): # If a plain string is passed, try to import it, assuming BaseMiddleware # signature app_class = dynamic_import_class(mw) app = app_class(self, self.application, config) elif type(mw) is dict: # If a dict with one entry is passed, use the key as module/class name # and the value as constructor arguments (positional or kwargs). assert len(mw) == 1 name, args = list(mw.items())[0] expand = {"${application}": self.application} app = dynamic_instantiate_middleware(name, args, expand) elif inspect.isclass(mw): # If a class is passed, assume BaseMiddleware (or compatible) assert issubclass( mw, BaseMiddleware ) # TODO: remove this assert with 3.0 app = mw(self, self.application, config) else: # Otherwise assume an initialized middleware instance app = mw # TODO: We should try to generalize this specific code: if isinstance(app, HTTPAuthenticator): domain_controller = app.get_domain_controller() # Check anonymous access for share, data in self.providerMap.items(): if app.allow_anonymous_access(share): data["allow_anonymous"] = True # Add middleware to the stack if app: mw_list.append(app) self.application = app else: _logger.error("Could not add middleware {}.".format(mw)) # Print info _logger.info( "WsgiDAV/{} Python/{} {}".format( __version__, util.PYTHON_VERSION, platform.platform(aliased=True) ) ) if self.verbose >= 4: _logger.info( "Default encoding: {!r} (file system: {!r})".format( sys.getdefaultencoding(), sys.getfilesystemencoding() ) ) _logger.info("Lock manager: {}".format(self.locksManager)) _logger.info("Property manager: {}".format(self.propsManager)) _logger.info("Domain controller: {}".format(domain_controller)) # We traversed the stack in reverse order. Now revert again, so # we see the order that was configured: _logger.info("Middleware stack:") for mw in reversed(mw_list): _logger.info(" - {}".format(mw)) _logger.info("Registered DAV providers by route:") for share in self.sortedShareList: data = self.providerMap[share] hint = " (anonymous)" if data["allow_anonymous"] else "" _logger.info(" - '{}': {}{}".format(share, data["provider"], hint)) for share, data in self.providerMap.items(): if data["allow_anonymous"]: # TODO: we should only warn here, if --no-auth is not given _logger.warn("Share '{}' will allow anonymous access.".format(share)) return
def __call__(self, environ, start_response): """""" # srvcfg = environ["wsgidav.config"] verbose = self._config.get("verbose", 3) method = environ["REQUEST_METHOD"] debugBreak = False dumpRequest = False dumpResponse = False if verbose >= 5: dumpRequest = dumpResponse = True # Process URL commands if "dump_storage" in environ.get("QUERY_STRING", ""): dav = environ.get("wsgidav.provider") if dav.lock_manager: dav.lock_manager._dump() if dav.prop_manager: dav.prop_manager._dump() # Turn on max. debugging for selected litmus tests litmusTag = environ.get("HTTP_X_LITMUS", environ.get("HTTP_X_LITMUS_SECOND")) if litmusTag and verbose >= 3: _logger.info("----\nRunning litmus test '{}'...".format(litmusTag)) for litmusSubstring in self.debug_litmus: if litmusSubstring in litmusTag: verbose = 5 debugBreak = True dumpRequest = True dumpResponse = True break for litmusSubstring in self.break_after_litmus: if ( litmusSubstring in self.passedLitmus and litmusSubstring not in litmusTag ): _logger.info(" *** break after litmus {}".format(litmusTag)) sys.exit(-1) if litmusSubstring in litmusTag: self.passedLitmus[litmusSubstring] = True # Turn on max. debugging for selected request methods if verbose >= 3 and method in self.debug_methods: verbose = 5 debugBreak = True dumpRequest = True dumpResponse = True # Set debug options to environment environ["wsgidav.verbose"] = verbose # environ["wsgidav.debug_methods"] = self.debug_methods environ["wsgidav.debug_break"] = debugBreak environ["wsgidav.dump_request_body"] = dumpRequest environ["wsgidav.dump_response_body"] = dumpResponse # Dump request headers if dumpRequest: _logger.info("{} Request ---".format(method)) # _logger.info("<{}> --- {} Request ---".format( # threading.currentThread().ident, method)) for k, v in environ.items(): if k == k.upper(): _logger.info("{:<20}: '{}'".format(k, safe_re_encode(v, "utf8"))) _logger.info("\n") # Intercept start_response # sub_app_start_response = util.SubAppStartResponse() nbytes = 0 first_yield = True app_iter = self.next_app(environ, sub_app_start_response) for v in app_iter: # Start response (the first time) if first_yield: # Success! start_response( sub_app_start_response.status, sub_app_start_response.response_headers, sub_app_start_response.exc_info, ) # Dump response headers if first_yield and dumpResponse: _logger.info( "<{}> ---{} Response({}): ---".format( threading.currentThread().ident, method, sub_app_start_response.status, ) ) headersdict = dict(sub_app_start_response.response_headers) for envitem in headersdict.keys(): _logger.info("{}: {}".format(envitem, repr(headersdict[envitem]))) _logger.info("") # Check, if response is a binary string, otherwise we probably have # calculated a wrong content-length assert compat.is_bytes(v), v # Dump response body drb = environ.get("wsgidav.dump_response_body") if compat.is_basestring(drb): # Middleware provided a formatted body representation _logger.info(drb) drb = environ["wsgidav.dump_response_body"] = None elif drb is True: # Else dump what we get, (except for long GET responses) if method == "GET": if first_yield: _logger.info("{}...".format(v[:50])) elif len(v) > 0: _logger.info(v) nbytes += len(v) first_yield = False yield v if hasattr(app_iter, "close"): app_iter.close() # Start response (if it hasn't been done yet) if first_yield: # Success! start_response( sub_app_start_response.status, sub_app_start_response.response_headers, sub_app_start_response.exc_info, ) if dumpResponse: _logger.info( "<{}> --- End of {} Response ({:d} bytes) ---".format( threading.currentThread().ident, method, nbytes ) ) return
def _expand(v): """Replace some string templates with defined values.""" if expand and compat.is_basestring(v) and v.lower() in expand: return expand[v] return v
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