コード例 #1
0
    def file_type(self, remote_file):

        assert not crypto.is_encrypted_path(remote_file), \
            "file_type receives unencrypted paths"

        # Try finding plain file first
        if remote_file[-1] == '/':
            response = self.client.file_type(remote_file[:-1])
        else:
            response = self.client.file_type(remote_file)
        is_encrypted = False
        if response['content'] is None and response['status'] == 'online':
            # Then encrypted file
            if remote_file[-1] == '/':
                remote_file_hash = crypto.get_path_hash(remote_file[:-1])
            else:
                remote_file_hash = crypto.get_path_hash(remote_file)
            response = self.client.file_type(remote_file_hash)
            is_encrypted = True

        if response['content'] is None:
            is_encrypted = False

        if response['status'] != 'online':
            raise VimboxOfflineError("Connection error")

        if response['content'] == 'dir':
            assert remote_file[-1] == '/', \
                VimboxClientError("Folder paths must end in /")
        elif response['content'] == 'file':
            assert remote_file[-1] != '/', \
                VimboxClientError("File paths can not end in /")

        return response['content'], is_encrypted, response['status']
コード例 #2
0
    def _tentative_fetch(self, remote_file, password):

        # Initial assumption about encryption
        if password:
            is_encrypted = True
            remote_file_hash = crypto.get_path_hash(remote_file)
            response = self.client.file_download(remote_file_hash)
        else:
            is_encrypted = False
            response = self.client.file_download(remote_file)

        if response['status'] == 'api-error':
            # Unexpected error
            raise VimboxClientError("api-error:\n%s" % response['alerts'])

        elif response['status'] == 'connection-error':
            # Offline
            raise VimboxOfflineError("Connection error")

        elif response['content'] is None:

            # No file found, but need to check for hashed / unhashed name
            # collision
            if password:
                # We asked for an encrypted file, try unencrypted
                response = self.client.file_download(remote_file)
            else:
                # We asked for an unencrypted file try encrypted
                remote_file_hash = crypto.get_path_hash(remote_file)
                response = self.client.file_download(remote_file_hash)

            # Second check
            if response['status'] == 'api-error':
                # Unexpected error
                # This one is weird, since we tried once and it was ok
                raise VimboxClientError("api-error:\n%s" % response['alerts'])

            elif response['status'] == 'connection-error':
                # Suddenly, Offline
                raise VimboxOfflineError("Connection error")

            elif response['content'] is None:
                # File really does not exist
                pass

            elif password:

                # Provided a password but the file exists unencrypted in remote
                VimboxClientError(
                    "Tried to fetch encrypted version of %s but it exists "
                    "unencrypted in remote" % remote_file)

            else:

                # Tried to fetch as unecrypted but it is encrypted
                is_encrypted = True

        return response, is_encrypted
コード例 #3
0
def register_file(remote_file, config, is_encripted):
    """
    Add folder of file to cache and file to hash list if encrypted

    A file can be registered by its folder or it name directly
    """

    assert remote_file, "file to register cannot be empty path"

    rewrite_config = False

    # Register folder in cache
    if remote_file[-1] == '/':
        remote_folder = remote_file
    else:
        remote_folder = "%s/" % os.path.dirname(remote_file)
    # if not is_registered and list(set(remote_folder))[0] != '/':
    if remote_folder not in config['cache']:
        config['cache'].append(remote_folder)
        rewrite_config = True
        # update cache in system
        print("Added to cache %s" % remote_folder)

    # Register file hash in the local cache
    if is_encripted:
        remote_file_hash = crypto.get_path_hash(remote_file)
        if remote_file_hash not in config['path_hashes']:
            config['path_hashes'][remote_file_hash] = remote_file
            rewrite_config = True
            print("Added to hash list %s" % remote_file)

    if rewrite_config:
        write_config(CONFIG_FILE, config)
