~dblsaiko/desktoputils

4468db961b048e67dacc77a3082235094e51020e — Marco Rebhan 9 months ago a202f10 master
Add some MIME parsing stuff for HTTP(S) and local files
M CMakeLists.txt => CMakeLists.txt +23 -8
@@ 18,32 18,47 @@ include(KDECompilerSettings NO_POLICY_SCOPE)

find_package(PkgConfig REQUIRED)
pkg_check_modules(glib REQUIRED IMPORTED_TARGET glib-2.0)
pkg_check_modules(magic REQUIRED IMPORTED_TARGET libmagic)

find_package(CURL REQUIRED)

find_package(KF5 COMPONENTS Config)
set_package_properties(KF5::Config PROPERTIES TYPE OPTIONAL)
add_feature_info("KF5::Config" KF5CONFIG_FOUND "kconfig needed for reading KDE default terminal settings")

set(rundesktop_SRC
set(desktoputils_SRC
    src/desktop.c
    src/rundesktop.c
    src/desktoputils.c
    src/execparser.c
    src/argbuilder.c
    src/config.c)
    src/config.c
    src/mime.c
    src/url.c)

set(rundesktop_SRC
    src/rundesktop.c)

set(duopen_SRC
    src/duopen.c)

if(KF5CONFIG_FOUND)
    add_compile_definitions(WITH_KDE)

    set(rundesktop_SRC
        ${rundesktop_SRC}
    set(desktoputils_SRC
        ${desktoputils_SRC}
        src/kdeterminal.cpp)
endif()

add_library(desktoputils STATIC ${desktoputils_SRC})
add_executable(rundesktop ${rundesktop_SRC})
add_executable(duopen ${duopen_SRC})

target_link_libraries(rundesktop PkgConfig::glib)
target_link_libraries(desktoputils PRIVATE PkgConfig::glib PkgConfig::magic)
target_link_libraries(rundesktop desktoputils)
target_link_libraries(duopen desktoputils curl)

if(KF5CONFIG_FOUND)
    target_link_libraries(rundesktop KF5::ConfigCore)
    target_link_libraries(desktoputils PRIVATE KF5::ConfigCore)
endif()

target_include_directories(rundesktop PRIVATE ${CMAKE_BINARY_DIR})
target_include_directories(desktoputils PUBLIC ${CMAKE_BINARY_DIR})

A src/desktoputils.c => src/desktoputils.c +6 -0
@@ 0,0 1,6 @@
#include "desktoputils.h"

bool du_strleq(const char *a, const char *b, size_t b_len)
{
    return strlen(a) == b_len && strncmp(a, b, b_len) == 0;
}

M src/desktoputils.h => src/desktoputils.h +5 -0
@@ 2,6 2,9 @@
#define DESKTOPUTILS_H

#include <error.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include <sysexits.h>

#define EXPORT __attribute__((__visibility__("default")))


@@ 10,6 13,8 @@

#define UNREACHABLE() error_at_line(EX_SOFTWARE, 0, __FILE__, __LINE__, "unreachable")

bool du_strleq(const char *a, const char *b, size_t b_len);

#ifdef __clang__

#define UNSAFE_CAST_BEGIN _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wincompatible-pointer-types\"")

A src/duopen.c => src/duopen.c +67 -0
@@ 0,0 1,67 @@
#include "desktoputils.h"
#include "mime.h"
#include "url.h"
#include "ver.h"
#include <argp.h>
#include <assert.h>
#include <string.h>
#include <sysexits.h>

EXPORT const char *argp_program_version = "duopen " desktoputils_VERSION_STRING;
EXPORT const char *argp_program_bug_address = "Marco Rebhan <me@dblsaiko.net>";

char doc[] = "Opens a URL in a program associated with its contents";

struct argp_option options[] = {
    {0},
};

error_t parse_opt(int key, char *arg, struct argp_state *state);

struct argp argp = {options, parse_opt, "URL", doc, 0, 0, 0};

char *url;

int main(int argc, char **argv)
{
    argp_parse(&argp, argc, argv, 0, 0, 0);

    url_info_t u = url_parse(url);

    if (!u.is_url || du_strleq("file", u.scheme, u.scheme_len)) {
        const char *path = u.is_url ? u.path : url;
        const char *mime_type = mime_type_for_file(path);
        assert(mime_type);
        puts(mime_type);
    } else if (du_strleq("http", u.scheme, u.scheme_len) || du_strleq("https", u.scheme, u.scheme_len)) {
        const char *mime_type = mime_type_for_url(u.path);
        assert(mime_type);
        puts(mime_type);
    } else {
        error(EX_DATAERR, 0, "unhandled URL scheme: %.*s", (int)u.scheme_len, u.scheme);
    }
}

error_t parse_opt(int key, char *arg, struct argp_state *state)
{
    switch (key) {
    case ARGP_KEY_ARG:
        if (!url) {
            url = arg;
        } else {
            argp_failure(state, EX_USAGE, 0, "extra arguments");
        }

        break;
    case ARGP_KEY_END:
        if (!url) {
            argp_failure(state, EX_USAGE, 0, "missing URL");
        }

        break;
    default:
        return ARGP_ERR_UNKNOWN;
    }

    return 0;
}

M src/kdeterminal.cpp => src/kdeterminal.cpp +1 -2
@@ 8,8 8,7 @@ extern "C" const char *kde_selected_terminal()
    static char *preferred_terminal = nullptr;

    if (preferred_terminal) {
        delete[] preferred_terminal;
        preferred_terminal = nullptr;
        return preferred_terminal;
    }

    KConfigGroup conf_group(KSharedConfig::openConfig(), QStringLiteral("General"));

M src/kdeterminal.h => src/kdeterminal.h +1 -1
@@ 5,7 5,7 @@
extern "C" {
#endif

const char *kde_selected_terminal();
const char *kde_selected_terminal(void);

#ifdef __cplusplus
}

A src/mime.c => src/mime.c +75 -0
@@ 0,0 1,75 @@
#include "mime.h"
#include <assert.h>
#include <curl/curl.h>
#include <error.h>
#include <magic.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>

const char *mime_type_for_file(const char *path)
{
    static magic_t magic = NULL;

    if (!magic) {
        magic = magic_open(MAGIC_MIME_TYPE);
        magic_load(magic, NULL);
    }

    return magic_file(magic, path);
}

const char *mime_type_for_url(const char *url)
{
    static bool i_init = false;

    if (!i_init) {
        CURLcode res = curl_global_init(0);

        if (res != CURLE_OK) {
            error(EX_SOFTWARE, 0, "cURL error during init: %d", res);
        }

        i_init = true;
    }

    CURL *curl = curl_easy_init();
    assert(curl); // hopefully, this should always give us a thingy

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_NOBODY, 1); // HEAD request
    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);

    CURLcode res = curl_easy_perform(curl);

    if (res != CURLE_OK) {
        error(EX_SOFTWARE, 0, "cURL error: %d", res);
    }

    long response_code;
    curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);

    static char *content_type_out = NULL;

    if (response_code != 200) {
        curl_easy_cleanup(curl);
        return NULL;
    }

    char *content_type = NULL;
    curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &content_type);

    if (content_type) {
        size_t size = strlen(content_type) + 1;
        content_type_out = realloc(content_type_out, size);
        memcpy(content_type_out, content_type, size);

        curl_easy_cleanup(curl);
        return content_type_out;
    } else {
        curl_easy_cleanup(curl);
        return "application/octet-stream";
    }
}

