Ejemplo n.º 1
0
    def index(self, path_b=b"", date="", usetar=""):
        assert isinstance(path_b, str)
        assert isinstance(date, unicode)
        assert isinstance(usetar, unicode)

        logger.debug("restoring [%s][%s]" % (decode_s(path_b, "replace"), date))

        # The path_b wont have leading and trailing "/".
        (path_b, file_b) = os.path.split(path_b)
        if not path_b:
            path_b = file_b
            file_b = b""

        # Check user access to repo / path.
        try:
            (repo_obj, path_obj) = self.validate_user_path(path_b)
        except librdiff.FileError as e:
            logger.exception("invalid user path")
            return self._compile_error_template(unicode(e))

        # Get the restore date
        try:
            restore_date = rdw_helpers.rdwTime()
            restore_date.initFromInt(int(date))
        except:
            logger.warn("invalid date %s" % date)
            return self._compile_error_template(_("Invalid date."))

        try:
            # Get if backup in progress
            if repo_obj.in_progress:
                return self._compile_error_template(
                    _(
                        """A backup is currently in progress to this repository. Restores are disabled until this backup is complete."""
                    )
                )

            # Restore the file
            file_path_b = path_obj.restore(file_b, restore_date, usetar != "T")

        except librdiff.FileError as e:
            logger.exception("fail to restore")
            return self._compile_error_template(unicode(e))
        except ValueError:
            logger.exception("fail to restore")
            return self._compile_error_template(_("Fail to restore."))

        # The restored file path need to be deleted when the user is finish
        # downloading. The auto-delete tool, will do it if we give him a file
        # to delete.
        cherrypy.request._autodelete_dir = file_path_b

        # The file name return by rdiff-backup is in bytes. We do not process
        # it. Cherrypy seams to handle it any weird encoding from this point.
        logger.info("restored file [%s]" % decode_s(file_path_b, "replace"))
        filename = os.path.basename(file_path_b)
        # Escape quotes in filename
        filename = filename.replace(b'"', b'\\"')
        return serve_file(file_path_b, None, disposition=b"attachment", name=filename)
Ejemplo n.º 2
0
    def validate_user_path(self, path_b):
        '''Takes a path relative to the user's root dir and validates that it
        is valid and within the user's root'''
        assert isinstance(path_b, str)

        # Add a ending slash (/) to avoid matching wrong repo. Ref #56
        path_b = path_b.strip(b'/') + b'/'

        # NOTE: a blank path is allowed, since the user root directory might be
        # a repository.

        logger.debug("check user access to path [%s]" %
                     decode_s(path_b, 'replace'))

        # Get reference to user repos
        user_repos = self.app.currentuser.repos

        # Check if any of the repos matches the given path.
        user_repos_matches = [
            encode_s(user_repo).strip(b'/')
            for user_repo in user_repos
            if path_b.startswith(encode_s(user_repo).strip(b'/') + b'/')]
        if not user_repos_matches:
            # No repo matches
            logger.error("user doesn't have access to [%s]" %
                         decode_s(path_b, 'replace'))
            raise librdiff.AccessDeniedError
        repo_b = user_repos_matches[0]

        # Get reference to user_root
        user_root_b = encode_s(self.app.currentuser.root_dir)

        # Check path vs real path value
        full_path_b = os.path.join(user_root_b, path_b).rstrip(b"/")
        real_path_b = os.path.realpath(full_path_b).rstrip(b"/")
        if full_path_b != real_path_b:
            # We can safely assume the realpath contains a symbolic link. If
            # the symbolic link is valid, we display the content of the "real"
            # path.
            if real_path_b.startswith(os.path.join(user_root_b, repo_b)):
                path_b = os.path.relpath(real_path_b, user_root_b)
            else:
                logger.warn("access is denied [%s] vs [%s]" % (
                    decode_s(full_path_b, 'replace'),
                    decode_s(real_path_b, 'replace')))
                raise librdiff.AccessDeniedError

        # Get reference to the repository (this ensure the repository does
        # exists and is valid.)
        repo_obj = librdiff.RdiffRepo(user_root_b, repo_b)

        # Get reference to the path.
        path_b = path_b[len(repo_b):]
        path_obj = repo_obj.get_path(path_b)

        return (repo_obj, path_obj)
