mime-actions: use file metadata for trusting desktop files

Currently we only trust desktop files that have the executable bit
set, and don't replace the displayed icon or the displayed name until
it's trusted, which prevents for running random programs by a malicious
desktop file.

However, the executable permission is preserved if the desktop file
comes from a compressed file.

To prevent this, add a metadata::trusted metadata to the file once the
user acknowledges the file as trusted. This adds metadata to the file,
which cannot be added unless it has access to the computer.

Also remove the SHEBANG "trusted" content we were putting inside the
desktop file, since that doesn't add more security since it can come
with the file itself.

https://bugzilla.gnome.org/show_bug.cgi?id=777991
verify_destination (CommonJob  *job,
                    GFile      *dest,
                    char      **dest_fs_id,
                    goffset     required_size)
{
    GFileInfo *info, *fsinfo;
    GError *error;
    guint64 free_size;
    guint64 size_difference;
    char *primary, *secondary, *details;
    int response;
    GFileType file_type;
    gboolean dest_is_symlink = FALSE;

    if (dest_fs_id)
    {
        *dest_fs_id = NULL;
    }

retry:

    error = NULL;
    info = g_file_query_info (dest,
                              G_FILE_ATTRIBUTE_STANDARD_TYPE ","
                              G_FILE_ATTRIBUTE_ID_FILESYSTEM,
                              dest_is_symlink ? G_FILE_QUERY_INFO_NONE : G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                              job->cancellable,
                              &error);

    if (info == NULL)
    {
        if (IS_IO_ERROR (error, CANCELLED))
        {
            g_error_free (error);
            return;
        }

        primary = f (_("Error while copying to “%B”."), dest);
        details = NULL;

        if (IS_IO_ERROR (error, PERMISSION_DENIED))
        {
            secondary = f (_("You do not have permissions to access the destination folder."));
        }
        else
        {
            secondary = f (_("There was an error getting information about the destination."));
            details = error->message;
        }

        response = run_error (job,
                              primary,
                              secondary,
                              details,
                              FALSE,
                              CANCEL, RETRY,
                              NULL);

        g_error_free (error);

        if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT)
        {
            abort_job (job);
        }
        else if (response == 1)
        {
            goto retry;
        }
        else
        {
            g_assert_not_reached ();
        }

        return;
    }

    file_type = g_file_info_get_file_type (info);
    if (!dest_is_symlink && file_type == G_FILE_TYPE_SYMBOLIC_LINK)
    {
        /* Record that destination is a symlink and do real stat() once again */
        dest_is_symlink = TRUE;
        g_object_unref (info);
        goto retry;
    }

    if (dest_fs_id)
    {
        *dest_fs_id =
            g_strdup (g_file_info_get_attribute_string (info,
                                                        G_FILE_ATTRIBUTE_ID_FILESYSTEM));
    }

    g_object_unref (info);

    if (file_type != G_FILE_TYPE_DIRECTORY)
    {
        primary = f (_("Error while copying to “%B”."), dest);
        secondary = f (_("The destination is not a folder."));

        run_error (job,
                   primary,
                   secondary,
                   NULL,
                   FALSE,
                   CANCEL,
                   NULL);

        abort_job (job);
        return;
    }

    if (dest_is_symlink)
    {
        /* We can't reliably statfs() destination if it's a symlink, thus not doing any further checks. */
        return;
    }

    fsinfo = g_file_query_filesystem_info (dest,
                                           G_FILE_ATTRIBUTE_FILESYSTEM_FREE ","
                                           G_FILE_ATTRIBUTE_FILESYSTEM_READONLY,
                                           job->cancellable,
                                           NULL);
    if (fsinfo == NULL)
    {
        /* All sorts of things can go wrong getting the fs info (like not supported)
         * only check these things if the fs returns them
         */
        return;
    }

    if (required_size > 0 &&
        g_file_info_has_attribute (fsinfo, G_FILE_ATTRIBUTE_FILESYSTEM_FREE))
    {
        free_size = g_file_info_get_attribute_uint64 (fsinfo,
                                                      G_FILE_ATTRIBUTE_FILESYSTEM_FREE);

        if (free_size < required_size)
        {
            size_difference = required_size - free_size;
            primary = f (_("Error while copying to “%B”."), dest);
            secondary = f (_("There is not enough space on the destination. Try to remove files to make space."));

            details = f (_("%S more space is required to copy to the destination."), size_difference);

            response = run_warning (job,
                                    primary,
                                    secondary,
                                    details,
                                    FALSE,
                                    CANCEL,
                                    COPY_FORCE,
                                    RETRY,
                                    NULL);

            if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT)
            {
                abort_job (job);
            }
            else if (response == 2)
            {
                goto retry;
            }
            else if (response == 1)
            {
                /* We are forced to copy - just fall through ... */
            }
            else
            {
                g_assert_not_reached ();
            }
        }
    }

    if (!job_aborted (job) &&
        g_file_info_get_attribute_boolean (fsinfo,
                                           G_FILE_ATTRIBUTE_FILESYSTEM_READONLY))
    {
        primary = f (_("Error while copying to “%B”."), dest);
        secondary = f (_("The destination is read-only."));

        run_error (job,
                   primary,
                   secondary,
                   NULL,
                   FALSE,
                   CANCEL,
                   NULL);

        g_error_free (error);

        abort_job (job);
    }

    g_object_unref (fsinfo);
}