コード例 #4
0
    def remove(self, remote_file, recursive=False, password=None):

        if remote_file == '/':
            raise VimboxClientError(
                "\nRemoving root is disallowed, just in case you have fat"
                " fingers\n")

        # Extra check for deletable files/folders
        is_rem, reason, is_encrypted = \
            self.is_removable(remote_file, recursive=recursive)

        if not is_rem:
            raise VimboxClientError("\nCan not remove due to: %s\n" % reason)

        # Hash name if necessary
        if is_encrypted:
            original_name = remote_file
            remote_file = crypto.get_path_hash(remote_file)
        else:
            original_name = remote_file

        # TODO: This should go to the client specific part and have exception
        # handling
        if remote_file[-1] == '/':
            # Remove backslash
            # TODO: This is input sanity check should go in the client
            # dependent part
            response = self.client.files_delete(remote_file[:-1])
            # update cache
            # local.update_cache()
        else:
            response = self.client.files_delete(remote_file)

        if response['status'] == 'api-error':

            # Remove local copy
            local_file = self.get_local_file(remote_file)
            if os.path.isfile(local_file):
                os.remove(local_file)
            elif os.path.isdir(local_file):
                shutil.rmtree(local_file)
            self.unregister_file(remote_file)
            if self.verbose > 0:
                print("%s did not exist in remote!" % original_name)

        elif response['status'] != 'connection-error':
            if self.verbose > 0:
                print("%-12s %s" % (yellow("removed"), original_name))

            # Remove local copy
            local_file = self.get_local_file(remote_file)
            if os.path.isfile(local_file):
                os.remove(local_file)
            elif os.path.isdir(local_file):
                shutil.rmtree(local_file)
            self.unregister_file(original_name)
        elif self.verbose > 0:
            print("%-12s did not remove!  %s" %
                  (red("offline"), original_name))
コード例 #5
0
    def _push(self, new_local_content, remote_file, password=None):
        """
        Push updates to remote

        NOTE: This overwrites remote content. It can lead to loss of data.
        """

        # If encrypted get encrypted remote-name
        if password is not None:
            # Validate pasword
            validated_password = crypto.validate_password(password)
            # Hash filename
            remote_file_hash = crypto.get_path_hash(remote_file)
            # Encript content
            new_local_content = crypto.encrypt_content(new_local_content,
                                                       validated_password)
        else:
            remote_file_hash = remote_file
            if sys.version_info[0] > 2:
                # Encoding for Python3
                new_local_content = str.encode(new_local_content)
        # Overwrite remote
        self.client.files_upload(new_local_content, remote_file_hash)
コード例 #6
0
def read_remote_content(remote_file, password=None):

    if password:
        password = crypto.validate_password(password)
        remote_file = crypto.get_path_hash(remote_file)
        true_path = get_fake_remote_local_path(remote_file)
        with open(true_path, 'rb') as fid:
            text = fid.read()
        text, _ = crypto.decript_content(text, password)
    else:
        true_path = get_fake_remote_local_path(remote_file)
        with open(true_path, 'rb') as fid:
            text = fid.read()

    # Python3
    if text and sys.version_info[0] > 2 and not isinstance(text, str):
        try:
            text = text.decode("utf-8")
        except UnicodeDecodeError:
            # Encrypted content
            pass

    return text
コード例 #7
0
def hash_is_registered(encrypted_file):
    key = (get_path_hash(encrypted_file), encrypted_file)
    return key in load_config()['path_hashes'].items()