Ejemplo n.º 3
0
    def validate_user_path(self, path_b):
        """Takes a path relative to the user's root dir and validates that it
        is valid and within the user's root"""
        assert isinstance(path_b, str)
        path_b = path_b.strip(b"/") + b"/"

        # NOTE: a blank path is allowed, since the user root directory might be
        # a repository.

        logger.debug("check user access to path [%s]" % decode_s(path_b, "replace"))

        # Get reference to user repos
        user_repos = self.getUserDB().get_repos(self.getUsername())

        # Check if any of the repos matches the given path.
        user_repos_matches = filter(lambda x: path_b.startswith(encode_s(x).strip(b"/") + b"/"), user_repos)
        if not user_repos_matches:
            # No repo matches
            logger.error("user doesn't have access to [%s]" % decode_s(path_b, "replace"))
            raise librdiff.AccessDeniedError
        repo_b = encode_s(user_repos_matches[0]).strip(b"/")

        # Get reference to user_root
        user_root = self.getUserDB().get_root_dir(self.getUsername())
        user_root_b = encode_s(user_root)

        # Check path vs real path value
        full_path_b = os.path.join(user_root_b, path_b).rstrip(b"/")
        real_path_b = os.path.realpath(full_path_b).rstrip(b"/")
        if full_path_b != real_path_b:
            # We can safely assume the realpath contains a symbolic link. If
            # the symbolic link is valid, we display the content of the "real"
            # path.
            if real_path_b.startswith(os.path.join(user_root_b, repo_b)):
                path_b = os.path.relpath(real_path_b, user_root_b)
            else:
                logger.warn(
                    "access is denied [%s] vs [%s]"
                    % (decode_s(full_path_b, "replace"), decode_s(real_path_b, "replace"))
                )
                raise librdiff.AccessDeniedError

        # Get reference to the repository (this ensure the repository does
        # exists and is valid.)
        repo_obj = librdiff.RdiffRepo(user_root_b, repo_b)

        # Get reference to the path.
        path_b = path_b[len(repo_b) :]
        path_obj = repo_obj.get_path(path_b)

        return (repo_obj, path_obj)
Ejemplo n.º 4
0
def autodelete():
    """Register an handler to delete the restored files when the HTTP
    request is ending."""
    if not hasattr(cherrypy.request, "_autodelete_dir"):
        return
    autodelete_dir = cherrypy.request._autodelete_dir
    logger.info("deleting temporary folder [%s]" % decode_s(autodelete_dir, "replace"))
    # Check if path exists
    if not os.access(autodelete_dir, os.F_OK):
        logger.info("temporary folder [%s] doesn't exists" % decode_s(autodelete_dir, "replace"))
        return
    if not os.path.isdir(autodelete_dir):
        autodelete_dir = os.path.dirname(autodelete_dir)
    shutil.rmtree(autodelete_dir, ignore_errors=True)
Ejemplo n.º 5
0
    def _get_parms_for_page(self, repo_obj, path_obj, restore):
        assert isinstance(repo_obj, librdiff.RdiffRepo)
        assert isinstance(path_obj, librdiff.RdiffPath)

        # Build "parent directories" links
        parents = []
        parents.append({"path": b"", "name": repo_obj.display_name})
        parent_path_b = b""
        for part_b in path_obj.path.split(b"/"):
            if part_b:
                parent_path_b = os.path.join(parent_path_b, part_b)
                display_name = decode_s(repo_obj.unquote(part_b), 'replace')
                parents.append({"path": parent_path_b,
                                "name": display_name})

        # Set up warning about in-progress backups, if necessary
        warning = ""
        if repo_obj.in_progress:
            warning = _("""A backup is currently in progress to this repository. The displayed data may be inconsistent.""")

        dir_entries = []
        restore_dates = []
        if restore:
            restore_dates = path_obj.restore_dates
        else:
            # Get list of actual directory entries
            dir_entries = path_obj.dir_entries

        return {"repo_name": repo_obj.display_name,
                "repo_path": repo_obj.path,
                "path": path_obj.path,
                "dir_entries": dir_entries,
                "parents": parents,
                "restore_dates": restore_dates,
                "warning": warning}
Ejemplo n.º 6
0
 def _hashPassword(self, password):
     # At this point the password should be unicode. We converted it into
     # system encoding.
     password_b = encode_s(password)
     import sha
     hasher = sha.new()
     hasher.update(password_b)
     return decode_s(hasher.hexdigest())
Ejemplo n.º 7
0
    def index(self, path_b=b""):
        assert isinstance(path_b, str)

        logger.debug("history [%s]" % decode_s(path_b, 'replace'))

        try:
            (repo_obj, path_obj) = self.validate_user_path(path_b)
        except librdiff.FileError as e:
            logger.exception("invalid user path")
            return self._writeErrorPage(unicode(e))

        try:
            parms = self._get_parms_for_page(repo_obj)
        except librdiff.FileError:
            logger.exception("can't create page params")
            return self._writeErrorPage(unicode(e))

        return self._writePage("history.html", **parms)
Ejemplo n.º 8
0
    def index(self, path_b=b"", restore=""):
        assert isinstance(path_b, str)
        assert isinstance(restore, unicode)

        logger.debug("browsing [%s]" % decode_s(path_b, 'replace'))

        # Check user access to the given repo & path
        try:
            (repo_obj, path_obj) = self.validate_user_path(path_b)
        except librdiff.FileError as e:
            logger.exception("invalid user path")
            return self._compile_error_template(unicode(e))

        # Build the parameters
        try:
            parms = self._get_parms_for_page(repo_obj,
                                             path_obj,
                                             restore == b"T")
        except librdiff.FileError as e:
            logger.exception("can't create params")
            return self._compile_error_template(unicode(e))

        return self._compile_template("browse.html", **parms)