A src/mime.h => src/mime.h +8 -0
@@ 0,0 1,8 @@
#ifndef MIME_H
#define MIME_H

const char *mime_type_for_file(const char *path);

const char *mime_type_for_url(const char *url);

#endif

M src/rundesktop.c => src/rundesktop.c +2 -2
@@ 237,13 237,13 @@ error_t parse_opt(int key, char *arg, struct argp_state *state)
            files_len += 1;
        } else {
            // we should never hit this but just in case something weird happens
            argp_failure(state, 1, 0, "too many arguments");
            argp_failure(state, EX_USAGE, 0, "too many arguments");
        }

        break;
    case ARGP_KEY_END:
        if (!desktop_file) {
            argp_failure(state, 1, 0, "missing desktop file");
            argp_failure(state, EX_USAGE, 0, "missing desktop file");
        }

        break;

A src/url.c => src/url.c +70 -0
@@ 0,0 1,70 @@
#include "url.h"
#include <ctype.h>
#include <string.h>

url_info_t url_parse(const char *str)
{
    url_info_t result = {0};

    // search for end of scheme
    size_t pos = 0;

    while (true) {
        if (!str[pos]) {
            // reached the end of the string without ':' found -> not a URL
            return result;
        } else if (str[pos] == ':') {
            if (pos == 0) {
                // scheme of length 0 is invalid
                return result;
            } else {
                break;
            }
        } else if (!isalnum(str[pos])) {
            // accept only alphanumeric chars in the scheme
            return result;
        }

        pos += 1;
    }

    // now we have a valid scheme in str[..pos], we don't really care about the
    // actual path since we just pass it off to either curl or the fs so nothing
    // needs to be parsed there

    result.scheme = str;
    result.scheme_len = pos;
    pos += 1;

    if (du_strleq("file", result.scheme, result.scheme_len)) {
        // file URLs are special since they don't need to start with file://,
        // instead file:/xyz.txt refers to /xyz.txt, but file://host/xyz.txt
        // refers to xyz.txt on host (which can also be empty for a local path)

        if (strncmp("//", str + pos, 2) == 0) {
            if (str[pos + 2] == '/') {
                // we have 'file:///' which is an absolute path (so keep the
                // third '/'!)
                pos += 2;
            } else {
                // this is a remote url. poo! keep it as '//host/path' for now,
                // like an SMB path
            }
        } else {
            // we have 'file:relpath' or 'file:/abspath', don't strip anything
        }
    } else {
        // other URLs always have to have the '//'

        if (strncmp("//", str + pos, 2) != 0) {
            return result;
        }

        pos += 2;
    }

    result.path = str + pos;

    result.is_url = true;
    return result;
}

A src/url.h => src/url.h +17 -0
@@ 0,0 1,17 @@
#ifndef URL_H
#define URL_H

#include "desktoputils.h"
#include <stdbool.h>
#include <stddef.h>

typedef struct url_info {
    bool is_url;
    const char *scheme;
    size_t scheme_len;
    const char *path;
} url_info_t;

EXPORT url_info_t url_parse(const char *str);

#endif