diff options
| author | jgmdev <jgmdev@gmail.com> | 2023-05-23 17:02:57 -0400 |
|---|---|---|
| committer | jgmdev <jgmdev@gmail.com> | 2024-03-13 15:04:16 -0400 |
| commit | 1b804dca9db26013d1522a27b24fdb58c6e14b75 (patch) | |
| tree | 61cf31b7df179b20cda18d9eee04952bcde6e88a /src/ppm.c | |
| parent | f677376167b401b2c8f9154ac30fcd21b8aff238 (diff) | |
| download | pragtical-plugin-manager-1.2.4.tar.gz pragtical-plugin-manager-1.2.4.zip | |
Diffstat (limited to 'src/ppm.c')
| -rw-r--r-- | src/ppm.c | 1507 |
1 files changed, 1507 insertions, 0 deletions
diff --git a/src/ppm.c b/src/ppm.c new file mode 100644 index 0000000..305f502 --- /dev/null +++ b/src/ppm.c @@ -0,0 +1,1507 @@ +#ifdef _WIN32 + #include <direct.h> + #include <winsock2.h> + #include <windows.h> + #include <fileapi.h> +#else + #include <netdb.h> + #include <sys/socket.h> + #include <sys/ioctl.h> + #include <arpa/inet.h> + #include <libgen.h> + #include <termios.h> + + #define MAX_PATH PATH_MAX +#endif + +#include <git2.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <lua.h> +#include <lualib.h> +#include <lauxlib.h> +#include <ctype.h> +#include <dirent.h> +#include <unistd.h> +#include <fcntl.h> + +#include <sys/stat.h> +#include <sys/file.h> +#include <git2.h> +#include <mbedtls/sha256.h> +#include <mbedtls/x509.h> +#include <mbedtls/entropy.h> +#include <mbedtls/ctr_drbg.h> +#include <mbedtls/ssl.h> +#include <mbedtls/error.h> +#include <mbedtls/net.h> +#ifdef MBEDTLS_DEBUG_C + #include <mbedtls/debug.h> +#endif + +#include <zlib.h> +#include <microtar.h> +#include <zip.h> + +#ifdef __APPLE__ + #include <Security/Security.h> +#endif + +#define HTTPS_RESPONSE_HEADER_BUFFER_LENGTH 8192 + + +#if _WIN32 +static LPCWSTR lua_toutf16(lua_State* L, const char* str) { + if (str && str[0] == 0) + return L""; + int len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0); + if (len > 0) { + LPWSTR output = (LPWSTR) malloc(sizeof(WCHAR) * len); + if (output) { + len = MultiByteToWideChar(CP_UTF8, 0, str, -1, output, len); + if (len > 0) { + lua_pushlstring(L, (char*)output, len * 2); + free(output); + return (LPCWSTR)lua_tostring(L, -1); + } + free(output); + } + } + luaL_error(L, "can't convert utf8 string"); + return NULL; +} + +static const char* lua_toutf8(lua_State* L, LPCWSTR str) { + int len = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL); + if (len > 0) { + char* output = (char *) malloc(sizeof(char) * len); + if (output) { + len = WideCharToMultiByte(CP_UTF8, 0, str, -1, output, len, NULL, NULL); + if (len) { + lua_pushlstring(L, output, len - 1); + free(output); + return lua_tostring(L, -1); + } + free(output); + } + } + luaL_error(L, "can't convert utf16 string"); + return NULL; +} + +static const int luaL_win32_error(lua_State* L, DWORD error_id, const char* message, ...) { + va_list va; + va_start(va, message); + lua_pushvfstring(L, message, va); + va_end(va); + wchar_t message_buffer[2048]; + size_t size = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, error_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), message_buffer, 2048, NULL); + lua_pushliteral(L, ": "); + lua_toutf8(L, message_buffer); + lua_concat(L, 3); + return lua_error(L); +} +#endif + +static FILE* lua_fopen(lua_State* L, const char* path, const char* mode) { + #ifdef _WIN32 + FILE* file = _wfopen(lua_toutf16(L, path), lua_toutf16(L, mode)); + lua_pop(L, 2); + return file; + #else + return fopen(path, mode); + #endif +} + +static char hex_digits[] = "0123456789abcdef"; +static void lua_pushhexstring(lua_State* L, const unsigned char* buffer, size_t length) { + char hex_buffer[length * 2 + 1]; + for (size_t i = 0; i < length; ++i) { + hex_buffer[i*2+0] = hex_digits[buffer[i] >> 4]; + hex_buffer[i*2+1] = hex_digits[buffer[i] & 0xF]; + } + lua_pushlstring(L, hex_buffer, length * 2); +} + +static int ppm_hash(lua_State* L) { + size_t len; + const char* data = luaL_checklstring(L, 1, &len); + const char* type = luaL_optstring(L, 2, "string"); + static const int digest_length = 32; + unsigned char buffer[digest_length]; + mbedtls_sha256_context hash_ctx; + mbedtls_sha256_init(&hash_ctx); + mbedtls_sha256_starts_ret(&hash_ctx, 0); + if (strcmp(type, "file") == 0) { + FILE* file = lua_fopen(L, data, "rb"); + if (!file) { + mbedtls_sha256_free(&hash_ctx); + return luaL_error(L, "can't open %s", data); + } + while (1) { + unsigned char chunk[4096]; + size_t bytes = fread(chunk, 1, sizeof(chunk), file); + mbedtls_sha256_update_ret(&hash_ctx, chunk, bytes); + if (bytes < sizeof(chunk)) + break; + } + fclose(file); + } else { + mbedtls_sha256_update_ret(&hash_ctx, data, len); + } + mbedtls_sha256_finish_ret(&hash_ctx, buffer); + mbedtls_sha256_free(&hash_ctx); + lua_pushhexstring(L, buffer, digest_length); + return 1; +} + +static int ppm_tcflush(lua_State* L) { + int stream = luaL_checkinteger(L, 1); + #ifndef _WIN32 + if (isatty(stream)) + tcflush(stream, TCIOFLUSH); + #endif + return 0; +} + +static int ppm_tcwidth(lua_State* L) { + int stream = luaL_checkinteger(L, 1); + #ifndef _WIN32 + if (isatty(stream)) { + struct winsize ws={0}; + ioctl(stream, TIOCGWINSZ, &ws); + lua_pushinteger(L, ws.ws_col); + return 1; + } + #else + CONSOLE_SCREEN_BUFFER_INFO csbi; + int columns, rows; + if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) { + lua_pushinteger(L, csbi.srWindow.Right - csbi.srWindow.Left + 1); + return 1; + } + #endif + return 0; +} + +static int ppm_symlink(lua_State* L) { + #ifndef _WIN32 + if (symlink(luaL_checkstring(L, 1), luaL_checkstring(L, 2))) + return luaL_error(L, "can't create symlink %s: %s", luaL_checkstring(L, 2), strerror(errno)); + return 0; + #else + return luaL_error(L, "can't create symbolic link %s: your operating system sucks", luaL_checkstring(L, 2)); + #endif +} + +static int ppm_chmod(lua_State* L) { + #ifdef _WIN32 + if (_wchmod(lua_toutf16(L, luaL_checkstring(L, 1)), luaL_checkinteger(L, 2))) + #else + if (chmod(luaL_checkstring(L, 1), luaL_checkinteger(L, 2))) + #endif + return luaL_error(L, "can't chmod %s: %s", luaL_checkstring(L, 1), strerror(errno)); + return 0; +} + +static int ppm_ls(lua_State *L) { + const char *path = luaL_checkstring(L, 1); + int i = 1; +#ifdef _WIN32 + lua_settop(L, 1); + lua_pushstring(L, path[0] == 0 || strpbrk(&path[strlen(path) - 1], "\\/") != NULL ? "*" : "\\*"); + lua_concat(L, 2); + path = lua_tostring(L, -1); + + WIN32_FIND_DATAW fd; + HANDLE find_handle = FindFirstFileExW(lua_toutf16(L, path), FindExInfoBasic, &fd, FindExSearchNameMatch, NULL, 0); + if (find_handle == INVALID_HANDLE_VALUE) + return luaL_win32_error(L, GetLastError(), "can't ls %s", path); + lua_newtable(L); + + do { + const char* filename = lua_toutf8(L, fd.cFileName); + if (strcmp(filename, ".") != 0 && strcmp(filename, "..") != 0) { + lua_rawseti(L, -2, i++); + } else + lua_pop(L, 1); + } while (FindNextFileW(find_handle, &fd)); + int err = GetLastError(); + FindClose(find_handle); + if (err != ERROR_NO_MORE_FILES) + return luaL_win32_error(L, err, "can't ls %s", path); +#else + DIR *dir = opendir(path); + if (!dir) + return luaL_error(L, "can't ls %s: %s", path, strerror(errno)); + lua_newtable(L); + struct dirent *entry; + while ( (entry = readdir(dir)) ) { + if (strcmp(entry->d_name, "." ) == 0) { continue; } + if (strcmp(entry->d_name, "..") == 0) { continue; } + lua_pushstring(L, entry->d_name); + lua_rawseti(L, -2, i++); + } + closedir(dir); +#endif + return 1; +} + +static int ppm_rmdir(lua_State *L) { + const char *path = luaL_checkstring(L, 1); +#ifdef _WIN32 + if (!RemoveDirectoryW(lua_toutf16(L, path))) + return luaL_win32_error(L, GetLastError(), "can't rmdir %s", path); +#else + if (remove(path)) + return luaL_error(L, "can't rmdir %s: %s", path, strerror(errno)); +#endif + return 0; +} + +static int ppm_mkdir(lua_State *L) { + const char *path = luaL_checkstring(L, 1); +#ifdef _WIN32 + int err = _wmkdir(lua_toutf16(L, path)); +#else + int err = mkdir(path, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); +#endif + if (err < 0) + return luaL_error(L, "can't mkdir %s: %s", path, strerror(errno)); + return 0; +} + +static int ppm_stat(lua_State *L) { + const char *path = luaL_checkstring(L, 1); +#ifdef _WIN32 + wchar_t full_path[MAX_PATH]; + struct _stat s; + LPCWSTR wpath = lua_toutf16(L, path); + int err = _wstat(wpath, &s); + const char *abs_path = !err && _wfullpath(full_path, wpath, MAX_PATH) ? lua_toutf8(L, (LPCWSTR)full_path) : NULL; +#else + char full_path[MAX_PATH]; + struct stat s; + int err = lstat(path, &s); + const char *abs_path = NULL; + if (!err) { + if (S_ISLNK(s.st_mode)) { + char folder_path[MAX_PATH]; + strcpy(folder_path, path); + abs_path = realpath(dirname(folder_path), full_path); + if (abs_path) { + strcat(full_path, "/"); + strcpy(folder_path, path); + strcat(full_path, basename(folder_path)); + } + } else + abs_path = realpath(path, full_path); + } +#endif + if (err || !abs_path) { + lua_pushnil(L); + lua_pushstring(L, strerror(errno)); + return 2; + } + lua_newtable(L); + lua_pushstring(L, abs_path); lua_setfield(L, -2, "abs_path"); + lua_pushvalue(L, 1); lua_setfield(L, -2, "path"); + +#if __linux__ + if (S_ISLNK(s.st_mode)) { + char buffer[PATH_MAX]; + ssize_t len = readlink(path, buffer, sizeof(buffer)); + if (len < 0) + return 0; + lua_pushlstring(L, buffer, len); + } else + lua_pushnil(L); + lua_setfield(L, -2, "symlink"); + if (S_ISLNK(s.st_mode)) + err = stat(path, &s); + if (err) + return 1; +#endif + lua_pushinteger(L, s.st_mtime); lua_setfield(L, -2, "modified"); + lua_pushinteger(L, s.st_size); lua_setfield(L, -2, "size"); + lua_pushinteger(L, s.st_mode); lua_setfield(L, -2, "mode"); + if (S_ISREG(s.st_mode)) { + lua_pushstring(L, "file"); + } else if (S_ISDIR(s.st_mode)) { + lua_pushstring(L, "dir"); + } else { + lua_pushnil(L); + } + lua_setfield(L, -2, "type"); + return 1; +} +/** END STOLEN PRAGTICAL CODE **/ + +static const char* git_error_last_string() { + const git_error* last_error = git_error_last(); + return last_error->message; +} + +static int git_get_id(git_oid* commit_id, git_repository* repository, const char* name) { + int length = strlen(name); + int is_hex = 1; + for (int i = 0; is_hex && i < length; ++i) + is_hex = isxdigit(name[i]); + if (!is_hex || length < 3) + return git_reference_name_to_id(commit_id, repository, name); + if (length < GIT_OID_SHA1_SIZE*2) { + if (length % 2 != 0) + return -1; + git_commit* commit; + git_oid oid = {0}; + for (int i = 0; i < length/2; ++i) + oid.id[i] |= (name[i] - '0') << ((i % 2) * 4); + int ret = git_commit_lookup_prefix(&commit, repository, &oid, length/2); + if (ret) + return ret; + git_oid_cpy(commit_id, git_commit_id(commit)); + git_commit_free(commit); + return 0; + } + return git_oid_fromstr(commit_id, name); +} + +static git_repository* luaL_checkgitrepo(lua_State* L, int index) { + const char* path = luaL_checkstring(L, index); + git_repository* repository; + if (git_repository_open(&repository, path)) + return (void*)(long long)luaL_error(L, "git open error: %s", git_error_last_string()); + return repository; +} + + +static git_commit* git_retrieve_commit(git_repository* repository, const char* commit_name) { + git_oid commit_id; + git_commit* commit; + if (git_get_id(&commit_id, repository, commit_name) || git_commit_lookup(&commit, repository, &commit_id)) + return NULL; + return commit; +} + +// We move this out of main, because this is a significantly expensive function, +// and we don't need to call it every time we run ppm. +static int git_initialized = 0; +static int git_cert_type = 0; +static char git_cert_path[MAX_PATH]; +static void git_init() { + if (!git_initialized) { + git_libgit2_init(); + if (git_cert_type) + git_libgit2_opts(GIT_OPT_SET_SSL_CERT_LOCATIONS, git_cert_type == 2 ? git_cert_path : NULL, git_cert_type == 1 ? git_cert_path : NULL); + git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, "."); + git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, "."); + git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, "."); + git_initialized = 1; + } +} + + +static int ppm_reset(lua_State* L) { + git_init(); + git_repository* repository = luaL_checkgitrepo(L, 1); + const char* commit_name = luaL_checkstring(L, 2); + const char* type = luaL_checkstring(L, 3); + git_commit* commit = git_retrieve_commit(repository, commit_name); + if (!commit) { + git_repository_free(repository); + return luaL_error(L, "git retrieve commit error: %s", git_error_last_string()); + } + git_reset_t reset_type = GIT_RESET_SOFT; + if (strcmp(type, "mixed") == 0) + reset_type = GIT_RESET_MIXED; + else if (strcmp(type, "hard") == 0) + reset_type = GIT_RESET_HARD; + int result = git_reset(repository, (git_object*)commit, reset_type, NULL); + git_commit_free(commit); + git_repository_free(repository); + if (result) + return luaL_error(L, "git reset error: %s", git_error_last_string()); + return 0; +} + + +static int ppm_init(lua_State* L) { + git_init(); + const char* path = luaL_checkstring(L, 1); + const char* url = luaL_checkstring(L, 2); + git_repository* repository; + if (git_repository_init(&repository, path, 0)) + return luaL_error(L, "git init error: %s", git_error_last_string()); + git_remote* remote; + if (git_remote_create(&remote, repository, "origin", url)) { + git_repository_free(repository); + return luaL_error(L, "git remote add error: %s", git_error_last_string()); + } + git_remote_free(remote); + git_repository_free(repository); + return 0; +} + +static int no_verify_ssl, has_setup_ssl, print_trace; +static mbedtls_x509_crt x509_certificate; +static mbedtls_entropy_context entropy_context; +static mbedtls_ctr_drbg_context drbg_context; +static mbedtls_ssl_config ssl_config; +static mbedtls_ssl_context ssl_context; + +static int ppm_git_transport_certificate_check_cb(struct git_cert *cert, int valid, const char *host, void *payload) { + return 0; // If no_verify_ssl is enabled, basically always return 0 when this is set as callback. +} + +static int ppm_git_transfer_progress_cb(const git_transfer_progress *stats, void *payload) { + lua_State* L = payload; + lua_pushvalue(L, 2); + lua_pushinteger(L, stats->received_bytes); + lua_pushinteger(L, stats->total_objects); + lua_pushinteger(L, stats->indexed_objects); + lua_pushinteger(L, stats->received_objects); + lua_pushinteger(L, stats->local_objects); + lua_pushinteger(L, stats->total_deltas); + lua_pushinteger(L, stats->indexed_deltas); + lua_call(L, 7, 1); + int value = lua_tointeger(L, -1); + lua_pop(L, 1); + return value; +} + +static int ppm_fetch(lua_State* L) { + git_init(); + git_repository* repository = luaL_checkgitrepo(L, 1); + git_remote* remote; + if (git_remote_lookup(&remote, repository, "origin")) { + git_repository_free(repository); + return luaL_error(L, "git remote fetch error: %s", git_error_last_string()); + } + const char* refspec = lua_gettop(L) >= 3 ? luaL_checkstring(L, 3) : NULL; + git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT; + fetch_opts.download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL; + fetch_opts.callbacks.payload = L; + #if (LIBGIT2_VER_MAJOR == 1 && LIBGIT2_VER_MINOR >= 7) || LIBGIT2_VER_MAJOR > 1 + fetch_opts.depth = 1; + #endif + if (no_verify_ssl) + fetch_opts.callbacks.certificate_check = ppm_git_transport_certificate_check_cb; + if (lua_type(L, 2) == LUA_TFUNCTION) + fetch_opts.callbacks.transfer_progress = ppm_git_transfer_progress_cb; + git_strarray array = { (char**)&refspec, 1 }; + int error = git_remote_connect(remote, GIT_DIRECTION_FETCH, &fetch_opts.callbacks, NULL, NULL) || + git_remote_download(remote, refspec ? &array : NULL, &fetch_opts) || + git_remote_update_tips(remote, &fetch_opts.callbacks, fetch_opts.update_fetchhead, fetch_opts.download_tags, NULL); + if (!error) { + git_buf branch_name = {0}; + if (!git_remote_default_branch(&branch_name, remote)) { + lua_pushlstring(L, branch_name.ptr, branch_name.size); + git_buf_dispose(&branch_name); + } else { + lua_pushnil(L); + } + } + git_remote_disconnect(remote); + git_remote_free(remote); + git_repository_free(repository); + if (error) + return luaL_error(L, "git remote fetch error: %s", git_error_last_string()); + if (lua_type(L, 2) == LUA_TFUNCTION) { + lua_pushvalue(L, 2); + lua_pushboolean(L, 1); + lua_pushvalue(L, -3); + lua_call(L, 2, 0); + } + return 1; +} + +static int mbedtls_snprintf(int mbedtls, char* buffer, int len, int status, const char* str, ...) { + char mbed_buffer[256]; + mbedtls_strerror(status, mbed_buffer, sizeof(mbed_buffer)); + int error_len = mbedtls ? strlen(mbed_buffer) : strlen(strerror(status)); + va_list va; + int offset = 0; + va_start(va, str); + offset = vsnprintf(buffer, len, str, va); + va_end(va); + if (offset < len - 2) { + strcat(buffer, ": "); + if (offset < len - error_len - 2) + strcat(buffer, mbedtls ? mbed_buffer : strerror(status)); + } + return strlen(buffer); +} + +static int luaL_mbedtls_error(lua_State* L, int code, const char* str, ...) { + char vsnbuffer[1024]; + char mbed_buffer[128]; + mbedtls_strerror(code, mbed_buffer, sizeof(mbed_buffer)); + va_list va; + va_start(va, str); + vsnprintf(vsnbuffer, sizeof(vsnbuffer), str, va); + va_end(va); + return luaL_error(L, "%s: %s", vsnbuffer, mbed_buffer); +} + +static void ppm_tls_debug(void *ctx, int level, const char *file, int line, const char *str) { + fprintf(stderr, "%s:%04d: |%d| %s", file, line, level, str); + fflush(stderr); +} + +static void ppm_libgit2_debug(git_trace_level_t level, const char *msg) { + fprintf(stderr, "[libgit2]: %s\n", msg); + fflush(stderr); +} + +static int ppm_trace(lua_State* L) { + print_trace = lua_toboolean(L, 1) ? 1 : 0; + return 0; +} + +static int ppm_certs(lua_State* L) { + const char* type = luaL_checkstring(L, 1); + int status; + if (has_setup_ssl) { + mbedtls_ssl_config_free(&ssl_config); + mbedtls_ctr_drbg_free(&drbg_context); + mbedtls_entropy_free(&entropy_context); + mbedtls_x509_crt_free(&x509_certificate); + } + mbedtls_x509_crt_init(&x509_certificate); + mbedtls_entropy_init(&entropy_context); + mbedtls_ctr_drbg_init(&drbg_context); + if ((status = mbedtls_ctr_drbg_seed(&drbg_context, mbedtls_entropy_func, &entropy_context, NULL, 0)) != 0) + return luaL_mbedtls_error(L, status, "failed to setup mbedtls_x509"); + mbedtls_ssl_config_init(&ssl_config); + status = mbedtls_ssl_config_defaults(&ssl_config, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT); + if (status) + return luaL_mbedtls_error(L, status, "can't set ssl_config defaults"); + mbedtls_ssl_conf_max_version(&ssl_config, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3); + mbedtls_ssl_conf_min_version(&ssl_config, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3); + mbedtls_ssl_conf_authmode(&ssl_config, MBEDTLS_SSL_VERIFY_REQUIRED); + mbedtls_ssl_conf_rng(&ssl_config, mbedtls_ctr_drbg_random, &drbg_context); + mbedtls_ssl_conf_read_timeout(&ssl_config, 5000); + #if defined(MBEDTLS_DEBUG_C) + if (print_trace) { + mbedtls_debug_set_threshold(5); + mbedtls_ssl_conf_dbg(&ssl_config, ppm_tls_debug, NULL); + git_init(); + git_trace_set(GIT_TRACE_TRACE, ppm_libgit2_debug); + } + #endif + has_setup_ssl = 1; + if (strcmp(type, "noverify") == 0) { + no_verify_ssl = 1; + mbedtls_ssl_conf_authmode(&ssl_config, MBEDTLS_SSL_VERIFY_OPTIONAL); + if (print_trace) { + fprintf(stderr, "[ssl] SSL verify set to optional.\n"); + fflush(stderr); + } + } else { + const char* path = luaL_checkstring(L, 2); + if (strcmp(type, "dir") == 0) { + git_cert_type = 1; + if (git_initialized) + git_libgit2_opts(GIT_OPT_SET_SSL_CERT_LOCATIONS, NULL, path); + strncpy(git_cert_path, path, MAX_PATH); + status = mbedtls_x509_crt_parse_path(&x509_certificate, path); + if (status < 0) + return luaL_mbedtls_error(L, status, "mbedtls_x509_crt_parse_path failed to parse all CA certificates in %s", path); + if (status > 0 && print_trace) { + fprintf(stderr, "[ssl] mbedtls_x509_crt_parse_path on %s failed to parse %d certificates, but still succeeded.\n", path, status); + fflush(stderr); + } + mbedtls_ssl_conf_ca_chain(&ssl_config, &x509_certificate, NULL); + if (print_trace) { + fprintf(stderr, "[ssl] SSL directory set to %s.\n", git_cert_path); + fflush(stderr); + } + } else { + if (strcmp(type, "system") == 0) { + #if _WIN32 + FILE* file = lua_fopen(L, path, "wb"); + if (!file) + return luaL_error(L, "can't open cert store %s for writing: %s", path, strerror(errno)); + HCERTSTORE hSystemStore = CertOpenSystemStore(0, TEXT("ROOT")); + if (!hSystemStore) { + fclose(file); + return luaL_error(L, "error getting system certificate store"); + } + PCCERT_CONTEXT pCertContext = NULL; + while (1) { + pCertContext = CertEnumCertificatesInStore(hSystemStore, pCertContext); + if (!pCertContext) + break; + BYTE keyUsage[2]; + if (pCertContext->dwCertEncodingType & X509_ASN_ENCODING && (CertGetIntendedKeyUsage(pCertContext->dwCertEncodingType, pCertContext->pCertInfo, keyUsage, sizeof(keyUsage)) && (keyUsage[0] & CERT_KEY_CERT_SIGN_KEY_USAGE))) { + DWORD size = 0; + CryptBinaryToString(pCertContext->pbCertEncoded, pCertContext->cbCertEncoded, CRYPT_STRING_BASE64HEADER, NULL, &size); + char* buffer = malloc(size); + CryptBinaryToString(pCertContext->pbCertEncoded, pCertContext->cbCertEncoded, CRYPT_STRING_BASE64HEADER, buffer, &size); + fwrite(buffer, sizeof(char), size, file); + free(buffer); + } + } + fclose(file); + CertCloseStore(hSystemStore, 0); + if (print_trace) { + fprintf(stderr, "[ssl] SSL file pulled from system store and written to %s.\n", path); + fflush(stderr); + } + #elif __APPLE__ // https://developer.apple.com/forums/thread/691009; see also curl's mac version + return luaL_error(L, "can't use system on mac yet"); + #else + return luaL_error(L, "can't use system certificates except on windows or mac"); + #endif + } + git_cert_type = 2; + if (git_initialized) + git_libgit2_opts(GIT_OPT_SET_SSL_CERT_LOCATIONS, path, NULL); + strncpy(git_cert_path, path, MAX_PATH); + status = mbedtls_x509_crt_parse_file(&x509_certificate, path); + if (status < 0) + return luaL_mbedtls_error(L, status, "mbedtls_x509_crt_parse_file failed to parse CA certificate %s", path); + if (status > 0 && print_trace) { + fprintf(stderr, "[ssl] mbedtls_x509_crt_parse_file on %s failed to parse %d certificates, but still still succeeded.\n", path, status); + fflush(stderr); + } + mbedtls_ssl_conf_ca_chain(&ssl_config, &x509_certificate, NULL); + if (print_trace) { + fprintf(stderr, "[ssl] SSL file set to %s.\n", path); + fflush(stderr); + } + } + } + return 0; +} + + +static int mkdirp(char* path, int len) { + for (int i = 0; i < len; ++i) { + if (path[i] == '/' && i > 0) { + path[i] = 0; + #ifndef _WIN32 + if (mkdir(path, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) && errno != EEXIST) + #else + if (mkdir(path) && errno != EEXIST) + #endif + return -1; + path[i] = '/'; + } + } + return 0; +} + +#define FA_RDONLY 0x01 // FILE_ATTRIBUTE_READONLY +#define FA_DIREC 0x10 // FILE_ATTRIBUTE_DIRECTORY + +static int ppm_extract(lua_State* L) { + const char* src = luaL_checkstring(L, 1); + const char* dst = luaL_checkstring(L, 2); + + if (strstr(src, ".zip")) { + int zip_error_code; + zip_t* archive = zip_open(src, ZIP_RDONLY, &zip_error_code); + + if (!archive) { + zip_error_t zip_error; + zip_error_init_with_code(&zip_error, zip_error_code); + lua_pushfstring(L, "can't open zip archive %s: %s", src, zip_error_strerror(&zip_error)); + zip_error_fini(&zip_error); + return lua_error(L); + } + + zip_int64_t entries = zip_get_num_entries(archive, 0); + for (zip_int64_t i = 0; i < entries; ++i) { + zip_file_t* zip_file = zip_fopen_index(archive, i, 0); + const char* zip_name = zip_get_name(archive, i, ZIP_FL_ENC_GUESS); + + if (!zip_file) { + lua_pushfstring(L, "can't read zip archive file %s: %s", zip_name, zip_strerror(archive)); + zip_close(archive); + return lua_error(L); + } + + char target[MAX_PATH]; + int target_length = snprintf(target, sizeof(target), "%s/%s", dst, zip_name); + + if (mkdirp(target, target_length)) { + zip_fclose(zip_file); + zip_close(archive); + return luaL_error(L, "can't extract zip archive file %s, can't create directory %s: %s", src, target, strerror(errno)); + } + + if (target[target_length-1] != '/') { + FILE* file = lua_fopen(L, target, "wb"); + if (!file) { + zip_fclose(zip_file); + zip_close(archive); + return luaL_error(L, "can't write file %s: %s", target, strerror(errno)); + } + + mode_t m = S_IRUSR | S_IRGRP | S_IROTH; + zip_uint8_t os; + zip_uint32_t attr; + + zip_file_get_external_attributes(archive, i, 0, &os, &attr); + if (os == ZIP_OPSYS_DOS) { + if (0 == (attr & FA_RDONLY)) + m |= S_IWUSR | S_IWGRP | S_IWOTH; + if (attr & FA_DIREC) + m = (S_IFDIR | (m & ~S_IFMT)) | S_IXUSR | S_IXGRP | S_IXOTH; + } else { + m = (attr >> 16); + } + + if (chmod(target, m)) { + zip_fclose(zip_file); + zip_close(archive); + return luaL_error(L, "can't chmod file %s: %s", target, strerror(errno)); + } + + while (1) { + char buffer[8192]; + zip_int64_t length = zip_fread(zip_file, buffer, sizeof(buffer)); + + if (length == -1) { + lua_pushfstring(L, "can't read zip archive file %s: %s", zip_name, zip_file_strerror(zip_file)); + zip_fclose(zip_file); + zip_close(archive); + return lua_error(L); + } + + if (length == 0) break; + fwrite(buffer, sizeof(char), length, file); + } + + fclose(file); + } + zip_fclose(zip_file); + } + zip_close(archive); + } + + else { + char actual_src[PATH_MAX]; + + if (strstr(src, ".gz") || strstr(src, ".tgz")) { + gzFile gzfile = gzopen(src, "rb"); + + if (!gzfile) + return luaL_error(L, "can't open tar.gz archive %s: %s", src, strerror(errno)); + + char buffer[8192]; + int len = strlen(src) - 3; + + if (strstr(src, ".tar")) + strncpy(actual_src, src, len < PATH_MAX ? len : PATH_MAX); + else if (strstr(src, ".tgz")) { + strncpy(actual_src, src, len < PATH_MAX ? len : PATH_MAX); + strcat(actual_src, "tar"); + len = strlen(src); + } + else{ + strcpy(actual_src, dst); + } + + actual_src[len] = 0; + FILE* file = lua_fopen(L, actual_src, "wb"); + + if (!file) { + gzclose(gzfile); + return luaL_error(L, "can't open %s for writing: %s", actual_src, strerror(errno)); + } + + while (1) { + int length = gzread(gzfile, buffer, sizeof(buffer)); + if (length == 0) + break; + fwrite(buffer, sizeof(char), length, file); + } + + char error[128]; + error[0] = 0; + if (!gzeof(gzfile)) { + int error_number; + strncpy(error, gzerror(gzfile, &error_number), sizeof(error)); + error[sizeof(error)-1] = 0; + } + + fclose(file); + gzclose(gzfile); + + if (error[0]) + return luaL_error(L, "can't unzip gzip archive %s: %s", src, error); + + } else + strcpy(actual_src, src); + + if (strstr(src, ".tar") || strstr(src, ".tgz")) { + /* It's incredibly slow to do it this way, probably because of all the seeking. + For now, just gunzip the whole file at once, and then untar it. + tar.read = gzip_read; + tar.seek = gzip_seek; + tar.close = gzip_close;*/ + + mtar_t tar = {0}; + int err; + if ((err = mtar_open(&tar, actual_src, "r"))) + return luaL_error(L, "can't open tar archive %s: %s", src, mtar_strerror(err)); + + mtar_header_t h; + mtar_header_t before_h; + mtar_header_t allways_h; + int has_ext_before = 0; + int has_ext_allways = 0; + + mtar_clear_header(&before_h); + mtar_clear_header(&allways_h); + + while ((mtar_read_header(&tar, &h)) != MTAR_ENULLRECORD ) { + if (h.type == MTAR_TREG) { + + if (has_ext_before) { + mtar_update_header(&h, &before_h); + has_ext_before = 0; + mtar_clear_header(&before_h); + } + if (has_ext_allways) + mtar_update_header(&h, &allways_h); + + char target[MAX_PATH]; + int target_length = snprintf(target, sizeof(target), "%s/%s", dst, h.name); + + if (mkdirp(target, target_length)) { + mtar_close(&tar); + return luaL_error(L, "can't extract tar archive file %s, can't create directory %s: %s", src, target, strerror(errno)); + } + + FILE* file = fopen(target, "wb"); + if (!file) { + mtar_close(&tar); + return luaL_error(L, "can't extract tar archive file %s, can't create file %s: %s", src, target, strerror(errno)); + } + + if (chmod(target, h.mode)) + return luaL_error(L, "can't extract tar archive file %s, can't chmod file %s: %s", src, target, strerror(errno)); + + char buffer[8192]; + int remaining = h.size; + while (remaining > 0) { + int read_size = remaining < sizeof(buffer) ? remaining : sizeof(buffer); + + int err = mtar_read_data(&tar, buffer, read_size); + if (err != MTAR_ESUCCESS) { + fclose(file); + mtar_close(&tar); + return luaL_error(L, "can't read file %s: %s", target, mtar_strerror(err)); + } + + fwrite(buffer, sizeof(char), read_size, file); + remaining -= read_size; + } + + fclose(file); + } + + else if (h.type == MTAR_TEHR || h.type == MTAR_TEHRA) { + mtar_header_t *h_to_change; + if (h.type == MTAR_TEHR) + h_to_change = &before_h; + else + h_to_change = &allways_h; + + char buffer[4096] = {0}; + char current_read[8192] = {0}; // If a line is more than 8192 char long, will not work! + char last_read[4096] = {0}; + int remaining = h.size; + + has_ext_before = 1; + + while (remaining > 0) { + int read_size = remaining < sizeof(buffer) ? remaining : sizeof(buffer); + remaining -= read_size; + + if (mtar_read_data(&tar, buffer, read_size) != MTAR_ESUCCESS) { + mtar_close(&tar); + return luaL_error(L, "Error while reading extended: %s", strerror(errno)); + } + + strcpy(current_read, last_read); + current_read[strlen(last_read)] = '\0'; + strcat(current_read, buffer); + current_read[strlen(last_read) + read_size] = '\0'; + + char *n_line_ptr = NULL; + char **l_line_ptr = NULL; + char *line = strtok_r(current_read, "\n", &n_line_ptr); + + while (line != NULL) { + char *in_line_ptr = NULL; + strtok_r(line, " ", &in_line_ptr); + char *header_key = strtok_r(NULL, "=", &in_line_ptr); + char *header_val = strtok_r(NULL, "=", &in_line_ptr); + + if (!strcmp(header_key, "path")) strcpy(h_to_change->name, header_val); + if (!strcmp(header_key, "linkpath")) strcpy(h_to_change->linkname, header_val); + // possibility to add more later + + l_line_ptr = &n_line_ptr; + line = strtok_r(NULL, "\n", &n_line_ptr); + } + + if (current_read[strlen(last_read) + read_size - 1] != '\n') + strcpy(last_read, strtok_r(current_read, "\n", l_line_ptr)); + else + memset(last_read, 0, strlen(last_read)); + } + } + + else if (h.type == MTAR_TGFP) { + has_ext_before = 1; + int read_size = before_h.size < sizeof(before_h.name) ? before_h.size : sizeof(before_h.name); + + if (mtar_read_data(&tar, before_h.name, read_size) != MTAR_ESUCCESS) { + mtar_close(&tar); + return luaL_error(L, "Error while reading GNU extended: %s", strerror(errno)); + } + } + + else if (h.type == MTAR_TGLP) { + has_ext_before = 1; + int read_size = before_h.size < sizeof(before_h.linkname) ? before_h.size : sizeof(before_h.linkname); + + if (mtar_read_data(&tar, before_h.linkname, read_size) != MTAR_ESUCCESS) { + mtar_close(&tar); + return luaL_error(L, "Error while reading GNU extended: %s", strerror(errno)); + } + } + + mtar_next(&tar); + } + mtar_close(&tar); + if (strstr(src, ".gz") || strstr(src, ".tgz")) + unlink(actual_src); + } + } + return 0; +} + + +static int ppm_socket_write(int fd, const char* buf, int len, mbedtls_ssl_context* ctx) { + if (ctx) + return mbedtls_ssl_write(ctx, buf, len); + return write(fd, buf, len); +} + +static int ppm_socket_read(int fd, char* buf, int len, mbedtls_ssl_context* ctx) { + if (ctx) + return mbedtls_ssl_read(ctx, buf, len); + return read(fd, buf, len); +} + +static int strncicmp(const char* a, const char* b, int n) { + for (int i = 0; i < n; ++i) { + if (a[i] == 0 && b[i] != 0) return -1; + if (a[i] != 0 && b[i] == 0) return 1; + int lowera = tolower(a[i]), lowerb = tolower(b[i]); + if (lowera == lowerb) continue; + if (lowera < lowerb) return -1; + return 1; + } + return 0; +} + +static const char* strnstr_local(const char* haystack, const char* needle, int n) { + int len = strlen(needle); + for (int i = 0; i <= n - len; ++i) { + if (strncmp(&haystack[i], needle, len) == 0) + return &haystack[i]; + } + return NULL; +} + +static const char* get_header(const char* buffer, const char* header, int* len) { + const char* line_end = strstr(buffer, "\r\n"); + const char* header_end = strstr(buffer, "\r\n\r\n"); + int header_len = strlen(header); + while (line_end && line_end < header_end) { + if (strncicmp(line_end + 2, header, header_len) == 0) { + const char* offset = line_end + header_len + 3; + while (*offset == ' ') { ++offset; } + const char* end = strstr(offset, "\r\n"); + if (len) + *len = end - offset; + return offset; + } + line_end = strstr(line_end + 2, "\r\n"); + } + return NULL; +} + +static int imin(int a, int b) { return a < b ? a : b; } +static int imax(int a, int b) { return a > b ? a : b; } + +static int ppm_get(lua_State* L) { + long response_code; + char err[1024] = {0}; + const char* protocol = luaL_checkstring(L, 1); + const char* hostname = luaL_checkstring(L, 2); + + int s = -2; + mbedtls_net_context net_context; + mbedtls_ssl_context ssl_context; + mbedtls_ssl_context* ssl_ctx = NULL; + mbedtls_net_context* net_ctx = NULL; + FILE* file = NULL; + if (strcmp(protocol, "https") == 0) { + int status; + const char* port = lua_tostring(L, 3); + // https://gist.github.com/Barakat/675c041fd94435b270a25b5881987a30 + ssl_ctx = &ssl_context; + mbedtls_ssl_init(&ssl_context); + if ((status = mbedtls_ssl_setup(&ssl_context, &ssl_config)) != 0) { + mbedtls_snprintf(1, err, sizeof(err), status, "can't set up ssl for %s: %d", hostname, status); goto cleanup; + } + net_ctx = &net_context; + mbedtls_net_init(&net_context); + mbedtls_net_set_block(&net_context); + mbedtls_ssl_set_bio(&ssl_context, &net_context, mbedtls_net_send, NULL, mbedtls_net_recv_timeout); + if ((status = mbedtls_net_connect(&net_context, hostname, port, MBEDTLS_NET_PROTO_TCP)) != 0) { + mbedtls_snprintf(1, err, sizeof(err), status, "can't connect to hostname %s", hostname); goto cleanup; + } else if ((status = mbedtls_ssl_set_hostname(&ssl_context, hostname)) != 0) { + mbedtls_snprintf(1, err, sizeof(err), status, "can't set hostname %s", hostname); goto cleanup; + } else if ((status = mbedtls_ssl_handshake(&ssl_context)) != 0) { + mbedtls_snprintf(1, err, sizeof(err), status, "can't handshake with %s", hostname); goto cleanup; + } else if (((status = mbedtls_ssl_get_verify_result(&ssl_context)) != 0) && !no_verify_ssl) { + mbedtls_snprintf(1, err, sizeof(err), status, "can't verify result for %s", hostname); goto cleanup; + } + } else { + int port = luaL_checkinteger(L, 3); + struct hostent *host = gethostbyname(hostname); + struct sockaddr_in dest_addr = {0}; + if (!host) + return luaL_error(L, "can't resolve hostname %s", hostname); + s = socket(AF_INET, SOCK_STREAM, 0); + #ifdef _WIN32 + DWORD timeout = 5 * 1000; + setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof timeout); + #else + struct timeval tv; + tv.tv_sec = 5; + tv.tv_usec = 0; + setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv); + #endif + dest_addr.sin_family = AF_INET; + dest_addr.sin_port = htons(port); + dest_addr.sin_addr.s_addr = *(long*)(host->h_addr); + const char* ip = inet_ntoa(dest_addr.sin_addr); + if (connect(s, (struct sockaddr *) &dest_addr, sizeof(struct sockaddr)) == -1 ) { + close(s); + return luaL_error(L, "can't connect to host %s [%s] on port %d", hostname, ip, port); + } + } + + const char* rest = luaL_checkstring(L, 4); + char buffer[HTTPS_RESPONSE_HEADER_BUFFER_LENGTH]; + int buffer_length = snprintf(buffer, sizeof(buffer), "GET %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", rest, hostname); + buffer_length = ppm_socket_write(s, buffer, buffer_length, ssl_ctx); + if (buffer_length < 0) { + mbedtls_snprintf(ssl_ctx ? 1 : 0, err, sizeof(err), ssl_ctx ? buffer_length : errno, "can't write to socket %s", hostname); goto cleanup; + } + int bytes_read = 0; + const char* header_end = NULL; + + + while (!header_end && bytes_read < sizeof(buffer) - 1) { + buffer_length = ppm_socket_read(s, &buffer[bytes_read], sizeof(buffer) - bytes_read - 1, ssl_ctx); + if (buffer_length < 0) { + mbedtls_snprintf(ssl_ctx ? 1 : 0, err, sizeof(err), ssl_ctx ? buffer_length : errno, "can't read from socket %s", hostname); goto cleanup; + } else if (buffer_length > 0) { + bytes_read += buffer_length; + buffer[bytes_read] = 0; + header_end = strstr(buffer, "\r\n\r\n"); + } + } + if (!header_end) { + snprintf(err, sizeof(err), "can't parse response headers for %s%s: %s", hostname, rest, bytes_read >= sizeof(buffer) - 1 ? "response header buffer length exceeded" : "malformed response"); + goto cleanup; + } + header_end += 4; + const char* protocol_end = strstr(buffer, " "); + int code = atoi(protocol_end + 1); + if (code != 200) { + if (code >= 301 && code <= 303) { + int len; + const char* location = get_header(buffer, "location", &len); + if (location) { + lua_pushnil(L); + lua_newtable(L); + lua_pushlstring(L, location, len); + lua_setfield(L, -2, "location"); + } else + snprintf(err, sizeof(err), "received invalid %d-response from %s%s: %d", code, hostname, rest, code); + goto cleanup; + } else { + snprintf(err, sizeof(err), "received non 200-response from %s%s: %d", hostname, rest, code); goto cleanup; + } + } + const char* transfer_encoding = get_header(buffer, "transfer-encoding", NULL); + int chunked = transfer_encoding && strncmp(transfer_encoding, "chunked", 7) == 0 ? 1 : 0; + const char* content_length_value = get_header(buffer, "content-length", NULL); + int content_length = content_length_value ? atoi(content_length_value) : -1; + const char* path = luaL_optstring(L, 5, NULL); + int callback_function = lua_type(L, 6) == LUA_TFUNCTION ? 6 : 0; + + buffer_length -= (header_end - buffer); + if (buffer_length > 0) + memmove(buffer, header_end, buffer_length); + + int total_downloaded = 0; + int chunk_length = !chunked && content_length == -1 ? INT_MAX : content_length; + int chunk_written = 0; + luaL_Buffer B; + if (path) { + file = lua_fopen(L, path, "wb"); + if (!file) { + snprintf(err, sizeof(err), "can't open file %s: %s", path, strerror(errno)); + goto cleanup; + } + } else + luaL_buffinit(L, &B); + while (1) { + // If we have an unknown amount of chunk bytes to be fetched, determine the size of the next chunk. + while (chunk_length == -1) { + char* newline = (char*)strnstr_local(buffer, "\r\n", buffer_length); + if (newline) { + *newline = '\0'; + if (sscanf(buffer, "%x", &chunk_length) != 1) { + snprintf(err, sizeof(err), "error retrieving chunk length %s%s", hostname, rest); + goto cleanup; + } + if (chunk_length == 0) + goto finish; + buffer_length -= (newline + 2 - buffer); + if (buffer_length > 0) + memmove(buffer, newline + 2, buffer_length); + chunk_written = 0; + } else if (buffer_length >= sizeof(buffer)) { + snprintf(err, sizeof(err), "can't find chunk length for %s%s", hostname, rest); + goto cleanup; + } else { + int length = ppm_socket_read(s, &buffer[buffer_length], sizeof(buffer) - buffer_length, ssl_ctx); + if (length <= 0 || (ssl_ctx && length == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY)) { + mbedtls_snprintf(ssl_ctx ? 1 : 0, err, sizeof(err), ssl_ctx ? length : errno, "error retrieving full response for %s%s", hostname, rest); + goto cleanup; + } + buffer_length += length; + } + } + if (buffer_length > 0) { + int to_write = imin(chunk_length - chunk_written, buffer_length); + if (to_write > 0) { + total_downloaded += to_write; + chunk_written += to_write; + if (callback_function) { + lua_pushvalue(L, callback_function); + lua_pushinteger(L, total_downloaded); + if (content_length == -1) + lua_pushnil(L); + else + lua_pushinteger(L, content_length); + lua_call(L, 2, 0); + } + if (file) + fwrite(buffer, sizeof(char), to_write, file); + else + luaL_addlstring(&B, buffer, to_write); + buffer_length -= to_write; + if (buffer_length > 0) + memmove(buffer, &buffer[to_write], buffer_length); + } + if (chunk_written == chunk_length) { + if (!chunked) + goto finish; + if (buffer_length >= 2) { + if (!strnstr_local(buffer, "\r\n", 2)) { + snprintf(err, sizeof(err), "invalid end to chunk for %s%s", hostname, rest); + goto cleanup; + } + memmove(buffer, &buffer[2], buffer_length - 2); + buffer_length -= 2; + chunk_length = -1; + } + } + } + if (chunk_length > 0) { + int length = ppm_socket_read(s, &buffer[buffer_length], imin(sizeof(buffer) - buffer_length, chunk_length - chunk_written + (chunked ? 2 : 0)), ssl_ctx); + if ((!ssl_ctx && length == 0) || (ssl_ctx && content_length == -1 && length == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY)) + goto finish; + if (length <= 0) { + mbedtls_snprintf(ssl_ctx ? 1 : 0, err, sizeof(err), ssl_ctx ? length : errno, "error retrieving full chunk for %s%s", hostname, rest); + goto cleanup; + } + buffer_length += length; + } + } + finish: + if (file) { + fclose(file); + file = NULL; + lua_pushnil(L); + } else { + luaL_pushresult(&B); + } + if (content_length != -1 && total_downloaded != content_length) { + snprintf(err, sizeof(err), "error retrieving full response for %s%s", hostname, rest); + goto cleanup; + } + if (callback_function) { + lua_pushvalue(L, callback_function); + lua_pushboolean(L, 1); + lua_call(L, 1, 0); + } + lua_newtable(L); + cleanup: + if (ssl_ctx) + mbedtls_ssl_free(ssl_ctx); + if (net_ctx) + mbedtls_net_free(net_ctx); + if (file) + fclose(file); + if (s != -2) + close(s); + if (err[0]) + return luaL_error(L, "%s", err); + return 2; +} + +static int ppm_chdir(lua_State* L) { + #ifdef _WIN32 + if (_wchdir(lua_toutf16(L, luaL_checkstring(L, 1)))) + #else + if (chdir(luaL_checkstring(L, 1))) + #endif + return luaL_error(L, "error chdiring: %s", strerror(errno)); + return 0; +} + +static int ppm_pwd(lua_State* L) { + #ifdef _WIN32 + wchar_t buffer[MAX_PATH]; + if (!_wgetcwd(buffer, sizeof(buffer))) + return luaL_error(L, "error getcwd: %s", strerror(errno)); + lua_toutf8(L, buffer); + #else + char buffer[MAX_PATH]; + if (!getcwd(buffer, sizeof(buffer))) + return luaL_error(L, "error getcwd: %s", strerror(errno)); + lua_pushstring(L, buffer); + #endif + return 1; +} + +static int ppm_flock(lua_State* L) { + const char* path = luaL_checkstring(L, 1); + luaL_checktype(L, 2, LUA_TFUNCTION); + int error_handler = lua_type(L, 3) == LUA_TFUNCTION ? 3 : 0; + int warning_handler = lua_type(L, 4) == LUA_TFUNCTION ? 4 : 0; + #ifdef _WIN32 + HANDLE file = CreateFileW(lua_toutf16(L, path), FILE_SHARE_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); + if (!file || file == INVALID_HANDLE_VALUE) + return luaL_win32_error(L, GetLastError(), "can't open for flock %s", path); + OVERLAPPED overlapped = {0}; + if (!LockFileEx(file, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, 0, 1, &overlapped)) { + if (GetLastError() == ERROR_IO_PENDING && warning_handler) { + lua_pushvalue(L, warning_handler); + lua_pcall(L, 0, 0, 0); + } + if (!LockFileEx(file, LOCKFILE_EXCLUSIVE_LOCK, 0, 0, 1, &overlapped)) { + CloseHandle(file); + return luaL_win32_error(L, GetLastError(), "can't flock %s", path); + } + } + #else + int fd = open(path, 0); + if (fd == -1) + return luaL_error(L, "can't flock %s: %s", path, strerror(errno)); + if (flock(fd, LOCK_EX | LOCK_NB) == -1) { + if (errno == EWOULDBLOCK && warning_handler) { + lua_pushvalue(L, warning_handler); + lua_pcall(L, 0, 0, 0); + } + if (flock(fd, LOCK_EX) == -1) { + close(fd); + return luaL_error(L, "can't acquire exclusive lock on %s: %s", strerror(errno)); + } + } + #endif + lua_pushvalue(L, 2); + lua_pushvalue(L, 1); + int err = lua_pcall(L, 1, 0, error_handler); + #ifdef _WIN32 + UnlockFile(file, 0, 0, 1, 0); + CloseHandle(file); + #else + flock(fd, LOCK_UN); + close(fd); + #endif + if (err) { + lua_pushboolean(L, 1); + return 1; + } + return 0; +} + +static double get_time() { + #if _WIN32 // Fuck I hate windows jesus chrsit. + LARGE_INTEGER LoggedTime, Frequency; + QueryPerformanceFrequency(&Frequency); + QueryPerformanceCounter(&LoggedTime); + return LoggedTime.QuadPart / (double)Frequency.QuadPart; + #else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec + ts.tv_nsec / 1000000000.0; + #endif +} + +static int ppm_time(lua_State* L) { + lua_pushnumber(L, get_time()); + return 1; +} + +static const luaL_Reg system_lib[] = { + { "ls", ppm_ls }, // Returns an array of files. + { "stat", ppm_stat }, // Returns info about a single file. + { "mkdir", ppm_mkdir }, // Makes a directory. + { "rmdir", ppm_rmdir }, // Removes a directory. + { "hash", ppm_hash }, // Returns a hex sha256 hash. + { "tcflush", ppm_tcflush }, // Flushes an terminal stream. + { "tcwidth", ppm_tcwidth }, // Gets the terminal width in columns. + { "symlink", ppm_symlink }, // Creates a symlink. + { "chmod", ppm_chmod }, // Chmod's a file. + { "init", ppm_init }, // Initializes a git repository with the specified remote. + { "fetch", ppm_fetch }, // Updates a git repository with the specified remote. + { "reset", ppm_reset }, // Updates a git repository to the specified commit/hash/branch. + { "get", ppm_get }, // HTTP(s) GET request. + { "extract", ppm_extract }, // Extracts .tar.gz, and .zip files. + { "trace", ppm_trace }, // Sets trace bit. + { "certs", ppm_certs }, // Sets the SSL certificate chain folder/file. + { "chdir", ppm_chdir }, // Changes directory. Only use for --post actions. + { "pwd", ppm_pwd }, // Gets existing directory. Only use for --post actions. + { "flock", ppm_flock }, // Locks a file. + { "time", ppm_time }, // Get high-precision system time. + { NULL, NULL } +}; + +#ifndef ARCH_PROCESSOR + #if defined(__x86_64__) || defined(_M_AMD64) || defined(__MINGW64__) + #define ARCH_PROCESSOR "x86_64" + #elif defined(__i386__) || defined(_M_IX86) || defined(__MINGW32__) + #define ARCH_PROCESSOR "x86" + #elif defined(__aarch64__) || defined(_M_ARM64) || defined (_M_ARM64EC) + #define ARCH_PROCESSOR "aarch64" + #elif defined(__arm__) || defined(_M_ARM) + #define ARCH_PROCESSOR "arm" + #elif defined(__riscv_xlen) && __riscv_xlen == 32 + #define ARCH_PROCESSOR "riscv32" + #elif defined(__riscv_xlen) && __riscv_xlen == 64 + #define ARCH_PROCESSOR "riscv64" + #else + #error "Please define -DARCH_PROCESSOR." + #endif +#endif +#ifndef ARCH_PLATFORM + #if _WIN32 + #define ARCH_PLATFORM "windows" + #elif __ANDROID__ + #define ARCH_PLATFORM "android" + #elif __linux__ + #define ARCH_PLATFORM "linux" + #elif __APPLE__ + #define ARCH_PLATFORM "darwin" + #else + #error "Please define -DARCH_PLATFORM." + #endif +#endif +#ifndef PRAGTICAL_ARCH_TUPLE + #define PRAGTICAL_ARCH_TUPLE ARCH_PROCESSOR "-" ARCH_PLATFORM +#endif + + +#ifndef PPM_VERSION + #define PPM_VERSION "unknown" +#endif +#ifndef PPM_DEFAULT_REPOSITORY + #define PPM_DEFAULT_REPOSITORY "https://github.com/pragtical/plugin-manager.git:latest" +#endif +// If this is defined as empty string, we disable self-upgrading. +#ifndef PPM_DEFAULT_RELEASE + #if _WIN32 + #define PPM_DEFAULT_RELEASE "https://github.com/pragtical/plugin-manager/releases/download/%r/ppm." PRAGTICAL_ARCH_TUPLE ".exe" + #else + #define PPM_DEFAULT_RELEASE "https://github.com/pragtical/plugin-manager/releases/download/%r/ppm." PRAGTICAL_ARCH_TUPLE + #endif +#endif + +#ifdef PPM_STATIC + extern const char ppm_luac[]; + extern unsigned int ppm_luac_len; +#endif + +int main(int argc, char* argv[]) { + lua_State* L = luaL_newstate(); + luaL_openlibs(L); + luaL_newlib(L, system_lib); + lua_setglobal(L, "system"); + lua_newtable(L); + for (int i = 0; i < argc; ++i) { + lua_pushstring(L, argv[i]); + lua_rawseti(L, -2, i+1); + } + lua_setglobal(L, "ARGV"); + lua_pushliteral(L, PPM_VERSION); + lua_setglobal(L, "VERSION"); + lua_pushliteral(L, ARCH_PLATFORM); + lua_setglobal(L, "PLATFORM"); + lua_pushboolean(L, isatty(fileno(stdout))); + lua_setglobal(L, "TTY"); + #if _WIN32 + lua_pushliteral(L, "\\"); + #else + lua_pushliteral(L, "/"); + #endif + lua_setglobal(L, "PATHSEP"); + #if _WIN32 + wchar_t tmpdir[MAX_PATH]; + DWORD length = GetTempPathW(MAX_PATH, tmpdir); + tmpdir[length - 1] = 0; + lua_toutf8(L, tmpdir); + #else + lua_pushstring(L, getenv("TMPDIR") ? getenv("TMPDIR") : P_tmpdir); + #endif + lua_setglobal(L, "SYSTMPDIR"); + + lua_pushliteral(L, PRAGTICAL_ARCH_TUPLE); + lua_setglobal(L, "ARCH"); + lua_pushliteral(L, PPM_DEFAULT_REPOSITORY); + lua_setglobal(L, "DEFAULT_REPO_URL"); + lua_pushliteral(L, PPM_DEFAULT_RELEASE); + lua_setglobal(L, "DEFAULT_RELEASE_URL"); + #ifndef PPM_STATIC + if (luaL_loadfile(L, "src/ppm.lua") || lua_pcall(L, 0, 1, 0)) { + #else + if (luaL_loadbuffer(L, ppm_luac, ppm_luac_len, "ppm.lua") || lua_pcall(L, 0, 1, 0)) { + #endif + fprintf(stderr, "internal error when starting the application: %s\n", lua_tostring(L, -1)); + return -1; + } + int status = lua_tointeger(L, -1); + lua_close(L); + if (git_initialized) + git_libgit2_shutdown(); + return status; +} |