コード例 #8
0
    def copy(self, remote_source, remote_target):
        """
        This should support:

        cp /path/to/file /path/to/another/file   (file does not exist)
        cp /path/to/file /path/to/folder/
        cp /path/to/folder/ /path/to/folder2/
        cp /path/to/folder/ /path/to/folder2/    (folder2 does not exist)
        """

        # Note file_type enforces using / for folders
        target_type, _, status = self.file_type(remote_target)
        if target_type == 'file':
            raise VimboxClientError('Target file %s exists' % remote_target)
        elif target_type == 'dir':
            if remote_source[-1] != '/':
                # cp /path/to/file /path/to/folder/
                # map to cp /path/to/file /path/to/folder/file
                source_basename = os.path.basename(remote_source)
                remote_target = remote_target + '/' + source_basename
            else:
                # cp /path/to/folder/ /path/to/folder2/
                # map to cp /path/to/folder/ /path/to/folder2/folder/
                source_basename = os.path.basename(remote_source[:-1])
                remote_target = remote_target + source_basename + "/"
        elif remote_source[-1] == '/':
            # cp /path/to/folder/ /path/to/folder2/ (folder2 does not exist)
            pass

        if status == 'connection-error':
            raise VimboxOfflineError("Connection error")

        # For folder we need to remove the ending back-slash
        if remote_source[-1] == '/':
            remote_source2 = remote_source[:-1]
        else:
            remote_source2 = remote_source
        if remote_target[-1] == '/':
            remote_target2 = remote_target[:-1]
        else:
            remote_target2 = remote_target
        response = self.client.files_copy(remote_source2, remote_target2)

        # If there is an error, try encrypted names
        is_encrypted = False
        if response['status'] == 'api-error':
            remote_source_hash = crypto.get_path_hash(remote_source2)
            remote_target_hash = crypto.get_path_hash(remote_target2)
            response = self.client.files_copy(remote_source_hash,
                                              remote_target_hash)
            is_encrypted = True

        if response['status'] == 'online':

            # Local move if we had a copy
            local_source = self.get_local_file(remote_source)
            local_target = self.get_local_file(remote_target)
            if os.path.isfile(local_source):
                # Make missing local folder
                local_target_folder = os.path.dirname(local_target)
                if not os.path.isdir(local_target_folder):
                    os.makedirs(local_target_folder)
                shutil.move(local_source, local_target)
            elif os.path.isdir(local_source):
                shutil.copytree(local_source, local_target)
            # update cache and hash list
            if remote_source[-1] != '/':
                self.register_file(remote_target, is_encrypted)
            else:
                # If we are copying a folder we need to look for hashes inside
                # that folder and change their names
                self.register_file(remote_target, is_encrypted)
                self.copy_hash(remote_source, remote_target)
            if self.verbose > 0:
                items = (yellow("copied"), remote_source, remote_target)
                print("%-12s %s %s" % items)
        elif response['status'] == 'connection-error':
            raise VimboxOfflineError("Connection error")
        else:
            raise VimboxClientError("api-error: %s" % response['alerts'])
コード例 #9
0
    def list_folders(self, remote_folder):
        """ list folder content in remote """

        # Try first remote
        if remote_folder and remote_folder[-1] == '/':
            response = self.client.list_folders(remote_folder[:-1])
        else:
            response = self.client.list_folders(remote_folder)
        entries = response['content']['entries']
        is_files = response['content']['is_files']
        status = response['status']
        message = response['alerts']

        # Second try to see if there is an ecrypted file
        # TODO: entries is None is used to signal a this is a file not a
        # folder. This is an obscure way of dealing with this.
        is_encrypted = False
        if status == 'online' and entries is False:
            enc_remote_folder = crypto.get_path_hash(remote_folder)
            response = self.client.list_folders(enc_remote_folder)
            entries = response['content']['entries']
            is_files = response['content']['is_files']
            status = response['status']
            message = response['alerts']
            is_encrypted = status == 'online'

        display_string = ""
        if status == 'api-error':
            raise VimboxClientError("api-error")

        elif status == 'online' and entries is False:
            # Folder/File non existing
            raise VimboxClientError("%s does not exist in remote" %
                                    remote_folder)

        elif status == 'online' and entries is None:
            # This was a file
            return True

        elif status == 'online':

            # Differentiate file and folders
            display_folders = []
            for entry, is_file in zip(entries, is_files):
                # Add slash to files on root
                if remote_folder == '':
                    entry = '/' + entry
                if is_file:
                    # File
                    display_folders.append(entry)
                else:
                    # Folder
                    display_folders.append("%s/" % entry)
            display_folders = sorted(display_folders)

            # Update to match folder
            if remote_folder:

                # Remove folder paths no more in remote
                for path in self.config['cache']:
                    if path[:len(remote_folder)] == remote_folder:
                        cache_folder = \
                            path[len(remote_folder):].split('/')[0] + '/'
                        if (cache_folder not in display_folders
                                and cache_folder != ''):
                            self.config['cache'].remove(path)

                # Add missing folders
                if remote_folder not in self.config['cache']:
                    self.config['cache'].append(remote_folder)
                for folder in display_folders:
                    if folder[-1] == '/':
                        new_path = "%s%s" % (remote_folder, folder)
                        if new_path not in self.config['cache']:
                            self.config['cache'].append(new_path)

                # Write cache
                self.config['cache'] = sorted(self.config['cache'])
                local.write_config(self.config_path, self.config)

            # Replace encrypted files
            entry_types = []
            new_display_folders = []
            for entry in display_folders:
                key = "%s%s" % (remote_folder, entry)
                if key in self.config['path_hashes'].keys():
                    entry_types.append('encrypted')
                    new_display_folders.append(
                        os.path.basename(self.config['path_hashes'][key]))
                elif entry[-1] == '/':
                    entry_types.append('folder')
                    new_display_folders.append(entry)
                else:
                    entry_types.append(None)
                    new_display_folders.append(entry)
            display_folders = new_display_folders

            # Display entries sorted and with colors
            new_display_folders = []
            indices = sorted(range(len(display_folders)),
                             key=display_folders.__getitem__)
            for file_folder, entry_type in zip(display_folders, entry_types):
                if entry_type == 'encrypted':
                    file_folder = red(file_folder)
                elif entry_type == 'folder':
                    file_folder = blue(file_folder)
                new_display_folders.append(file_folder)
            display_string = "".join(
                ["%s\n" % new_display_folders[index] for index in indices])

            # Add file to cache
            if remote_folder not in self.config['cache']:
                self.config['cache'].append(remote_folder)
                local.write_config(self.config_path, self.config)

        elif os.path.isdir(local.get_local_file(remote_folder)):

            # If it fails resort to local cache
            display_folders = local.list_local(remote_folder, self.config)
            if self.verbose > 0:
                print("\n%s content for %s " % (red("offline"), remote_folder))
            display_string = "".join(
                ["%s\n" % folder for folder in sorted(display_folders)])

        # Print
        if self.verbose > 0:
            print("\n%s\n" % display_string.rstrip())