Ejemplo n.º 9
0
    def _getUserMessages(self,
                         repos,
                         includeSuccess,
                         includeFailure,
                         earliest_date,
                         latest_date):

        user_root = self.app.userdb.get_user_root(self.app.currentuser.username)
        user_root_b = encode_s(user_root)

        repoErrors = []
        allBackups = []
        for repo in repos:
            # Get binary representation of the repo
            repo_b = encode_s(repo) if isinstance(repo, unicode) else repo
            repo_b = repo_b.lstrip(b"/")
            try:
                repo_obj = librdiff.RdiffRepo(user_root_b, repo_b)
                backups = repo_obj.get_history_entries(-1, earliest_date,
                                                       latest_date)
                allBackups += [{"repo_path": repo_obj.path,
                                "repo_name": repo_obj.display_name,
                                "date": backup.date,
                                "size": backup.size,
                                "errors": backup.errors} for backup in backups]
            except librdiff.FileError as e:
                repoErrors.append(
                    {"repo_path": repo_b,
                     "repo_name": decode_s(repo_b, 'replace'),
                     "error": unicode(e)})

        allBackups.sort(lambda x, y: cmp(y["date"], x["date"]))
        failedBackups = filter(lambda x: x["errors"], allBackups)

        # group successful backups by day
        successfulBackups = filter(lambda x: not x["errors"], allBackups)
        if successfulBackups:
            lastSuccessDate = successfulBackups[0]["date"]
        successfulBackups = rdw_helpers.groupby(
            successfulBackups, lambda x: x["date"].getLocalDaysSinceEpoch())

        userMessages = []

        # generate failure messages
        if includeFailure:
            for job in failedBackups:
                date = job["date"]
                job.update(
                    {"is_success": False,
                     "date": date,
                     "repoErrors": [],
                     "backups": [],
                     "repo_path": job["repo_path"],
                     "repo_name": job["repo_name"]})
                userMessages.append(job)

        # generate success messages (publish date is most recent backup date)
        if includeSuccess:
            for day in successfulBackups.keys():
                date = successfulBackups[day][0]["date"]

                # include repository errors in most recent entry
                if date == lastSuccessDate:
                    repoErrorsForMsg = repoErrors
                else:
                    repoErrorsForMsg = []

                userMessages.append(
                    {"is_success": True,
                     "date": date,
                     "repoErrors": repoErrorsForMsg,
                     "backups": successfulBackups[day]})

        # sort messages by date
        userMessages.sort(lambda x, y: cmp(y["date"], x["date"]))
        return userMessages
Ejemplo n.º 10
0
    def restore(self, name, restore_date, use_zip):
        """Used to restore the given file located in this path."""
        assert isinstance(name, str)
        assert isinstance(restore_date, rdw_helpers.rdwTime)
        name = name.lstrip(b"/")

        # Determine the file name to be restore (from rdiff-backup
        # point of view).
        file_to_restore = os.path.join(self.full_path, name)
        file_to_restore = self.repo.unquote(file_to_restore)

        # Convert the date into epoch.
        date_epoch = str(restore_date.getSeconds())

        # Define a location where to restore the data
        if name == b"" and self.path == b"":
            filename = b"root"
        if self.path != b"":
            filename = os.path.basename(self.path)
        if name != b"":
            filename = name
        # Generate a temporary location used to restore data.
        output = os.path.join(tempfile.mkdtemp(), filename)

        # Execute rdiff-backup to restore the data.
        logger.info(
            "execute rdiff-backup --restore-as-of=%s '%s' '%s'" % (
                date_epoch,
                rdw_helpers.decode_s(file_to_restore, 'replace'),
                rdw_helpers.decode_s(output, 'replace'),
            ))
        results = self._execute(
            b"rdiff-backup",
            b"--restore-as-of=" + date_epoch,
            file_to_restore,
            output)

        # Check the result
        if results['exitCode'] != 0 or not os.access(output, os.F_OK):
            error = results['stderr']
            if not error:
                error = '''rdiff-backup claimed success, but did not restore
                        anything. This indicates a bug in rdiffweb. Please
                        report this to a developer.'''
            raise UnknownError('unable to restore!\n' + error)

        # The path restored is a directory and need to be archived using zip
        # or tar
        if os.path.isdir(output):
            output_dir = output
            try:
                if use_zip:
                    output = output_dir + ZIP_SUFFIX
                    self._recursive_zip(output_dir, output)
                else:
                    output = output_dir + TARGZ_SUFFIX
                    self._recursiveTarDir(output_dir, output)
            finally:
                shutil.rmtree(output_dir, ignore_errors=True)

        # Return the location of the file to be restored
        return output