verify_destination (CommonJob  *job,
                    GFile      *dest,
                    char      **dest_fs_id,
                    goffset     required_size)
{
    GFileInfo *info, *fsinfo;
    GError *error;
    guint64 free_size;
    guint64 size_difference;
    char *primary, *secondary, *details;
    int response;
    GFileType file_type;
    gboolean dest_is_symlink = FALSE;

    if (dest_fs_id)
    {
        *dest_fs_id = NULL;
    }

retry:

    error = NULL;
    info = g_file_query_info (dest,
                              G_FILE_ATTRIBUTE_STANDARD_TYPE ","
                              G_FILE_ATTRIBUTE_ID_FILESYSTEM,
                              dest_is_symlink ? G_FILE_QUERY_INFO_NONE : G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
                              job->cancellable,
                              &error);

    if (info == NULL)
    {
        if (IS_IO_ERROR (error, CANCELLED))
        {
            g_error_free (error);
            return;
        }

        primary = f (_("Error while copying to “%B”."), dest);
        details = NULL;

        if (IS_IO_ERROR (error, PERMISSION_DENIED))
        {
            secondary = f (_("You do not have permissions to access the destination folder."));
        }
        else
        {
            secondary = f (_("There was an error getting information about the destination."));
            details = error->message;
        }

        response = run_error (job,
                              primary,
                              secondary,
                              details,
                              FALSE,
                              CANCEL, RETRY,
                              NULL);

        g_error_free (error);

        if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT)
        {
            abort_job (job);
        }
        else if (response == 1)
        {
            goto retry;
        }
        else
        {
            g_assert_not_reached ();
        }

        return;
    }

    file_type = g_file_info_get_file_type (info);
    if (!dest_is_symlink && file_type == G_FILE_TYPE_SYMBOLIC_LINK)
    {
        /* Record that destination is a symlink and do real stat() once again */
        dest_is_symlink = TRUE;
        g_object_unref (info);
        goto retry;
    }

    if (dest_fs_id)
    {
        *dest_fs_id =
            g_strdup (g_file_info_get_attribute_string (info,
                                                        G_FILE_ATTRIBUTE_ID_FILESYSTEM));
    }

    g_object_unref (info);

    if (file_type != G_FILE_TYPE_DIRECTORY)
    {
        primary = f (_("Error while copying to “%B”."), dest);
        secondary = f (_("The destination is not a folder."));

        run_error (job,
                   primary,
                   secondary,
                   NULL,
                   FALSE,
                   CANCEL,
                   NULL);

        abort_job (job);
        return;
    }

    if (dest_is_symlink)
    {
        /* We can't reliably statfs() destination if it's a symlink, thus not doing any further checks. */
        return;
    }

    fsinfo = g_file_query_filesystem_info (dest,
                                           G_FILE_ATTRIBUTE_FILESYSTEM_FREE ","
                                           G_FILE_ATTRIBUTE_FILESYSTEM_READONLY,
                                           job->cancellable,
                                           NULL);
    if (fsinfo == NULL)
    {
        /* All sorts of things can go wrong getting the fs info (like not supported)
         * only check these things if the fs returns them
         */
        return;
    }

    if (required_size > 0 &&
        g_file_info_has_attribute (fsinfo, G_FILE_ATTRIBUTE_FILESYSTEM_FREE))
    {
        free_size = g_file_info_get_attribute_uint64 (fsinfo,
                                                      G_FILE_ATTRIBUTE_FILESYSTEM_FREE);

        if (free_size < required_size)
        {
            size_difference = required_size - free_size;
            primary = f (_("Error while copying to “%B”."), dest);
            secondary = f (_("There is not enough space on the destination. Try to remove files to make space."));

            details = f (_("%S more space is required to copy to the destination."), size_difference);

            response = run_warning (job,
                                    primary,
                                    secondary,
                                    details,
                                    FALSE,
                                    CANCEL,
                                    COPY_FORCE,
                                    RETRY,
                                    NULL);

            if (response == 0 || response == GTK_RESPONSE_DELETE_EVENT)
            {
                abort_job (job);
            }
            else if (response == 2)
            {
                goto retry;
            }
            else if (response == 1)
            {
                /* We are forced to copy - just fall through ... */
            }
            else
            {
                g_assert_not_reached ();
            }
        }
    }

    if (!job_aborted (job) &&
        g_file_info_get_attribute_boolean (fsinfo,
                                           G_FILE_ATTRIBUTE_FILESYSTEM_READONLY))
    {
        primary = f (_("Error while copying to “%B”."), dest);
        secondary = f (_("The destination is read-only."));

        run_error (job,
                   primary,
                   secondary,
                   NULL,
                   FALSE,
                   CANCEL,
                   NULL);

        g_error_free (error);

        abort_job (job);
    }

    g_object_unref (fsinfo);
}