コード例 #10
0
def install_backend(config_file, default_config):

    if os.path.isfile(config_file):

        config = local.read_config(config_file)
        if 'DROPBOX_TOKEN' in config:
            print("Found valid config in %s" % config_file)

        # check for updated hashes from < v0.5.0
        if int(__version__.split('.')[1]) >= 5:
            from vimbox.crypto import get_path_hash
            client = StorageBackEnd(config['DROPBOX_TOKEN'])
            old_hashes = config['path_hashes'].items()
            new_hashes = []
            for dhash, path in old_hashes:
                new_dhash = get_path_hash(path, md5_hash=False)
                if dhash != new_dhash:
                    # Change remote path
                    client.files_copy(dhash, new_dhash)
                    client.files_delete(dhash)
                    new_hashes.append((str(new_dhash), str(path)))
            config['path_hashes'] = dict(new_hashes)
            local.write_config(config_file, config)
            print("Updated %d hashes" % len(new_hashes))

    else:

        # Prompt user for a token
        print(
            "\nI need you to create a dropbox app and give me an acess token."
            " Go here \n\nhttps://www.dropbox.com/developers/apps/\n\n"
            "1) Create App, select Dropbox API\n"
            "2) Select either App folder or Full Dropbox\n"
            "3) Name is irrelevant but vimbox may help you remember\n"
        )

        if sys.version_info[0] > 2:
            dropbox_token = input(
                "Press \"generate acess token\" to get one and paste it here: "
            )
        else:
            dropbox_token = raw_input(
                "Press \"generate acess token\" to get one and paste it here: "
            )

        # Validate token by connecting to dropbox
        client = StorageBackEnd(dropbox_token)
        response = client.get_user_account()
        if response['content'] is None:
            print("Could not connect to dropbox %s" % response['status'])
            exit(1)
        else:
            user_acount = response['content']
            print("Connected to dropbox account %s (%s)" % (
                user_acount.name.display_name,
                user_acount.email)
            )
            # Store
            config = default_config
            config['DROPBOX_TOKEN'] = dropbox_token
            local.write_config(config_file, config)
            print("Created config in %s" % config_file)
コード例 #11
0
def is_fake_remote_file(file_path, password=None):
    if password:
        file_path = crypto.get_path_hash(file_path)
    remote_file = get_fake_remote_local_path(file_path)
    return os.path.isfile(remote_file)