diff options
50 files changed, 2100 insertions, 563 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0eedab87a..cdcea1644 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -130,19 +130,19 @@ jobs: SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true setup-script: osx - - name: "Windows (amd64, Visual Studio)" + - name: "Windows (amd64, Visual Studio, Schannel)" id: windows-amd64-vs os: windows-2019 setup-script: win32 env: ARCH: amd64 CMAKE_GENERATOR: Visual Studio 16 2019 - CMAKE_OPTIONS: -A x64 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_SSH=ON -DCMAKE_PREFIX_PATH=D:\Temp\libssh2 + CMAKE_OPTIONS: -A x64 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_HTTPS=Schannel -DUSE_SSH=ON -DCMAKE_PREFIX_PATH=D:\Temp\libssh2 BUILD_PATH: C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin;D:\Temp\libssh2\bin BUILD_TEMP: D:\Temp SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true - - name: "Windows (x86, Visual Studio)" + - name: "Windows (x86, Visual Studio, WinHTTP)" id: windows-x86-vs os: windows-2019 setup-script: win32 @@ -154,7 +154,7 @@ jobs: BUILD_TEMP: D:\Temp SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true - - name: "Windows (amd64, mingw)" + - name: "Windows (amd64, mingw, WinHTTP)" id: windows-amd64-mingw os: windows-2019 setup-script: mingw @@ -166,14 +166,14 @@ jobs: BUILD_PATH: D:\Temp\mingw64\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true - - name: "Windows (x86, mingw)" + - name: "Windows (x86, mingw, Schannel)" id: windows-x86-mingw os: windows-2019 setup-script: mingw env: ARCH: x86 CMAKE_GENERATOR: MinGW Makefiles - CMAKE_OPTIONS: -DDEPRECATE_HARD=ON + CMAKE_OPTIONS: -DDEPRECATE_HARD=ON -DUSE_HTTPS=Schannel BUILD_TEMP: D:\Temp BUILD_PATH: D:\Temp\mingw32\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin SKIP_SSH_TESTS: true diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 5a0b7d12b..f461530ae 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -162,32 +162,39 @@ jobs: SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true setup-script: osx - - name: "Windows (amd64, Visual Studio)" + - name: "Windows (amd64, Visual Studio, WinHTTP)" os: windows-2019 env: ARCH: amd64 CMAKE_GENERATOR: Visual Studio 16 2019 - CMAKE_OPTIONS: -A x64 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON + CMAKE_OPTIONS: -A x64 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_HTTPS=WinHTTP SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true - - name: "Windows (no mmap)" + - name: "Windows (x86, Visual Studio, WinHTTP)" + os: windows-2019 + env: + ARCH: x86 + CMAKE_GENERATOR: Visual Studio 16 2019 + CMAKE_OPTIONS: -A Win32 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_HTTPS=WinHTTP -DUSE_SHA1=HTTPS -DUSE_BUNDLED_ZLIB=ON + SKIP_SSH_TESTS: true + SKIP_NEGOTIATE_TESTS: true + - name: "Windows (amd64, Visual Studio, Schannel)" os: windows-2019 env: ARCH: amd64 CMAKE_GENERATOR: Visual Studio 16 2019 - CFLAGS: -DNO_MMAP - CMAKE_OPTIONS: -A x64 -DDEPRECATE_HARD=ON + CMAKE_OPTIONS: -A x64 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_HTTPS=Schannel SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true - - name: "Windows (x86, Visual Studio)" + - name: "Windows (x86, Visual Studio, Schannel)" os: windows-2019 env: ARCH: x86 CMAKE_GENERATOR: Visual Studio 16 2019 - CMAKE_OPTIONS: -A Win32 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_SHA1=HTTPS -DUSE_BUNDLED_ZLIB=ON + CMAKE_OPTIONS: -A Win32 -DWIN32_LEAKCHECK=ON -DDEPRECATE_HARD=ON -DUSE_HTTPS=Schannel -DUSE_BUNDLED_ZLIB=ON SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true - - name: "Windows (amd64, mingw)" + - name: "Windows (amd64, mingw, WinHTTP)" os: windows-2019 setup-script: mingw env: @@ -198,17 +205,26 @@ jobs: BUILD_PATH: D:\Temp\mingw64\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true - - name: "Windows (x86, mingw)" + - name: "Windows (x86, mingw, Schannel)" os: windows-2019 setup-script: mingw env: ARCH: x86 CMAKE_GENERATOR: MinGW Makefiles - CMAKE_OPTIONS: -DDEPRECATE_HARD=ON + CMAKE_OPTIONS: -DDEPRECATE_HARD=ON -DUSE_HTTPS=Schannel BUILD_TEMP: D:\Temp BUILD_PATH: D:\Temp\mingw32\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\CMake\bin SKIP_SSH_TESTS: true SKIP_NEGOTIATE_TESTS: true + - name: "Windows (no mmap)" + os: windows-2019 + env: + ARCH: amd64 + CMAKE_GENERATOR: Visual Studio 16 2019 + CFLAGS: -DNO_MMAP + CMAKE_OPTIONS: -A x64 -DDEPRECATE_HARD=ON + SKIP_SSH_TESTS: true + SKIP_NEGOTIATE_TESTS: true - name: "Linux (Bionic, GCC, dynamically-loaded OpenSSL)" container: name: bionic diff --git a/CMakeLists.txt b/CMakeLists.txt index fc31faeca..30527b928 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.5.1) -project(libgit2 VERSION "1.6.2" LANGUAGES C) +project(libgit2 VERSION "1.7.0" LANGUAGES C) # Add find modules to the path set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake") @@ -83,12 +83,6 @@ if(MSVC) option(WIN32_LEAKCHECK "Enable leak reporting via crtdbg" OFF) endif() -if(WIN32) - # By default, libgit2 is built with WinHTTP. To use the built-in - # HTTP transport, invoke CMake with the "-DUSE_WINHTTP=OFF" argument. - option(USE_WINHTTP "Use Win32 WinHTTP routines" ON) -endif() - if(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) endif() diff --git a/cmake/SelectGSSAPI.cmake b/cmake/SelectGSSAPI.cmake index 24e2d68b9..5bde11697 100644 --- a/cmake/SelectGSSAPI.cmake +++ b/cmake/SelectGSSAPI.cmake @@ -29,7 +29,7 @@ if(USE_GSSAPI) list(APPEND LIBGIT2_SYSTEM_LIBS ${GSSFRAMEWORK_LIBRARIES}) set(GIT_GSSFRAMEWORK 1) - add_feature_info(SPNEGO GIT_GSSFRAMEWORK "SPNEGO authentication support (${USE_GSSAPI})") + add_feature_info(GSSAPI GIT_GSSFRAMEWORK "GSSAPI support for SPNEGO authentication (${USE_GSSAPI})") elseif(USE_GSSAPI STREQUAL "gssapi") if(NOT GSSAPI_FOUND) message(FATAL_ERROR "Asked for gssapi GSS backend, but it wasn't found") @@ -38,11 +38,11 @@ if(USE_GSSAPI) list(APPEND LIBGIT2_SYSTEM_LIBS ${GSSAPI_LIBRARIES}) set(GIT_GSSAPI 1) - add_feature_info(SPNEGO GIT_GSSAPI "SPNEGO authentication support (${USE_GSSAPI})") + add_feature_info(GSSAPI GIT_GSSAPI "GSSAPI support for SPNEGO authentication (${USE_GSSAPI})") else() message(FATAL_ERROR "Asked for backend ${USE_GSSAPI} but it wasn't found") endif() else() set(GIT_GSSAPI 0) - add_feature_info(SPNEGO NO "SPNEGO authentication support") + add_feature_info(GSSAPI NO "GSSAPI support for SPNEGO authentication") endif() diff --git a/cmake/SelectHTTPSBackend.cmake b/cmake/SelectHTTPSBackend.cmake index 20221bf9f..d14941643 100644 --- a/cmake/SelectHTTPSBackend.cmake +++ b/cmake/SelectHTTPSBackend.cmake @@ -19,7 +19,7 @@ if(USE_HTTPS) message(STATUS "Security framework is too old, falling back to OpenSSL") set(USE_HTTPS "OpenSSL") endif() - elseif(USE_WINHTTP) + elseif(WIN32) set(USE_HTTPS "WinHTTP") elseif(OPENSSL_FOUND) set(USE_HTTPS "OpenSSL") @@ -106,8 +106,27 @@ if(USE_HTTPS) # https://github.com/ARMmbed/mbedtls/issues/228 # For now, pass its link flags as our own list(APPEND LIBGIT2_PC_LIBS ${MBEDTLS_LIBRARIES}) + elseif(USE_HTTPS STREQUAL "Schannel") + set(GIT_SCHANNEL 1) + + list(APPEND LIBGIT2_SYSTEM_LIBS "rpcrt4" "crypt32" "ole32" "secur32") + list(APPEND LIBGIT2_PC_LIBS "-lrpcrt4" "-lcrypt32" "-lole32" "-lsecur32") elseif(USE_HTTPS STREQUAL "WinHTTP") - # WinHTTP setup was handled in the WinHTTP-specific block above + set(GIT_WINHTTP 1) + + # Since MinGW does not come with headers or an import library for winhttp, + # we have to include a private header and generate our own import library + if(MINGW) + add_subdirectory("${PROJECT_SOURCE_DIR}/deps/winhttp" "${PROJECT_BINARY_DIR}/deps/winhttp") + list(APPEND LIBGIT2_SYSTEM_LIBS winhttp) + list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${PROJECT_SOURCE_DIR}/deps/winhttp") + else() + list(APPEND LIBGIT2_SYSTEM_LIBS "winhttp") + list(APPEND LIBGIT2_PC_LIBS "-lwinhttp") + endif() + + list(APPEND LIBGIT2_SYSTEM_LIBS "rpcrt4" "crypt32" "ole32" "secur32") + list(APPEND LIBGIT2_PC_LIBS "-lrpcrt4" "-lcrypt32" "-lole32" "-lsecur32") elseif(USE_HTTPS STREQUAL "OpenSSL-Dynamic") set(GIT_OPENSSL 1) set(GIT_OPENSSL_DYNAMIC 1) diff --git a/cmake/SelectHashes.cmake b/cmake/SelectHashes.cmake index faf9e2ea3..5c007e587 100644 --- a/cmake/SelectHashes.cmake +++ b/cmake/SelectHashes.cmake @@ -13,6 +13,8 @@ if(USE_SHA1 STREQUAL ON) elseif(USE_SHA1 STREQUAL "HTTPS") if(USE_HTTPS STREQUAL "SecureTransport") set(USE_SHA1 "CommonCrypto") + elseif(USE_HTTPS STREQUAL "Schannel") + set(USE_SHA1 "Win32") elseif(USE_HTTPS STREQUAL "WinHTTP") set(USE_SHA1 "Win32") elseif(USE_HTTPS) @@ -51,6 +53,8 @@ endif() if(USE_SHA256 STREQUAL "HTTPS") if(USE_HTTPS STREQUAL "SecureTransport") set(USE_SHA256 "CommonCrypto") + elseif(USE_HTTPS STREQUAL "Schannel") + set(USE_SHA256 "Win32") elseif(USE_HTTPS STREQUAL "WinHTTP") set(USE_SHA256 "Win32") elseif(USE_HTTPS) diff --git a/cmake/SelectWinHTTP.cmake b/cmake/SelectWinHTTP.cmake deleted file mode 100644 index 96e0bdbae..000000000 --- a/cmake/SelectWinHTTP.cmake +++ /dev/null @@ -1,17 +0,0 @@ -if(WIN32 AND USE_WINHTTP) - set(GIT_WINHTTP 1) - - # Since MinGW does not come with headers or an import library for winhttp, - # we have to include a private header and generate our own import library - if(MINGW) - add_subdirectory("${PROJECT_SOURCE_DIR}/deps/winhttp" "${PROJECT_BINARY_DIR}/deps/winhttp") - list(APPEND LIBGIT2_SYSTEM_LIBS winhttp) - list(APPEND LIBGIT2_DEPENDENCY_INCLUDES "${PROJECT_SOURCE_DIR}/deps/winhttp") - else() - list(APPEND LIBGIT2_SYSTEM_LIBS "winhttp") - list(APPEND LIBGIT2_PC_LIBS "-lwinhttp") - endif() - - list(APPEND LIBGIT2_SYSTEM_LIBS "rpcrt4" "crypt32" "ole32") - list(APPEND LIBGIT2_PC_LIBS "-lrpcrt4" "-lcrypt32" "-lole32") -endif() diff --git a/docs/changelog.md b/docs/changelog.md index f685234aa..20e48a084 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,3 +1,26 @@ +v1.6.3 +------ + +## What's Changed + +### Bug fixes + +* odb: restore `git_odb_open` by @ethomson in https://github.com/libgit2/libgit2/pull/6520 +* Ensure that `git_index_add_all` handles ignored directories by @ethomson in https://github.com/libgit2/libgit2/pull/6521 +* pack: use 64 bits for the number of objects by @carlosmn in https://github.com/libgit2/libgit2/pull/6530 + +### Build and CI improvements + +* Remove unused wditer variable by @georgthegreat in https://github.com/libgit2/libgit2/pull/6518 +* fs_path: let root run the ownership tests by @ethomson in https://github.com/libgit2/libgit2/pull/6513 +* sysdir: Do not declare win32 functions on non-win32 platforms by @Batchyx in https://github.com/libgit2/libgit2/pull/6527 +* cmake: don't include `include/git2` by @ethomson in https://github.com/libgit2/libgit2/pull/6529 + +## New Contributors +* @georgthegreat made their first contribution in https://github.com/libgit2/libgit2/pull/6518 + +**Full Changelog**: https://github.com/libgit2/libgit2/compare/v1.6.2...v1.6.3 + v1.6.2 ------ diff --git a/include/git2/version.h b/include/git2/version.h index 8b5eb3138..bed47f51e 100644 --- a/include/git2/version.h +++ b/include/git2/version.h @@ -11,16 +11,16 @@ * The version string for libgit2. This string follows semantic * versioning (v2) guidelines. */ -#define LIBGIT2_VERSION "1.6.2" +#define LIBGIT2_VERSION "1.7.0-alpha" /** The major version number for this version of libgit2. */ #define LIBGIT2_VER_MAJOR 1 /** The minor version number for this version of libgit2. */ -#define LIBGIT2_VER_MINOR 6 +#define LIBGIT2_VER_MINOR 7 /** The revision ("teeny") version number for this version of libgit2. */ -#define LIBGIT2_VER_REVISION 2 +#define LIBGIT2_VER_REVISION 0 /** The Windows DLL patch number for this version of libgit2. */ #define LIBGIT2_VER_PATCH 0 @@ -31,9 +31,9 @@ * a prerelease name like "beta" or "rc1". For final releases, this will * be `NULL`. */ -#define LIBGIT2_VER_PRERELEASE NULL +#define LIBGIT2_VER_PRERELEASE "alpha" /** The library ABI soversion for this version of libgit2. */ -#define LIBGIT2_SOVERSION "1.6" +#define LIBGIT2_SOVERSION "1.7" #endif diff --git a/package.json b/package.json index e29459efd..398446ea5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libgit2", - "version": "1.6.2", + "version": "1.7.0-alpha", "repo": "https://github.com/libgit2/libgit2", "description": " A cross-platform, linkable library implementation of Git that you can use in your application.", "install": "mkdir build && cd build && cmake .. && cmake --build ." diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e14bd36c2..cc0a0d4dc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -43,7 +43,6 @@ include(SelectHTTPParser) include(SelectRegex) include(SelectXdiff) include(SelectSSH) -include(SelectWinHTTP) include(SelectZlib) # diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index ac1659c17..84b6c1901 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -1,7 +1,6 @@ set(CLI_INCLUDES "${libgit2_BINARY_DIR}/src/util" "${libgit2_BINARY_DIR}/include" - "${libgit2_BINARY_DIR}/include/git2" "${libgit2_SOURCE_DIR}/src/util" "${libgit2_SOURCE_DIR}/src/cli" "${libgit2_SOURCE_DIR}/include" diff --git a/src/libgit2/CMakeLists.txt b/src/libgit2/CMakeLists.txt index 5c453aab9..876a703e8 100644 --- a/src/libgit2/CMakeLists.txt +++ b/src/libgit2/CMakeLists.txt @@ -10,7 +10,6 @@ include(PkgBuildConfig) set(LIBGIT2_INCLUDES "${PROJECT_BINARY_DIR}/src/util" "${PROJECT_BINARY_DIR}/include" - "${PROJECT_BINARY_DIR}/include/git2" "${PROJECT_SOURCE_DIR}/src/libgit2" "${PROJECT_SOURCE_DIR}/src/util" "${PROJECT_SOURCE_DIR}/include") diff --git a/src/libgit2/config.c b/src/libgit2/config.c index 6d15a8db6..23a8f9ffa 100644 --- a/src/libgit2/config.c +++ b/src/libgit2/config.c @@ -1174,9 +1174,12 @@ int git_config__find_programdata(git_str *path) GIT_FS_PATH_OWNER_CURRENT_USER | GIT_FS_PATH_OWNER_ADMINISTRATOR; bool is_safe; + int error; + + if ((error = git_sysdir_find_programdata_file(path, GIT_CONFIG_FILENAME_PROGRAMDATA)) < 0) + return error; - if (git_sysdir_find_programdata_file(path, GIT_CONFIG_FILENAME_PROGRAMDATA) < 0 || - git_fs_path_owner_is(&is_safe, path->ptr, owner_level) < 0) + if (git_fs_path_owner_is(&is_safe, path->ptr, owner_level) < 0) return -1; if (!is_safe) { diff --git a/src/libgit2/index.c b/src/libgit2/index.c index 195ec1d5a..d4532c005 100644 --- a/src/libgit2/index.c +++ b/src/libgit2/index.c @@ -3509,7 +3509,8 @@ static int index_apply_to_wd_diff(git_index *index, int action, const git_strarr GIT_DIFF_RECURSE_UNTRACKED_DIRS; if (flags == GIT_INDEX_ADD_FORCE) - opts.flags |= GIT_DIFF_INCLUDE_IGNORED; + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | + GIT_DIFF_RECURSE_IGNORED_DIRS; } if ((error = git_diff_index_to_workdir(&diff, repo, index, &opts)) < 0) diff --git a/src/libgit2/libgit2.c b/src/libgit2/libgit2.c index f225122e5..5d796b1f7 100644 --- a/src/libgit2/libgit2.c +++ b/src/libgit2/libgit2.c @@ -30,6 +30,7 @@ #include "streams/registry.h" #include "streams/mbedtls.h" #include "streams/openssl.h" +#include "streams/socket.h" #include "transports/smart.h" #include "transports/http.h" #include "transports/ssh.h" @@ -78,6 +79,7 @@ int git_libgit2_init(void) git_merge_driver_global_init, git_transport_ssh_global_init, git_stream_registry_global_init, + git_socket_stream_global_init, git_openssl_stream_global_init, git_mbedtls_stream_global_init, git_mwindow_global_init, diff --git a/src/libgit2/pack.c b/src/libgit2/pack.c index c30801844..d59973aa9 100644 --- a/src/libgit2/pack.c +++ b/src/libgit2/pack.c @@ -200,7 +200,7 @@ static void pack_index_free(struct git_pack_file *p) static int pack_index_check_locked(const char *path, struct git_pack_file *p) { struct git_pack_idx_header *hdr; - uint32_t version, nr, i, *index; + uint32_t version, nr = 0, i, *index; void *idx_map; size_t idx_size; struct stat st; @@ -246,7 +246,6 @@ static int pack_index_check_locked(const char *path, struct git_pack_file *p) version = 1; } - nr = 0; index = idx_map; if (version > 1) @@ -269,7 +268,7 @@ static int pack_index_check_locked(const char *path, struct git_pack_file *p) * - 20/32-byte SHA of the packfile * - 20/32-byte SHA file checksum */ - if (idx_size != (4 * 256 + (nr * (p->oid_size + 4)) + (p->oid_size * 2))) { + if (idx_size != (4 * 256 + ((uint64_t) nr * (p->oid_size + 4)) + (p->oid_size * 2))) { git_futils_mmap_free(&p->index_map); return packfile_error("index is corrupted"); } @@ -287,8 +286,8 @@ static int pack_index_check_locked(const char *path, struct git_pack_file *p) * variable sized table containing 8-byte entries * for offsets larger than 2^31. */ - unsigned long min_size = 8 + (4 * 256) + (nr * (p->oid_size + 4 + 4)) + (p->oid_size * 2); - unsigned long max_size = min_size; + uint64_t min_size = 8 + (4 * 256) + ((uint64_t)nr * (p->oid_size + 4 + 4)) + (p->oid_size * 2); + uint64_t max_size = min_size; if (nr) max_size += (nr - 1)*8; diff --git a/src/libgit2/repository.c b/src/libgit2/repository.c index 8c41167a1..3f57fb23c 100644 --- a/src/libgit2/repository.c +++ b/src/libgit2/repository.c @@ -66,7 +66,7 @@ static const struct { static int check_repositoryformatversion(int *version, git_config *config); static int check_extensions(git_config *config, int version); -static int load_global_config(git_config **config); +static int load_global_config(git_config **config, bool use_env); static int load_objectformat(git_repository *repo, git_config *config); #define GIT_COMMONDIR_FILE "commondir" @@ -191,11 +191,23 @@ void git_repository_free(git_repository *repo) } /* Check if we have a separate commondir (e.g. we have a worktree) */ -static int lookup_commondir(bool *separate, git_str *commondir, git_str *repository_path) +static int lookup_commondir( + bool *separate, + git_str *commondir, + git_str *repository_path, + uint32_t flags) { - git_str common_link = GIT_STR_INIT; + git_str common_link = GIT_STR_INIT; int error; + /* Environment variable overrides configuration */ + if ((flags & GIT_REPOSITORY_OPEN_FROM_ENV)) { + error = git__getenv(commondir, "GIT_COMMON_DIR"); + + if (!error || error != GIT_ENOTFOUND) + goto done; + } + /* * If there's no commondir file, the repository path is the * common path, but it needs a trailing slash. @@ -222,12 +234,11 @@ static int lookup_commondir(bool *separate, git_str *commondir, git_str *reposit git_str_swap(commondir, &common_link); } - git_str_dispose(&common_link); - /* Make sure the commondir path always has a trailing slash */ error = git_fs_path_prettify_dir(commondir, commondir->ptr, NULL); done: + git_str_dispose(&common_link); return error; } @@ -252,14 +263,19 @@ GIT_INLINE(int) validate_repo_path(git_str *path) * * Open a repository object from its path */ -static int is_valid_repository_path(bool *out, git_str *repository_path, git_str *common_path) +static int is_valid_repository_path( + bool *out, + git_str *repository_path, + git_str *common_path, + uint32_t flags) { bool separate_commondir = false; int error; *out = false; - if ((error = lookup_commondir(&separate_commondir, common_path, repository_path)) < 0) + if ((error = lookup_commondir(&separate_commondir, + common_path, repository_path, flags)) < 0) return error; /* Ensure HEAD file exists */ @@ -337,19 +353,42 @@ static int load_config_data(git_repository *repo, const git_config *config) return 0; } -static int load_workdir(git_repository *repo, git_config *config, git_str *parent_path) +static int load_workdir( + git_repository *repo, + git_config *config, + git_str *parent_path) { - int error; - git_config_entry *ce; + git_config_entry *ce = NULL; git_str worktree = GIT_STR_INIT; git_str path = GIT_STR_INIT; + git_str workdir_env = GIT_STR_INIT; + const char *value = NULL; + int error; if (repo->is_bare) return 0; - if ((error = git_config__lookup_entry( - &ce, config, "core.worktree", false)) < 0) - return error; + /* Environment variables are preferred */ + if (repo->use_env) { + error = git__getenv(&workdir_env, "GIT_WORK_TREE"); + + if (error == 0) + value = workdir_env.ptr; + else if (error == GIT_ENOTFOUND) + error = 0; + else + goto cleanup; + } + + /* Examine configuration values if necessary */ + if (!value) { + if ((error = git_config__lookup_entry(&ce, config, + "core.worktree", false)) < 0) + return error; + + if (ce && ce->value) + value = ce->value; + } if (repo->is_worktree) { char *gitlink = git_worktree__read_link(repo->gitdir, GIT_GITDIR_FILE); @@ -367,17 +406,21 @@ static int load_workdir(git_repository *repo, git_config *config, git_str *paren } repo->workdir = git_str_detach(&worktree); - } - else if (ce && ce->value) { - if ((error = git_fs_path_prettify_dir( - &worktree, ce->value, repo->gitdir)) < 0) + } else if (value) { + if (!*value) { + git_error_set(GIT_ERROR_NET, "working directory cannot be set to empty path"); + error = -1; + goto cleanup; + } + + if ((error = git_fs_path_prettify_dir(&worktree, + value, repo->gitdir)) < 0) goto cleanup; repo->workdir = git_str_detach(&worktree); - } - else if (parent_path && git_fs_path_isdir(parent_path->ptr)) + } else if (parent_path && git_fs_path_isdir(parent_path->ptr)) { repo->workdir = git_str_detach(parent_path); - else { + } else { if (git_fs_path_dirname_r(&worktree, repo->gitdir) < 0 || git_fs_path_to_dir(&worktree) < 0) { error = -1; @@ -388,8 +431,10 @@ static int load_workdir(git_repository *repo, git_config *config, git_str *paren } GIT_ERROR_CHECK_ALLOC(repo->workdir); + cleanup: git_str_dispose(&path); + git_str_dispose(&workdir_env); git_config_entry_free(ce); return error; } @@ -541,7 +586,10 @@ static int validate_ownership_cb(const git_config_entry *entry, void *payload) return 0; } -static int validate_ownership_config(bool *is_safe, const char *path) +static int validate_ownership_config( + bool *is_safe, + const char *path, + bool use_env) { validate_ownership_data ownership_data = { path, GIT_STR_INIT, is_safe @@ -549,7 +597,7 @@ static int validate_ownership_config(bool *is_safe, const char *path) git_config *config; int error; - if (load_global_config(&config) != 0) + if (load_global_config(&config, use_env) != 0) return 0; error = git_config_get_multivar_foreach(config, @@ -623,7 +671,8 @@ static int validate_ownership(git_repository *repo) } if (is_safe || - (error = validate_ownership_config(&is_safe, validation_paths[0])) < 0) + (error = validate_ownership_config( + &is_safe, validation_paths[0], repo->use_env)) < 0) goto done; if (!is_safe) { @@ -637,14 +686,28 @@ done: return error; } -static int find_repo( - git_str *gitdir_path, - git_str *workdir_path, - git_str *gitlink_path, - git_str *commondir_path, +struct repo_paths { + git_str gitdir; + git_str workdir; + git_str gitlink; + git_str commondir; +}; + +#define REPO_PATHS_INIT { GIT_STR_INIT } + +GIT_INLINE(void) repo_paths_dispose(struct repo_paths *paths) +{ + git_str_dispose(&paths->gitdir); + git_str_dispose(&paths->workdir); + git_str_dispose(&paths->gitlink); + git_str_dispose(&paths->commondir); +} + +static int find_repo_traverse( + struct repo_paths *out, const char *start_path, - uint32_t flags, - const char *ceiling_dirs) + const char *ceiling_dirs, + uint32_t flags) { git_str path = GIT_STR_INIT; git_str repo_link = GIT_STR_INIT; @@ -656,19 +719,23 @@ static int find_repo( size_t ceiling_offset = 0; int error; - git_str_clear(gitdir_path); + git_str_clear(&out->gitdir); - error = git_fs_path_prettify(&path, start_path, NULL); - if (error < 0) + if ((error = git_fs_path_prettify(&path, start_path, NULL)) < 0) return error; - /* in_dot_git toggles each loop: + /* + * In each loop we look first for a `.git` dir within the + * directory, then to see if the directory itself is a repo. + * + * In other words: if we start in /a/b/c, then we look at: * /a/b/c/.git, /a/b/c, /a/b/.git, /a/b, /a/.git, /a - * With GIT_REPOSITORY_OPEN_BARE or GIT_REPOSITORY_OPEN_NO_DOTGIT, we - * assume we started with /a/b/c.git and don't append .git the first - * time through. - * min_iterations indicates the number of iterations left before going - * further counts as a search. */ + * + * With GIT_REPOSITORY_OPEN_BARE or GIT_REPOSITORY_OPEN_NO_DOTGIT, + * we assume we started with /a/b/c.git and don't append .git the + * first time through. min_iterations indicates the number of + * iterations left before going further counts as a search. + */ if (flags & (GIT_REPOSITORY_OPEN_BARE | GIT_REPOSITORY_OPEN_NO_DOTGIT)) { in_dot_git = true; min_iterations = 1; @@ -695,48 +762,51 @@ static int find_repo( break; if (S_ISDIR(st.st_mode)) { - if ((error = is_valid_repository_path(&is_valid, &path, &common_link)) < 0) + if ((error = is_valid_repository_path(&is_valid, &path, &common_link, flags)) < 0) goto out; if (is_valid) { if ((error = git_fs_path_to_dir(&path)) < 0 || - (error = git_str_set(gitdir_path, path.ptr, path.size)) < 0) + (error = git_str_set(&out->gitdir, path.ptr, path.size)) < 0) + goto out; + + if ((error = git_str_attach(&out->gitlink, git_worktree__read_link(path.ptr, GIT_GITDIR_FILE), 0)) < 0) goto out; - if (gitlink_path) - if ((error = git_str_attach(gitlink_path, git_worktree__read_link(path.ptr, GIT_GITDIR_FILE), 0)) < 0) - goto out; - if (commondir_path) - git_str_swap(&common_link, commondir_path); + git_str_swap(&common_link, &out->commondir); break; } } else if (S_ISREG(st.st_mode) && git__suffixcmp(path.ptr, "/" DOT_GIT) == 0) { if ((error = read_gitfile(&repo_link, path.ptr)) < 0 || - (error = is_valid_repository_path(&is_valid, &repo_link, &common_link)) < 0) + (error = is_valid_repository_path(&is_valid, &repo_link, &common_link, flags)) < 0) goto out; if (is_valid) { - git_str_swap(gitdir_path, &repo_link); + git_str_swap(&out->gitdir, &repo_link); - if (gitlink_path) - if ((error = git_str_put(gitlink_path, path.ptr, path.size)) < 0) - goto out; - if (commondir_path) - git_str_swap(&common_link, commondir_path); + if ((error = git_str_put(&out->gitlink, path.ptr, path.size)) < 0) + goto out; + + git_str_swap(&common_link, &out->commondir); } break; } } - /* Move up one directory. If we're in_dot_git, we'll search the - * parent itself next. If we're !in_dot_git, we'll search .git - * in the parent directory next (added at the top of the loop). */ + /* + * Move up one directory. If we're in_dot_git, we'll + * search the parent itself next. If we're !in_dot_git, + * we'll search .git in the parent directory next (added + * at the top of the loop). + */ if ((error = git_fs_path_dirname_r(&path, path.ptr)) < 0) goto out; - /* Once we've checked the directory (and .git if applicable), - * find the ceiling for a search. */ + /* + * Once we've checked the directory (and .git if + * applicable), find the ceiling for a search. + */ if (min_iterations && (--min_iterations == 0)) ceiling_offset = find_ceiling_dir_offset(path.ptr, ceiling_dirs); @@ -746,29 +816,96 @@ static int find_repo( break; } - if (workdir_path && !(flags & GIT_REPOSITORY_OPEN_BARE)) { - if (!git_str_len(gitdir_path)) - git_str_clear(workdir_path); - else if ((error = git_fs_path_dirname_r(workdir_path, path.ptr)) < 0 || - (error = git_fs_path_to_dir(workdir_path)) < 0) + if (!(flags & GIT_REPOSITORY_OPEN_BARE)) { + if (!git_str_len(&out->gitdir)) + git_str_clear(&out->workdir); + else if ((error = git_fs_path_dirname_r(&out->workdir, path.ptr)) < 0 || + (error = git_fs_path_to_dir(&out->workdir)) < 0) goto out; } - /* If we didn't find the repository, and we don't have any other error - * to report, report that. */ - if (!git_str_len(gitdir_path)) { - git_error_set(GIT_ERROR_REPOSITORY, "could not find repository from '%s'", start_path); + /* If we didn't find the repository, and we don't have any other + * error to report, report that. */ + if (!git_str_len(&out->gitdir)) { + git_error_set(GIT_ERROR_REPOSITORY, "could not find repository at '%s'", start_path); error = GIT_ENOTFOUND; goto out; } out: + if (error) + repo_paths_dispose(out); + git_str_dispose(&path); git_str_dispose(&repo_link); git_str_dispose(&common_link); return error; } +static int find_repo( + struct repo_paths *out, + const char *start_path, + const char *ceiling_dirs, + uint32_t flags) +{ + bool use_env = !!(flags & GIT_REPOSITORY_OPEN_FROM_ENV); + git_str gitdir_buf = GIT_STR_INIT, + ceiling_dirs_buf = GIT_STR_INIT, + across_fs_buf = GIT_STR_INIT; + int error; + + if (use_env && !start_path) { + error = git__getenv(&gitdir_buf, "GIT_DIR"); + + if (!error) { + start_path = gitdir_buf.ptr; + flags |= GIT_REPOSITORY_OPEN_NO_SEARCH; + flags |= GIT_REPOSITORY_OPEN_NO_DOTGIT; + } else if (error == GIT_ENOTFOUND) { + start_path = "."; + } else { + goto done; + } + } + + if (use_env && !ceiling_dirs) { + error = git__getenv(&ceiling_dirs_buf, + "GIT_CEILING_DIRECTORIES"); + + if (!error) + ceiling_dirs = ceiling_dirs_buf.ptr; + else if (error != GIT_ENOTFOUND) + goto done; + } + + if (use_env) { + error = git__getenv(&across_fs_buf, + "GIT_DISCOVERY_ACROSS_FILESYSTEM"); + + if (!error) { + int across_fs = 0; + + if ((error = git_config_parse_bool(&across_fs, + git_str_cstr(&across_fs_buf))) < 0) + goto done; + + if (across_fs) + flags |= GIT_REPOSITORY_OPEN_CROSS_FS; + } else if (error != GIT_ENOTFOUND) { + goto done; + } + } + + error = find_repo_traverse(out, start_path, ceiling_dirs, flags); + +done: + git_str_dispose(&gitdir_buf); + git_str_dispose(&ceiling_dirs_buf); + git_str_dispose(&across_fs_buf); + + return error; +} + static int obtain_config_and_set_oid_type( git_config **config_ptr, git_repository *repo) @@ -787,7 +924,7 @@ static int obtain_config_and_set_oid_type( goto out; if (config && - (error = check_repositoryformatversion(&version, config)) < 0) + (error = check_repositoryformatversion(&version, config)) < 0) goto out; if ((error = check_extensions(config, version)) < 0) @@ -817,7 +954,7 @@ int git_repository_open_bare( git_config *config; if ((error = git_fs_path_prettify_dir(&path, bare_path, NULL)) < 0 || - (error = is_valid_repository_path(&is_valid, &path, &common_path)) < 0) + (error = is_valid_repository_path(&is_valid, &path, &common_path, 0)) < 0) return error; if (!is_valid) { @@ -851,173 +988,22 @@ cleanup: return error; } -static int _git_repository_open_ext_from_env( - git_repository **out, - const char *start_path) +static int repo_load_namespace(git_repository *repo) { - git_repository *repo = NULL; - git_index *index = NULL; - git_odb *odb = NULL; - git_str dir_buf = GIT_STR_INIT; - git_str ceiling_dirs_buf = GIT_STR_INIT; - git_str across_fs_buf = GIT_STR_INIT; - git_str index_file_buf = GIT_STR_INIT; git_str namespace_buf = GIT_STR_INIT; - git_str object_dir_buf = GIT_STR_INIT; - git_str alts_buf = GIT_STR_INIT; - git_str work_tree_buf = GIT_STR_INIT; - git_str common_dir_buf = GIT_STR_INIT; - const char *ceiling_dirs = NULL; - unsigned flags = 0; int error; - if (!start_path) { - error = git__getenv(&dir_buf, "GIT_DIR"); - if (error == GIT_ENOTFOUND) { - git_error_clear(); - start_path = "."; - } else if (error < 0) - goto error; - else { - start_path = git_str_cstr(&dir_buf); - flags |= GIT_REPOSITORY_OPEN_NO_SEARCH; - flags |= GIT_REPOSITORY_OPEN_NO_DOTGIT; - } - } - - error = git__getenv(&ceiling_dirs_buf, "GIT_CEILING_DIRECTORIES"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - else - ceiling_dirs = git_str_cstr(&ceiling_dirs_buf); - - error = git__getenv(&across_fs_buf, "GIT_DISCOVERY_ACROSS_FILESYSTEM"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - else { - int across_fs = 0; - error = git_config_parse_bool(&across_fs, git_str_cstr(&across_fs_buf)); - if (error < 0) - goto error; - if (across_fs) - flags |= GIT_REPOSITORY_OPEN_CROSS_FS; - } - - error = git__getenv(&index_file_buf, "GIT_INDEX_FILE"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - else { - error = git_index_open(&index, git_str_cstr(&index_file_buf)); - if (error < 0) - goto error; - } + if (!repo->use_env) + return 0; error = git__getenv(&namespace_buf, "GIT_NAMESPACE"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - - error = git__getenv(&object_dir_buf, "GIT_OBJECT_DIRECTORY"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - else { - error = git_odb__open(&odb, git_str_cstr(&object_dir_buf), NULL); - if (error < 0) - goto error; - } - - error = git__getenv(&work_tree_buf, "GIT_WORK_TREE"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - else { - git_error_set(GIT_ERROR_INVALID, "GIT_WORK_TREE unimplemented"); - error = GIT_ERROR; - goto error; - } - - error = git__getenv(&work_tree_buf, "GIT_COMMON_DIR"); - if (error == GIT_ENOTFOUND) - git_error_clear(); - else if (error < 0) - goto error; - else { - git_error_set(GIT_ERROR_INVALID, "GIT_COMMON_DIR unimplemented"); - error = GIT_ERROR; - goto error; - } - - error = git_repository_open_ext(&repo, start_path, flags, ceiling_dirs); - if (error < 0) - goto error; - - if (odb) - git_repository_set_odb(repo, odb); - - error = git__getenv(&alts_buf, "GIT_ALTERNATE_OBJECT_DIRECTORIES"); - if (error == GIT_ENOTFOUND) { - git_error_clear(); - error = 0; - } else if (error < 0) - goto error; - else { - const char *end; - char *alt, *sep; - if (!odb) { - error = git_repository_odb(&odb, repo); - if (error < 0) - goto error; - } - end = git_str_cstr(&alts_buf) + git_str_len(&alts_buf); - for (sep = alt = alts_buf.ptr; sep != end; alt = sep+1) { - for (sep = alt; *sep && *sep != GIT_PATH_LIST_SEPARATOR; sep++) - ; - if (*sep) - *sep = '\0'; - error = git_odb_add_disk_alternate(odb, alt); - if (error < 0) - goto error; - } - } + if (error == 0) + repo->namespace = git_str_detach(&namespace_buf); + else if (error != GIT_ENOTFOUND) + return error; - if (git_str_len(&namespace_buf)) { - error = git_repository_set_namespace(repo, git_str_cstr(&namespace_buf)); - if (error < 0) - goto error; - } - - git_repository_set_index(repo, index); - - if (out) { - *out = repo; - goto success; - } -error: - git_repository_free(repo); -success: - git_odb_free(odb); - git_index_free(index); - git_str_dispose(&common_dir_buf); - git_str_dispose(&work_tree_buf); - git_str_dispose(&alts_buf); - git_str_dispose(&object_dir_buf); - git_str_dispose(&namespace_buf); - git_str_dispose(&index_file_buf); - git_str_dispose(&across_fs_buf); - git_str_dispose(&ceiling_dirs_buf); - git_str_dispose(&dir_buf); - return error; + return 0; } static int repo_is_worktree(unsigned *out, const git_repository *repo) @@ -1049,21 +1035,16 @@ int git_repository_open_ext( unsigned int flags, const char *ceiling_dirs) { - int error; - unsigned is_worktree; - git_str gitdir = GIT_STR_INIT, workdir = GIT_STR_INIT, - gitlink = GIT_STR_INIT, commondir = GIT_STR_INIT; + struct repo_paths paths = { GIT_STR_INIT }; git_repository *repo = NULL; git_config *config = NULL; - - if (flags & GIT_REPOSITORY_OPEN_FROM_ENV) - return _git_repository_open_ext_from_env(repo_ptr, start_path); + unsigned is_worktree; + int error; if (repo_ptr) *repo_ptr = NULL; - error = find_repo( - &gitdir, &workdir, &gitlink, &commondir, start_path, flags, ceiling_dirs); + error = find_repo(&paths, start_path, ceiling_dirs, flags); if (error < 0 || !repo_ptr) goto cleanup; @@ -1071,20 +1052,23 @@ int git_repository_open_ext( repo = repository_alloc(); GIT_ERROR_CHECK_ALLOC(repo); - repo->gitdir = git_str_detach(&gitdir); + repo->use_env = !!(flags & GIT_REPOSITORY_OPEN_FROM_ENV); + + repo->gitdir = git_str_detach(&paths.gitdir); GIT_ERROR_CHECK_ALLOC(repo->gitdir); - if (gitlink.size) { - repo->gitlink = git_str_detach(&gitlink); + if (paths.gitlink.size) { + repo->gitlink = git_str_detach(&paths.gitlink); GIT_ERROR_CHECK_ALLOC(repo->gitlink); } - if (commondir.size) { - repo->commondir = git_str_detach(&commondir); + if (paths.commondir.size) { + repo->commondir = git_str_detach(&paths.commondir); GIT_ERROR_CHECK_ALLOC(repo->commondir); } if ((error = repo_is_worktree(&is_worktree, repo)) < 0) goto cleanup; + repo->is_worktree = is_worktree; error = obtain_config_and_set_oid_type(&config, repo); @@ -1096,10 +1080,13 @@ int git_repository_open_ext( } else { if (config && ((error = load_config_data(repo, config)) < 0 || - (error = load_workdir(repo, config, &workdir)) < 0)) + (error = load_workdir(repo, config, &paths.workdir)) < 0)) goto cleanup; } + if ((error = repo_load_namespace(repo)) < 0) + goto cleanup; + /* * Ensure that the git directory and worktree are * owned by the current user. @@ -1109,10 +1096,7 @@ int git_repository_open_ext( goto cleanup; cleanup: - git_str_dispose(&gitdir); - git_str_dispose(&workdir); - git_str_dispose(&gitlink); - git_str_dispose(&commondir); + repo_paths_dispose(&paths); git_config_free(config); if (error < 0) @@ -1180,11 +1164,17 @@ int git_repository_discover( int across_fs, const char *ceiling_dirs) { + struct repo_paths paths = { GIT_STR_INIT }; uint32_t flags = across_fs ? GIT_REPOSITORY_OPEN_CROSS_FS : 0; + int error; GIT_ASSERT_ARG(start_path); - GIT_BUF_WRAP_PRIVATE(out, find_repo, NULL, NULL, NULL, start_path, flags, ceiling_dirs); + if ((error = find_repo(&paths, start_path, ceiling_dirs, flags)) == 0) + error = git_buf_fromstr(out, &paths.gitdir); + + repo_paths_dispose(&paths); + return error; } static int load_config( @@ -1255,32 +1245,81 @@ static const char *path_unless_empty(git_str *buf) return git_str_len(buf) > 0 ? git_str_cstr(buf) : NULL; } +GIT_INLINE(int) config_path_system(git_str *out, bool use_env) +{ + if (use_env) { + git_str no_system_buf = GIT_STR_INIT; + int no_system = 0; + int error; + + error = git__getenv(&no_system_buf, "GIT_CONFIG_NOSYSTEM"); + + if (error && error != GIT_ENOTFOUND) + return error; + + error = git_config_parse_bool(&no_system, no_system_buf.ptr); + git_str_dispose(&no_system_buf); + + if (no_system) + return 0; + + error = git__getenv(out, "GIT_CONFIG_SYSTEM"); + + if (error == 0 || error != GIT_ENOTFOUND) + return 0; + } + + git_config__find_system(out); + return 0; +} + +GIT_INLINE(int) config_path_global(git_str *out, bool use_env) +{ + if (use_env) { + int error = git__getenv(out, "GIT_CONFIG_GLOBAL"); + + if (error == 0 || error != GIT_ENOTFOUND) + return 0; + } + + git_config__find_global(out); + return 0; +} + int git_repository_config__weakptr(git_config **out, git_repository *repo) { int error = 0; if (repo->_config == NULL) { + git_str system_buf = GIT_STR_INIT; git_str global_buf = GIT_STR_INIT; git_str xdg_buf = GIT_STR_INIT; - git_str system_buf = GIT_STR_INIT; git_str programdata_buf = GIT_STR_INIT; + bool use_env = repo->use_env; git_config *config; - git_config__find_global(&global_buf); - git_config__find_xdg(&xdg_buf); - git_config__find_system(&system_buf); - git_config__find_programdata(&programdata_buf); + if (!(error = config_path_system(&system_buf, use_env)) && + !(error = config_path_global(&global_buf, use_env))) { + git_config__find_xdg(&xdg_buf); + git_config__find_programdata(&programdata_buf); + } - /* If there is no global file, open a backend for it anyway */ - if (git_str_len(&global_buf) == 0) - git_config__global_location(&global_buf); + if (!error) { + /* + * If there is no global file, open a backend + * for it anyway. + */ + if (git_str_len(&global_buf) == 0) + git_config__global_location(&global_buf); + + error = load_config( + &config, repo, + path_unless_empty(&global_buf), + path_unless_empty(&xdg_buf), + path_unless_empty(&system_buf), + path_unless_empty(&programdata_buf)); + } - error = load_config( - &config, repo, - path_unless_empty(&global_buf), - path_unless_empty(&xdg_buf), - path_unless_empty(&system_buf), - path_unless_empty(&programdata_buf)); if (!error) { GIT_REFCOUNT_OWN(config, repo); @@ -1329,6 +1368,56 @@ int git_repository_set_config(git_repository *repo, git_config *config) return 0; } +static int repository_odb_path(git_str *out, git_repository *repo) +{ + int error = GIT_ENOTFOUND; + + if (repo->use_env) + error = git__getenv(out, "GIT_OBJECT_DIRECTORY"); + + if (error == GIT_ENOTFOUND) + error = git_repository__item_path(out, repo, + GIT_REPOSITORY_ITEM_OBJECTS); + + return error; +} + +static int repository_odb_alternates( + git_odb *odb, + git_repository *repo) +{ + git_str alternates = GIT_STR_INIT; + char *sep, *alt; + int error; + + if (!repo->use_env) + return 0; + + error = git__getenv(&alternates, "GIT_ALTERNATE_OBJECT_DIRECTORIES"); + + if (error != 0) + return (error == GIT_ENOTFOUND) ? 0 : error; + + alt = alternates.ptr; + + while (*alt) { + sep = strchr(alt, GIT_PATH_LIST_SEPARATOR); + + if (sep) + *sep = '\0'; + + error = git_odb_add_disk_alternate(odb, alt); + + if (sep) + alt = sep + 1; + else + break; + } + + git_str_dispose(&alternates); + return 0; +} + int git_repository_odb__weakptr(git_odb **out, git_repository *repo) { int error = 0; @@ -1344,9 +1433,9 @@ int git_repository_odb__weakptr(git_odb **out, git_repository *repo) odb_opts.oid_type = repo->oid_type; - if ((error = git_repository__item_path(&odb_path, repo, - GIT_REPOSITORY_ITEM_OBJECTS)) < 0 || - (error = git_odb__new(&odb, &odb_opts)) < 0) + if ((error = repository_odb_path(&odb_path, repo)) < 0 || + (error = git_odb__new(&odb, &odb_opts)) < 0 || + (error = repository_odb_alternates(odb, repo)) < 0) return error; GIT_REFCOUNT_OWN(odb, repo); @@ -1430,6 +1519,20 @@ int git_repository_set_refdb(git_repository *repo, git_refdb *refdb) return 0; } +static int repository_index_path(git_str *out, git_repository *repo) +{ + int error = GIT_ENOTFOUND; + + if (repo->use_env) + error = git__getenv(out, "GIT_INDEX_FILE"); + + if (error == GIT_ENOTFOUND) + error = git_repository__item_path(out, repo, + GIT_REPOSITORY_ITEM_INDEX); + + return error; +} + int git_repository_index__weakptr(git_index **out, git_repository *repo) { int error = 0; @@ -1441,7 +1544,7 @@ int git_repository_index__weakptr(git_index **out, git_repository *repo) git_str index_path = GIT_STR_INIT; git_index *index; - if ((error = git_str_joinpath(&index_path, repo->gitdir, GIT_INDEX_FILE)) < 0) + if ((error = repository_index_path(&index_path, repo)) < 0) return error; error = git_index_open(&index, index_path.ptr); @@ -1632,7 +1735,7 @@ static const char *builtin_extensions[] = { "objectformat" }; -static git_vector user_extensions = GIT_VECTOR_INIT; +static git_vector user_extensions = { 0, git__strcmp_cb }; static int check_valid_extension(const git_config_entry *entry, void *payload) { @@ -1773,7 +1876,7 @@ int git_repository__extensions(char ***out, size_t *out_len) char *extension; size_t i, j; - if (git_vector_init(&extensions, 8, NULL) < 0) + if (git_vector_init(&extensions, 8, git__strcmp_cb) < 0) return -1; for (i = 0; i < ARRAY_SIZE(builtin_extensions); i++) { @@ -1805,21 +1908,49 @@ int git_repository__extensions(char ***out, size_t *out_len) return -1; } + git_vector_sort(&extensions); + *out = (char **)git_vector_detach(out_len, NULL, &extensions); return 0; } +static int dup_ext_err(void **old, void *extension) +{ + GIT_UNUSED(old); + GIT_UNUSED(extension); + return GIT_EEXISTS; +} + int git_repository__set_extensions(const char **extensions, size_t len) { char *extension; - size_t i; + size_t i, j; + int error; git_repository__free_extensions(); for (i = 0; i < len; i++) { - if ((extension = git__strdup(extensions[i])) == NULL || - git_vector_insert(&user_extensions, extension) < 0) + bool is_builtin = false; + + for (j = 0; j < ARRAY_SIZE(builtin_extensions); j++) { + if (strcmp(builtin_extensions[j], extensions[i]) == 0) { + is_builtin = true; + break; + } + } + + if (is_builtin) + continue; + + if ((extension = git__strdup(extensions[i])) == NULL) return -1; + + if ((error = git_vector_insert_sorted(&user_extensions, extension, dup_ext_err)) < 0) { + git__free(extension); + + if (error != GIT_EEXISTS) + return -1; + } } return 0; @@ -1888,7 +2019,7 @@ static bool is_filesystem_case_insensitive(const char *gitdir_path) * Return a configuration object with only the global and system * configurations; no repository-level configuration. */ -static int load_global_config(git_config **config) +static int load_global_config(git_config **config, bool use_env) { git_str global_buf = GIT_STR_INIT; git_str xdg_buf = GIT_STR_INIT; @@ -1896,16 +2027,17 @@ static int load_global_config(git_config **config) git_str programdata_buf = GIT_STR_INIT; int error; - git_config__find_global(&global_buf); - git_config__find_xdg(&xdg_buf); - git_config__find_system(&system_buf); - git_config__find_programdata(&programdata_buf); + if (!(error = config_path_system(&system_buf, use_env)) && + !(error = config_path_global(&global_buf, use_env))) { + git_config__find_xdg(&xdg_buf); + git_config__find_programdata(&programdata_buf); - error = load_config(config, NULL, - path_unless_empty(&global_buf), - path_unless_empty(&xdg_buf), - path_unless_empty(&system_buf), - path_unless_empty(&programdata_buf)); + error = load_config(config, NULL, + path_unless_empty(&global_buf), + path_unless_empty(&xdg_buf), + path_unless_empty(&system_buf), + path_unless_empty(&programdata_buf)); + } git_str_dispose(&global_buf); git_str_dispose(&xdg_buf); @@ -1915,7 +2047,7 @@ static int load_global_config(git_config **config) return error; } -static bool are_symlinks_supported(const char *wd_path) +static bool are_symlinks_supported(const char *wd_path, bool use_env) { git_config *config = NULL; int symlinks = 0; @@ -1928,10 +2060,12 @@ static bool are_symlinks_supported(const char *wd_path) * _not_ set, then we do not test or enable symlink support. */ #ifdef GIT_WIN32 - if (load_global_config(&config) < 0 || + if (load_global_config(&config, use_env) < 0 || git_config_get_bool(&symlinks, config, "core.symlinks") < 0 || !symlinks) goto done; +#else + GIT_UNUSED(use_env); #endif if (!(symlinks = git_fs_path_supports_symlinks(wd_path))) @@ -2004,7 +2138,8 @@ static int repo_init_fs_configs( const char *cfg_path, const char *repo_dir, const char *work_dir, - bool update_ignorecase) + bool update_ignorecase, + bool use_env) { int error = 0; @@ -2015,7 +2150,7 @@ static int repo_init_fs_configs( cfg, "core.filemode", is_chmod_supported(cfg_path))) < 0) return error; - if (!are_symlinks_supported(work_dir)) { + if (!are_symlinks_supported(work_dir, use_env)) { if ((error = git_config_set_bool(cfg, "core.symlinks", false)) < 0) return error; } else if (git_config_delete_entry(cfg, "core.symlinks") < 0) @@ -2052,6 +2187,7 @@ static int repo_init_config( git_config *config = NULL; bool is_bare = ((flags & GIT_REPOSITORY_INIT_BARE) != 0); bool is_reinit = ((flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0); + bool use_env = ((flags & GIT_REPOSITORY_OPEN_FROM_ENV) != 0); int version = GIT_REPO_VERSION_DEFAULT; if ((error = repo_local_config(&config, &cfg_path, NULL, repo_dir)) < 0) @@ -2072,7 +2208,8 @@ static int repo_init_config( SET_REPO_CONFIG(int32, "core.repositoryformatversion", version); if ((error = repo_init_fs_configs( - config, cfg_path.ptr, repo_dir, work_dir, !is_reinit)) < 0) + config, cfg_path.ptr, repo_dir, work_dir, + !is_reinit, use_env)) < 0) goto cleanup; if (!is_bare) { @@ -2136,8 +2273,8 @@ int git_repository_reinit_filesystem(git_repository *repo, int recurse) const char *repo_dir = git_repository_path(repo); if (!(error = repo_local_config(&config, &path, repo, repo_dir))) - error = repo_init_fs_configs( - config, path.ptr, repo_dir, git_repository_workdir(repo), true); + error = repo_init_fs_configs(config, path.ptr, repo_dir, + git_repository_workdir(repo), true, repo->use_env); git_config_free(config); git_str_dispose(&path); @@ -2606,7 +2743,7 @@ int git_repository_init_ext( wd = (opts->flags & GIT_REPOSITORY_INIT_BARE) ? NULL : git_str_cstr(&wd_path); - if ((error = is_valid_repository_path(&is_valid, &repo_path, &common_path)) < 0) + if ((error = is_valid_repository_path(&is_valid, &repo_path, &common_path, opts->flags)) < 0) goto out; if (is_valid) { diff --git a/src/libgit2/repository.h b/src/libgit2/repository.h index 75380ae53..9a36ef972 100644 --- a/src/libgit2/repository.h +++ b/src/libgit2/repository.h @@ -151,8 +151,9 @@ struct git_repository { git_array_t(git_str) reserved_names; - unsigned is_bare:1; - unsigned is_worktree:1; + unsigned use_env:1, + is_bare:1, + is_worktree:1; git_oid_t oid_type; unsigned int lru_counter; diff --git a/src/libgit2/streams/schannel.c b/src/libgit2/streams/schannel.c new file mode 100644 index 000000000..f09615819 --- /dev/null +++ b/src/libgit2/streams/schannel.c @@ -0,0 +1,715 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "streams/schannel.h" + +#ifdef GIT_SCHANNEL + +#define SECURITY_WIN32 + +#include <security.h> +#include <schannel.h> +#include <sspi.h> + +#include "stream.h" +#include "streams/socket.h" + +#ifndef SP_PROT_TLS1_2_CLIENT +# define SP_PROT_TLS1_2_CLIENT 2048 +#endif + +#ifndef SP_PROT_TLS1_3_CLIENT +# define SP_PROT_TLS1_3_CLIENT 8192 +#endif + +#ifndef SECBUFFER_ALERT +# define SECBUFFER_ALERT 17 +#endif + +#define READ_BLOCKSIZE (16 * 1024) + +typedef enum { + STATE_NONE = 0, + STATE_CRED = 1, + STATE_CONTEXT = 2, + STATE_CERTIFICATE = 3 +} schannel_state; + +typedef struct { + git_stream parent; + git_stream *io; + int owned; + bool connected; + wchar_t *host_w; + + schannel_state state; + + CredHandle cred; + CtxtHandle context; + SecPkgContext_StreamSizes stream_sizes; + + CERT_CONTEXT *certificate; + const CERT_CHAIN_CONTEXT *cert_chain; + git_cert_x509 x509; + + git_str plaintext_in; + git_str ciphertext_in; +} schannel_stream; + +static int connect_context(schannel_stream *st) +{ + SCHANNEL_CRED cred = { 0 }; + SECURITY_STATUS status = SEC_E_INTERNAL_ERROR; + DWORD context_flags; + static size_t MAX_RETRIES = 1024; + size_t retries; + ssize_t read_len; + int error = 0; + + if (st->owned && (error = git_stream_connect(st->io)) < 0) + return error; + + cred.dwVersion = SCHANNEL_CRED_VERSION; + cred.dwFlags = SCH_CRED_IGNORE_NO_REVOCATION_CHECK | + SCH_CRED_IGNORE_REVOCATION_OFFLINE | + SCH_CRED_MANUAL_CRED_VALIDATION | + SCH_CRED_NO_DEFAULT_CREDS | + SCH_CRED_NO_SERVERNAME_CHECK; + cred.grbitEnabledProtocols = SP_PROT_TLS1_2_CLIENT | + SP_PROT_TLS1_3_CLIENT; + + if (AcquireCredentialsHandleW(NULL, SCHANNEL_NAME_W, + SECPKG_CRED_OUTBOUND, NULL, &cred, NULL, + NULL, &st->cred, NULL) != SEC_E_OK) { + git_error_set(GIT_ERROR_OS, "could not acquire credentials handle"); + return -1; + } + + st->state = STATE_CRED; + + context_flags = ISC_REQ_ALLOCATE_MEMORY | + ISC_REQ_CONFIDENTIALITY | + ISC_REQ_REPLAY_DETECT | + ISC_REQ_SEQUENCE_DETECT | + ISC_REQ_STREAM; + + for (retries = 0; retries < MAX_RETRIES; retries++) { + SecBuffer input_buf[] = { + { (unsigned long)st->ciphertext_in.size, + SECBUFFER_TOKEN, + st->ciphertext_in.size ? st->ciphertext_in.ptr : NULL }, + { 0, SECBUFFER_EMPTY, NULL } + }; + SecBuffer output_buf[] = { { 0, SECBUFFER_TOKEN, NULL }, + { 0, SECBUFFER_ALERT, NULL } }; + + SecBufferDesc input_buf_desc = { SECBUFFER_VERSION, 2, input_buf }; + SecBufferDesc output_buf_desc = { SECBUFFER_VERSION, 2, output_buf }; + + status = InitializeSecurityContextW(&st->cred, + retries ? &st->context : NULL, st->host_w, + context_flags, 0, 0, retries ? &input_buf_desc : NULL, 0, + retries ? NULL : &st->context, &output_buf_desc, + &context_flags, NULL); + + if (status == SEC_E_OK || status == SEC_I_CONTINUE_NEEDED) { + st->state = STATE_CONTEXT; + + if (output_buf[0].cbBuffer > 0) { + error = git_stream__write_full(st->io, + output_buf[0].pvBuffer, + output_buf[0].cbBuffer, 0); + + FreeContextBuffer(output_buf[0].pvBuffer); + } + + /* handle any leftover, unprocessed data */ + if (input_buf[1].BufferType == SECBUFFER_EXTRA) { + GIT_ASSERT(st->ciphertext_in.size > input_buf[1].cbBuffer); + + git_str_consume_bytes(&st->ciphertext_in, + st->ciphertext_in.size - input_buf[1].cbBuffer); + } else { + git_str_clear(&st->ciphertext_in); + } + + if (error < 0 || status == SEC_E_OK) + break; + } else if (status == SEC_E_INCOMPLETE_MESSAGE) { + /* we need additional data from the client; */ + if (git_str_grow_by(&st->ciphertext_in, READ_BLOCKSIZE) < 0) { + error = -1; + break; + } + + if ((read_len = git_stream_read(st->io, + st->ciphertext_in.ptr + st->ciphertext_in.size, + (st->ciphertext_in.asize - st->ciphertext_in.size))) < 0) { + error = -1; + break; + } + + GIT_ASSERT((size_t)read_len <= + st->ciphertext_in.asize - st->ciphertext_in.size); + st->ciphertext_in.size += read_len; + } else { + git_error_set(GIT_ERROR_OS, + "could not initialize security context"); + error = -1; + break; + } + + GIT_ASSERT(st->ciphertext_in.size < ULONG_MAX); + } + + if (retries == MAX_RETRIES) { + git_error_set(GIT_ERROR_SSL, + "could not initialize security context: too many retries"); + error = -1; + } + + if (!error) { + if (QueryContextAttributesW(&st->context, + SECPKG_ATTR_STREAM_SIZES, + &st->stream_sizes) != SEC_E_OK) { + git_error_set(GIT_ERROR_SSL, + "could not query stream sizes"); + error = -1; + } + } + + return error; +} + +static int set_certificate_lookup_error(DWORD status) +{ + switch (status) { + case CERT_TRUST_IS_NOT_TIME_VALID: + git_error_set(GIT_ERROR_SSL, + "certificate is expired or not yet valid"); + break; + case CERT_TRUST_IS_REVOKED: + git_error_set(GIT_ERROR_SSL, "certificate is revoked"); + break; + case CERT_TRUST_IS_NOT_SIGNATURE_VALID: + case CERT_TRUST_IS_NOT_VALID_FOR_USAGE: + case CERT_TRUST_INVALID_EXTENSION: + case CERT_TRUST_INVALID_POLICY_CONSTRAINTS: + case CERT_TRUST_INVALID_BASIC_CONSTRAINTS: + case CERT_TRUST_INVALID_NAME_CONSTRAINTS: + case CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT: + case CERT_TRUST_HAS_NOT_DEFINED_NAME_CONSTRAINT: + case CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT: + case CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT: + case CERT_TRUST_NO_ISSUANCE_CHAIN_POLICY: + case CERT_TRUST_HAS_NOT_SUPPORTED_CRITICAL_EXT: + git_error_set(GIT_ERROR_SSL, "certificate is not valid"); + break; + case CERT_TRUST_IS_UNTRUSTED_ROOT: + case CERT_TRUST_IS_CYCLIC: + case CERT_TRUST_IS_EXPLICIT_DISTRUST: + git_error_set(GIT_ERROR_SSL, "certificate is not trusted"); + break; + case CERT_TRUST_REVOCATION_STATUS_UNKNOWN: + git_error_set(GIT_ERROR_SSL, + "certificate revocation status could not be verified"); + break; + case CERT_TRUST_IS_OFFLINE_REVOCATION: + git_error_set(GIT_ERROR_SSL, + "certificate revocation is offline or stale"); + break; + case CERT_TRUST_HAS_WEAK_SIGNATURE: + git_error_set(GIT_ERROR_SSL, "certificate has a weak signature"); + break; + default: + git_error_set(GIT_ERROR_SSL, + "unknown certificate lookup failure: %d", status); + return -1; + } + + return GIT_ECERTIFICATE; +} + +static int set_certificate_validation_error(DWORD status) +{ + switch (status) { + case TRUST_E_CERT_SIGNATURE: + git_error_set(GIT_ERROR_SSL, + "the certificate cannot be verified"); + break; + case CRYPT_E_REVOKED: + git_error_set(GIT_ERROR_SSL, + "the certificate or signature has been revoked"); + break; + case CERT_E_UNTRUSTEDROOT: + git_error_set(GIT_ERROR_SSL, + "the certificate root is not trusted"); + break; + case CERT_E_UNTRUSTEDTESTROOT: + git_error_set(GIT_ERROR_SSL, + "the certificate root is a test certificate"); + break; + case CERT_E_CHAINING: + git_error_set(GIT_ERROR_SSL, + "the certificate chain is invalid"); + break; + case CERT_E_WRONG_USAGE: + case CERT_E_PURPOSE: + git_error_set(GIT_ERROR_SSL, + "the certificate is not valid for this usage"); + break; + case CERT_E_EXPIRED: + git_error_set(GIT_ERROR_SSL, + "certificate is expired or not yet valid"); + break; + case CERT_E_INVALID_NAME: + case CERT_E_CN_NO_MATCH: + git_error_set(GIT_ERROR_SSL, + "certificate is not valid for this hostname"); + break; + case CERT_E_INVALID_POLICY: + case TRUST_E_BASIC_CONSTRAINTS: + case CERT_E_CRITICAL: + case CERT_E_VALIDITYPERIODNESTING: + git_error_set(GIT_ERROR_SSL, "certificate is not valid"); + break; + case CRYPT_E_NO_REVOCATION_CHECK: + git_error_set(GIT_ERROR_SSL, + "certificate revocation status could not be verified"); + break; + case CRYPT_E_REVOCATION_OFFLINE: + git_error_set(GIT_ERROR_SSL, + "certificate revocation is offline or stale"); + break; + case CERT_E_ROLE: + git_error_set(GIT_ERROR_SSL, "certificate authority is not valid"); + break; + default: + git_error_set(GIT_ERROR_SSL, + "unknown certificate policy checking failure: %d", + status); + return -1; + } + + return GIT_ECERTIFICATE; +} + +static int check_certificate(schannel_stream* st) +{ + CERT_CHAIN_PARA cert_chain_parameters; + SSL_EXTRA_CERT_CHAIN_POLICY_PARA ssl_policy_parameters; + CERT_CHAIN_POLICY_PARA cert_policy_parameters = + { sizeof(CERT_CHAIN_POLICY_PARA), 0, &ssl_policy_parameters }; + CERT_CHAIN_POLICY_STATUS cert_policy_status; + + memset(&cert_chain_parameters, 0, sizeof(CERT_CHAIN_PARA)); + cert_chain_parameters.cbSize = sizeof(CERT_CHAIN_PARA); + + if (QueryContextAttributesW(&st->context, + SECPKG_ATTR_REMOTE_CERT_CONTEXT, + &st->certificate) != SEC_E_OK) { + git_error_set(GIT_ERROR_OS, + "could not query remote certificate context"); + return -1; + } + + /* TODO: do we really want to do revokcation checking ? */ + if (!CertGetCertificateChain(NULL, st->certificate, NULL, + st->certificate->hCertStore, &cert_chain_parameters, + CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT, + NULL, &st->cert_chain)) { + git_error_set(GIT_ERROR_OS, "could not query remote certificate chain"); + CertFreeCertificateContext(st->certificate); + return -1; + } + + st->state = STATE_CERTIFICATE; + + /* Set up the x509 certificate data for future callbacks */ + + st->x509.parent.cert_type = GIT_CERT_X509; + st->x509.data = st->certificate->pbCertEncoded; + st->x509.len = st->certificate->cbCertEncoded; + + /* Handle initial certificate validation */ + + if (st->cert_chain->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR) + return set_certificate_lookup_error(st->cert_chain->TrustStatus.dwErrorStatus); + + ssl_policy_parameters.cbSize = sizeof(SSL_EXTRA_CERT_CHAIN_POLICY_PARA); + ssl_policy_parameters.dwAuthType = AUTHTYPE_SERVER; + ssl_policy_parameters.pwszServerName = st->host_w; + + if (!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, + st->cert_chain, &cert_policy_parameters, + &cert_policy_status)) { + git_error_set(GIT_ERROR_OS, "could not verify certificate chain policy"); + return -1; + } + + if (cert_policy_status.dwError != SEC_E_OK) + return set_certificate_validation_error(cert_policy_status.dwError); + + return 0; +} + +static int schannel_connect(git_stream *stream) +{ + schannel_stream *st = (schannel_stream *)stream; + int error; + + GIT_ASSERT(st->state == STATE_NONE); + + if ((error = connect_context(st)) < 0 || + (error = check_certificate(st)) < 0) + return error; + + st->connected = 1; + return 0; +} + +static int schannel_certificate(git_cert **out, git_stream *stream) +{ + schannel_stream *st = (schannel_stream *)stream; + + *out = &st->x509.parent; + return 0; +} + +static int schannel_set_proxy( + git_stream *stream, + const git_proxy_options *proxy_options) +{ + schannel_stream *st = (schannel_stream *)stream; + return git_stream_set_proxy(st->io, proxy_options); +} + +static ssize_t schannel_write( + git_stream *stream, + const char *data, + size_t data_len, + int flags) +{ + schannel_stream *st = (schannel_stream *)stream; + SecBuffer encrypt_buf[3]; + SecBufferDesc encrypt_buf_desc = { SECBUFFER_VERSION, 3, encrypt_buf }; + git_str ciphertext_out = GIT_STR_INIT; + ssize_t total_len = 0; + + GIT_UNUSED(flags); + + if (data_len > SSIZE_MAX) + data_len = SSIZE_MAX; + + git_str_init(&ciphertext_out, + st->stream_sizes.cbHeader + + st->stream_sizes.cbMaximumMessage + + st->stream_sizes.cbTrailer); + + while (data_len > 0) { + size_t message_len = min(data_len, st->stream_sizes.cbMaximumMessage); + size_t ciphertext_len, ciphertext_written = 0; + + encrypt_buf[0].BufferType = SECBUFFER_STREAM_HEADER; + encrypt_buf[0].cbBuffer = st->stream_sizes.cbHeader; + encrypt_buf[0].pvBuffer = ciphertext_out.ptr; + + encrypt_buf[1].BufferType = SECBUFFER_DATA; + encrypt_buf[1].cbBuffer = (unsigned long)message_len; + encrypt_buf[1].pvBuffer = + ciphertext_out.ptr + st->stream_sizes.cbHeader; + + encrypt_buf[2].BufferType = SECBUFFER_STREAM_TRAILER; + encrypt_buf[2].cbBuffer = st->stream_sizes.cbTrailer; + encrypt_buf[2].pvBuffer = + ciphertext_out.ptr + st->stream_sizes.cbHeader + + message_len; + + memcpy(ciphertext_out.ptr + st->stream_sizes.cbHeader, data, message_len); + + if (EncryptMessage(&st->context, 0, &encrypt_buf_desc, 0) != SEC_E_OK) { + git_error_set(GIT_ERROR_OS, "could not encrypt tls message"); + total_len = -1; + goto done; + } + + ciphertext_len = encrypt_buf[0].cbBuffer + + encrypt_buf[1].cbBuffer + + encrypt_buf[2].cbBuffer; + + while (ciphertext_written < ciphertext_len) { + ssize_t chunk_len = git_stream_write(st->io, + ciphertext_out.ptr + ciphertext_written, + ciphertext_len - ciphertext_written, 0); + + if (chunk_len < 0) { + total_len = -1; + goto done; + } + + ciphertext_len -= chunk_len; + ciphertext_written += chunk_len; + } + + total_len += message_len; + + data += message_len; + data_len -= message_len; + } + +done: + git_str_dispose(&ciphertext_out); + return total_len; +} + +static ssize_t schannel_read(git_stream *stream, void *_data, size_t data_len) +{ + schannel_stream *st = (schannel_stream *)stream; + char *data = (char *)_data; + SecBuffer decrypt_buf[4]; + SecBufferDesc decrypt_buf_desc = { SECBUFFER_VERSION, 4, decrypt_buf }; + SECURITY_STATUS status; + ssize_t chunk_len, total_len = 0; + + if (data_len > SSIZE_MAX) + data_len = SSIZE_MAX; + + /* + * Loop until we have some bytes to return - we may have decrypted + * bytes queued or ciphertext from the wire that we can decrypt and + * return. Return any queued bytes if they're available to avoid a + * network read, which may block. We may return less than the + * caller requested, and they can retry for an actual network + */ + while ((size_t)total_len < data_len) { + if (st->plaintext_in.size > 0) { + size_t copy_len = min(st->plaintext_in.size, data_len); + + memcpy(data, st->plaintext_in.ptr, copy_len); + git_str_consume_bytes(&st->plaintext_in, copy_len); + + data += copy_len; + data_len -= copy_len; + + total_len += copy_len; + + continue; + } + + if (st->ciphertext_in.size > 0) { + decrypt_buf[0].BufferType = SECBUFFER_DATA; + decrypt_buf[0].cbBuffer = (unsigned long)min(st->ciphertext_in.size, ULONG_MAX); + decrypt_buf[0].pvBuffer = st->ciphertext_in.ptr; + + decrypt_buf[1].BufferType = SECBUFFER_EMPTY; + decrypt_buf[1].cbBuffer = 0; + decrypt_buf[1].pvBuffer = NULL; + + decrypt_buf[2].BufferType = SECBUFFER_EMPTY; + decrypt_buf[2].cbBuffer = 0; + decrypt_buf[2].pvBuffer = NULL; + + decrypt_buf[3].BufferType = SECBUFFER_EMPTY; + decrypt_buf[3].cbBuffer = 0; + decrypt_buf[3].pvBuffer = NULL; + + status = DecryptMessage(&st->context, &decrypt_buf_desc, 0, NULL); + + if (status == SEC_E_OK) { + GIT_ASSERT(decrypt_buf[0].BufferType == SECBUFFER_STREAM_HEADER); + GIT_ASSERT(decrypt_buf[1].BufferType == SECBUFFER_DATA); + GIT_ASSERT(decrypt_buf[2].BufferType == SECBUFFER_STREAM_TRAILER); + + if (git_str_put(&st->plaintext_in, decrypt_buf[1].pvBuffer, decrypt_buf[1].cbBuffer) < 0) { + total_len = -1; + goto done; + } + + if (decrypt_buf[3].BufferType == SECBUFFER_EXTRA) { + git_str_consume_bytes(&st->ciphertext_in, (st->ciphertext_in.size - decrypt_buf[3].cbBuffer)); + } else { + git_str_clear(&st->ciphertext_in); + } + + continue; + } else if (status == SEC_E_CONTEXT_EXPIRED) { + break; + } else if (status != SEC_E_INCOMPLETE_MESSAGE) { + git_error_set(GIT_ERROR_SSL, "could not decrypt tls message"); + total_len = -1; + goto done; + } + } + + if (total_len != 0) + break; + + if (git_str_grow_by(&st->ciphertext_in, READ_BLOCKSIZE) < 0) { + total_len = -1; + goto done; + } + + if ((chunk_len = git_stream_read(st->io, st->ciphertext_in.ptr + st->ciphertext_in.size, st->ciphertext_in.asize - st->ciphertext_in.size)) < 0) { + total_len = -1; + goto done; + } + + st->ciphertext_in.size += chunk_len; + } + +done: + return total_len; +} + +static int schannel_close(git_stream *stream) +{ + schannel_stream *st = (schannel_stream *)stream; + int error = 0; + + if (st->connected) { + SecBuffer shutdown_buf; + SecBufferDesc shutdown_buf_desc = + { SECBUFFER_VERSION, 1, &shutdown_buf }; + DWORD shutdown_message = SCHANNEL_SHUTDOWN, shutdown_flags; + + shutdown_buf.BufferType = SECBUFFER_TOKEN; + shutdown_buf.cbBuffer = sizeof(DWORD); + shutdown_buf.pvBuffer = &shutdown_message; + + if (ApplyControlToken(&st->context, &shutdown_buf_desc) != SEC_E_OK) { + git_error_set(GIT_ERROR_SSL, "could not shutdown stream"); + error = -1; + } + + shutdown_buf.BufferType = SECBUFFER_TOKEN; + shutdown_buf.cbBuffer = 0; + shutdown_buf.pvBuffer = NULL; + + shutdown_flags = ISC_REQ_ALLOCATE_MEMORY | + ISC_REQ_CONFIDENTIALITY | + ISC_REQ_REPLAY_DETECT | + ISC_REQ_SEQUENCE_DETECT | + ISC_REQ_STREAM; + + if (InitializeSecurityContext(&st->cred, &st->context, + NULL, shutdown_flags, 0, 0, + &shutdown_buf_desc, 0, NULL, + &shutdown_buf_desc, &shutdown_flags, + NULL) == SEC_E_OK) { + if (shutdown_buf.cbBuffer > 0) { + if (git_stream__write_full(st->io, + shutdown_buf.pvBuffer, + shutdown_buf.cbBuffer, 0) < 0) + error = -1; + + FreeContextBuffer(shutdown_buf.pvBuffer); + } + } + } + + st->connected = false; + + if (st->owned && git_stream_close(st->io) < 0) + error = -1; + + return error; +} + +static void schannel_free(git_stream *stream) +{ + schannel_stream *st = (schannel_stream *)stream; + + if (st->state >= STATE_CERTIFICATE) { + CertFreeCertificateContext(st->certificate); + CertFreeCertificateChain(st->cert_chain); + } + + if (st->state >= STATE_CONTEXT) + DeleteSecurityContext(&st->context); + + if (st->state >= STATE_CRED) + FreeCredentialsHandle(&st->cred); + + st->state = STATE_NONE; + + git_str_dispose(&st->ciphertext_in); + git_str_dispose(&st->plaintext_in); + + git__free(st->host_w); + + if (st->owned) + git_stream_free(st->io); + + git__free(st); +} + +static int schannel_stream_wrap( + git_stream **out, + git_stream *in, + const char *host, + int owned) +{ + schannel_stream *st; + + st = git__calloc(1, sizeof(schannel_stream)); + GIT_ERROR_CHECK_ALLOC(st); + + st->io = in; + st->owned = owned; + + if (git_utf8_to_16_alloc(&st->host_w, host) < 0) { + git__free(st); + return -1; + } + + st->parent.version = GIT_STREAM_VERSION; + st->parent.encrypted = 1; + st->parent.proxy_support = git_stream_supports_proxy(st->io); + st->parent.connect = schannel_connect; + st->parent.certificate = schannel_certificate; + st->parent.set_proxy = schannel_set_proxy; + st->parent.read = schannel_read; + st->parent.write = schannel_write; + st->parent.close = schannel_close; + st->parent.free = schannel_free; + + *out = (git_stream *)st; + return 0; +} + +extern int git_schannel_stream_new( + git_stream **out, + const char *host, + const char *port) +{ + git_stream *stream; + int error; + + GIT_ASSERT_ARG(out); + GIT_ASSERT_ARG(host); + GIT_ASSERT_ARG(port); + + if ((error = git_socket_stream_new(&stream, host, port)) < 0) + return error; + + if ((error = schannel_stream_wrap(out, stream, host, 1)) < 0) { + git_stream_close(stream); + git_stream_free(stream); + } + + return error; +} + +extern int git_schannel_stream_wrap( + git_stream **out, + git_stream *in, + const char *host) +{ + return schannel_stream_wrap(out, in, host, 0); +} + +#endif diff --git a/src/libgit2/streams/schannel.h b/src/libgit2/streams/schannel.h new file mode 100644 index 000000000..3584970d1 --- /dev/null +++ b/src/libgit2/streams/schannel.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_steams_schannel_h__ +#define INCLUDE_steams_schannel_h__ + +#include "common.h" + +#include "git2/sys/stream.h" + +#ifdef GIT_SCHANNEL + +extern int git_schannel_stream_new( + git_stream **out, + const char *host, + const char *port); + +extern int git_schannel_stream_wrap( + git_stream **out, + git_stream *in, + const char *host); + +#endif + +#endif diff --git a/src/libgit2/streams/socket.c b/src/libgit2/streams/socket.c index 908e8c02f..8f23e746e 100644 --- a/src/libgit2/streams/socket.c +++ b/src/libgit2/streams/socket.c @@ -10,22 +10,23 @@ #include "posix.h" #include "netops.h" #include "registry.h" +#include "runtime.h" #include "stream.h" #ifndef _WIN32 -# include <sys/types.h> -# include <sys/socket.h> -# include <sys/select.h> -# include <sys/time.h> -# include <netdb.h> -# include <netinet/in.h> -# include <arpa/inet.h> +# include <sys/types.h> +# include <sys/socket.h> +# include <sys/select.h> +# include <sys/time.h> +# include <netdb.h> +# include <netinet/in.h> +# include <arpa/inet.h> #else -# include <winsock2.h> -# include <ws2tcpip.h> -# ifdef _MSC_VER -# pragma comment(lib, "ws2_32") -# endif +# include <winsock2.h> +# include <ws2tcpip.h> +# ifdef _MSC_VER +# pragma comment(lib, "ws2_32") +# endif #endif #ifdef GIT_WIN32 @@ -54,11 +55,8 @@ static int close_socket(GIT_SOCKET s) return 0; #ifdef GIT_WIN32 - if (SOCKET_ERROR == closesocket(s)) - return -1; - - if (0 != WSACleanup()) { - git_error_set(GIT_ERROR_OS, "winsock cleanup failed"); + if (closesocket(s) != 0) { + net_set_error("could not close socket"); return -1; } @@ -77,23 +75,6 @@ static int socket_connect(git_stream *stream) GIT_SOCKET s = INVALID_SOCKET; int ret; -#ifdef GIT_WIN32 - /* on win32, the WSA context needs to be initialized - * before any socket calls can be performed */ - WSADATA wsd; - - if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) { - git_error_set(GIT_ERROR_OS, "winsock init failed"); - return -1; - } - - if (LOBYTE(wsd.wVersion) != 2 || HIBYTE(wsd.wVersion) != 2) { - WSACleanup(); - git_error_set(GIT_ERROR_OS, "winsock init failed"); - return -1; - } -#endif - memset(&hints, 0x0, sizeof(struct addrinfo)); hints.ai_socktype = SOCK_STREAM; hints.ai_family = AF_UNSPEC; @@ -240,3 +221,42 @@ int git_socket_stream_new( return init(out, host, port); } + +#ifdef GIT_WIN32 + +static void socket_stream_global_shutdown(void) +{ + WSACleanup(); +} + +int git_socket_stream_global_init(void) +{ + WORD winsock_version; + WSADATA wsa_data; + + winsock_version = MAKEWORD(2, 2); + + if (WSAStartup(winsock_version, &wsa_data) != 0) { + git_error_set(GIT_ERROR_OS, "could not initialize Windows Socket Library"); + return -1; + } + + if (LOBYTE(wsa_data.wVersion) != 2 || + HIBYTE(wsa_data.wVersion) != 2) { + git_error_set(GIT_ERROR_SSL, "Windows Socket Library does not support Winsock 2.2"); + return -1; + } + + return git_runtime_shutdown_register(socket_stream_global_shutdown); +} + +#else + +#include "stream.h" + +int git_socket_stream_global_init(void) +{ + return 0; +} + + #endif diff --git a/src/libgit2/streams/socket.h b/src/libgit2/streams/socket.h index 3235f3167..300e70893 100644 --- a/src/libgit2/streams/socket.h +++ b/src/libgit2/streams/socket.h @@ -20,4 +20,6 @@ typedef struct { extern int git_socket_stream_new(git_stream **out, const char *host, const char *port); +extern int git_socket_stream_global_init(void); + #endif diff --git a/src/libgit2/streams/tls.c b/src/libgit2/streams/tls.c index e063a33f9..246ac9ca7 100644 --- a/src/libgit2/streams/tls.c +++ b/src/libgit2/streams/tls.c @@ -13,6 +13,7 @@ #include "streams/mbedtls.h" #include "streams/openssl.h" #include "streams/stransport.h" +#include "streams/schannel.h" int git_tls_stream_new(git_stream **out, const char *host, const char *port) { @@ -33,6 +34,8 @@ int git_tls_stream_new(git_stream **out, const char *host, const char *port) init = git_openssl_stream_new; #elif defined(GIT_MBEDTLS) init = git_mbedtls_stream_new; +#elif defined(GIT_SCHANNEL) + init = git_schannel_stream_new; #endif } else { return error; @@ -63,6 +66,8 @@ int git_tls_stream_wrap(git_stream **out, git_stream *in, const char *host) wrap = git_openssl_stream_wrap; #elif defined(GIT_MBEDTLS) wrap = git_mbedtls_stream_wrap; +#elif defined(GIT_SCHANNEL) + wrap = git_schannel_stream_wrap; #endif } diff --git a/src/libgit2/sysdir.h b/src/libgit2/sysdir.h index 1d15bbf43..03f59e1de 100644 --- a/src/libgit2/sysdir.h +++ b/src/libgit2/sysdir.h @@ -134,10 +134,12 @@ extern int git_sysdir_set(git_sysdir_t which, const char *paths); */ extern int git_sysdir_reset(void); +#ifdef GIT_WIN32 /** Sets the registry system dir to a mock; for testing. */ extern int git_win32__set_registry_system_dir(const wchar_t *mock_sysdir); /** Find the given system dir; for testing. */ extern int git_win32__find_system_dirs(git_str *out, const char *subdir); +#endif #endif diff --git a/src/libgit2/transports/auth_negotiate.c b/src/libgit2/transports/auth_gssapi.c index 6380504be..500553841 100644 --- a/src/libgit2/transports/auth_negotiate.c +++ b/src/libgit2/transports/auth_gssapi.c @@ -20,13 +20,13 @@ #include <krb5.h> #endif -static gss_OID_desc negotiate_oid_spnego = +static gss_OID_desc gssapi_oid_spnego = { 6, (void *) "\x2b\x06\x01\x05\x05\x02" }; -static gss_OID_desc negotiate_oid_krb5 = +static gss_OID_desc gssapi_oid_krb5 = { 9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" }; -static gss_OID negotiate_oids[] = - { &negotiate_oid_spnego, &negotiate_oid_krb5, NULL }; +static gss_OID gssapi_oids[] = + { &gssapi_oid_spnego, &gssapi_oid_krb5, NULL }; typedef struct { git_http_auth_context parent; @@ -36,9 +36,9 @@ typedef struct { char *challenge; gss_ctx_id_t gss_context; gss_OID oid; -} http_auth_negotiate_context; +} http_auth_gssapi_context; -static void negotiate_err_set( +static void gssapi_err_set( OM_uint32 status_major, OM_uint32 status_minor, const char *message) @@ -58,11 +58,11 @@ static void negotiate_err_set( } } -static int negotiate_set_challenge( +static int gssapi_set_challenge( git_http_auth_context *c, const char *challenge) { - http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; + http_auth_gssapi_context *ctx = (http_auth_gssapi_context *)c; GIT_ASSERT_ARG(ctx); GIT_ASSERT_ARG(challenge); @@ -76,7 +76,7 @@ static int negotiate_set_challenge( return 0; } -static void negotiate_context_dispose(http_auth_negotiate_context *ctx) +static void gssapi_context_dispose(http_auth_gssapi_context *ctx) { OM_uint32 status_minor; @@ -92,12 +92,12 @@ static void negotiate_context_dispose(http_auth_negotiate_context *ctx) ctx->challenge = NULL; } -static int negotiate_next_token( +static int gssapi_next_token( git_str *buf, git_http_auth_context *c, git_credential *cred) { - http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; + http_auth_gssapi_context *ctx = (http_auth_gssapi_context *)c; OM_uint32 status_major, status_minor; gss_buffer_desc target_buffer = GSS_C_EMPTY_BUFFER, input_token = GSS_C_EMPTY_BUFFER, @@ -126,7 +126,7 @@ static int negotiate_next_token( GSS_C_NT_HOSTBASED_SERVICE, &server); if (GSS_ERROR(status_major)) { - negotiate_err_set(status_major, status_minor, + gssapi_err_set(status_major, status_minor, "could not parse principal"); error = -1; goto done; @@ -152,10 +152,10 @@ static int negotiate_next_token( input_token.length = input_buf.size; input_token_ptr = &input_token; } else if (ctx->gss_context != GSS_C_NO_CONTEXT) { - negotiate_context_dispose(ctx); + gssapi_context_dispose(ctx); } - mech = &negotiate_oid_spnego; + mech = &gssapi_oid_spnego; status_major = gss_init_sec_context( &status_minor, @@ -173,14 +173,14 @@ static int negotiate_next_token( NULL); if (GSS_ERROR(status_major)) { - negotiate_err_set(status_major, status_minor, "negotiate failure"); + gssapi_err_set(status_major, status_minor, "negotiate failure"); error = -1; goto done; } /* This message merely told us auth was complete; we do not respond. */ if (status_major == GSS_S_COMPLETE) { - negotiate_context_dispose(ctx); + gssapi_context_dispose(ctx); ctx->complete = 1; goto done; } @@ -204,20 +204,20 @@ done: return error; } -static int negotiate_is_complete(git_http_auth_context *c) +static int gssapi_is_complete(git_http_auth_context *c) { - http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; + http_auth_gssapi_context *ctx = (http_auth_gssapi_context *)c; GIT_ASSERT_ARG(ctx); return (ctx->complete == 1); } -static void negotiate_context_free(git_http_auth_context *c) +static void gssapi_context_free(git_http_auth_context *c) { - http_auth_negotiate_context *ctx = (http_auth_negotiate_context *)c; + http_auth_gssapi_context *ctx = (http_auth_gssapi_context *)c; - negotiate_context_dispose(ctx); + gssapi_context_dispose(ctx); ctx->configured = 0; ctx->complete = 0; @@ -226,8 +226,8 @@ static void negotiate_context_free(git_http_auth_context *c) git__free(ctx); } -static int negotiate_init_context( - http_auth_negotiate_context *ctx, +static int gssapi_init_context( + http_auth_gssapi_context *ctx, const git_net_url *url) { OM_uint32 status_major, status_minor; @@ -239,13 +239,13 @@ static int negotiate_init_context( status_major = gss_indicate_mechs(&status_minor, &mechanism_list); if (GSS_ERROR(status_major)) { - negotiate_err_set(status_major, status_minor, + gssapi_err_set(status_major, status_minor, "could not query mechanisms"); return -1; } if (mechanism_list) { - for (oid = negotiate_oids; *oid; oid++) { + for (oid = gssapi_oids; *oid; oid++) { for (i = 0; i < mechanism_list->count; i++) { item = &mechanism_list->elements[i]; @@ -285,14 +285,14 @@ int git_http_auth_negotiate( git_http_auth_context **out, const git_net_url *url) { - http_auth_negotiate_context *ctx; + http_auth_gssapi_context *ctx; *out = NULL; - ctx = git__calloc(1, sizeof(http_auth_negotiate_context)); + ctx = git__calloc(1, sizeof(http_auth_gssapi_context)); GIT_ERROR_CHECK_ALLOC(ctx); - if (negotiate_init_context(ctx, url) < 0) { + if (gssapi_init_context(ctx, url) < 0) { git__free(ctx); return -1; } @@ -300,10 +300,10 @@ int git_http_auth_negotiate( ctx->parent.type = GIT_HTTP_AUTH_NEGOTIATE; ctx->parent.credtypes = GIT_CREDENTIAL_DEFAULT; ctx->parent.connection_affinity = 1; - ctx->parent.set_challenge = negotiate_set_challenge; - ctx->parent.next_token = negotiate_next_token; - ctx->parent.is_complete = negotiate_is_complete; - ctx->parent.free = negotiate_context_free; + ctx->parent.set_challenge = gssapi_set_challenge; + ctx->parent.next_token = gssapi_next_token; + ctx->parent.is_complete = gssapi_is_complete; + ctx->parent.free = gssapi_context_free; *out = (git_http_auth_context *)ctx; diff --git a/src/libgit2/transports/auth_negotiate.h b/src/libgit2/transports/auth_negotiate.h index 34aff295b..4360785c5 100644 --- a/src/libgit2/transports/auth_negotiate.h +++ b/src/libgit2/transports/auth_negotiate.h @@ -12,7 +12,7 @@ #include "git2.h" #include "auth.h" -#if defined(GIT_GSSAPI) || defined(GIT_GSSFRAMEWORK) +#if defined(GIT_GSSAPI) || defined(GIT_GSSFRAMEWORK) || defined(GIT_WIN32) extern int git_http_auth_negotiate( git_http_auth_context **out, diff --git a/src/libgit2/transports/auth_ntlm.h b/src/libgit2/transports/auth_ntlm.h index 40689498c..33406ae94 100644 --- a/src/libgit2/transports/auth_ntlm.h +++ b/src/libgit2/transports/auth_ntlm.h @@ -13,7 +13,7 @@ /* NTLM requires a full request/challenge/response */ #define GIT_AUTH_STEPS_NTLM 2 -#ifdef GIT_NTLM +#if defined(GIT_NTLM) || defined(GIT_WIN32) #if defined(GIT_OPENSSL) # define CRYPT_OPENSSL diff --git a/src/libgit2/transports/auth_ntlm.c b/src/libgit2/transports/auth_ntlmclient.c index f49ce101a..6f26a6179 100644 --- a/src/libgit2/transports/auth_ntlm.c +++ b/src/libgit2/transports/auth_ntlmclient.c @@ -23,7 +23,7 @@ typedef struct { bool complete; } http_auth_ntlm_context; -static int ntlm_set_challenge( +static int ntlmclient_set_challenge( git_http_auth_context *c, const char *challenge) { @@ -40,7 +40,7 @@ static int ntlm_set_challenge( return 0; } -static int ntlm_set_credentials(http_auth_ntlm_context *ctx, git_credential *_cred) +static int ntlmclient_set_credentials(http_auth_ntlm_context *ctx, git_credential *_cred) { git_credential_userpass_plaintext *cred; const char *sep, *username; @@ -76,7 +76,7 @@ done: return error; } -static int ntlm_next_token( +static int ntlmclient_next_token( git_str *buf, git_http_auth_context *c, git_credential *cred) @@ -104,7 +104,7 @@ static int ntlm_next_token( */ ctx->complete = true; - if (cred && ntlm_set_credentials(ctx, cred) != 0) + if (cred && ntlmclient_set_credentials(ctx, cred) != 0) goto done; if (challenge_len < 4) { @@ -162,7 +162,7 @@ done: return error; } -static int ntlm_is_complete(git_http_auth_context *c) +static int ntlmclient_is_complete(git_http_auth_context *c) { http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c; @@ -170,7 +170,7 @@ static int ntlm_is_complete(git_http_auth_context *c) return (ctx->complete == true); } -static void ntlm_context_free(git_http_auth_context *c) +static void ntlmclient_context_free(git_http_auth_context *c) { http_auth_ntlm_context *ctx = (http_auth_ntlm_context *)c; @@ -179,7 +179,7 @@ static void ntlm_context_free(git_http_auth_context *c) git__free(ctx); } -static int ntlm_init_context( +static int ntlmclient_init_context( http_auth_ntlm_context *ctx, const git_net_url *url) { @@ -206,7 +206,7 @@ int git_http_auth_ntlm( ctx = git__calloc(1, sizeof(http_auth_ntlm_context)); GIT_ERROR_CHECK_ALLOC(ctx); - if (ntlm_init_context(ctx, url) < 0) { + if (ntlmclient_init_context(ctx, url) < 0) { git__free(ctx); return -1; } @@ -214,10 +214,10 @@ int git_http_auth_ntlm( ctx->parent.type = GIT_HTTP_AUTH_NTLM; ctx->parent.credtypes = GIT_CREDENTIAL_USERPASS_PLAINTEXT; ctx->parent.connection_affinity = 1; - ctx->parent.set_challenge = ntlm_set_challenge; - ctx->parent.next_token = ntlm_next_token; - ctx->parent.is_complete = ntlm_is_complete; - ctx->parent.free = ntlm_context_free; + ctx->parent.set_challenge = ntlmclient_set_challenge; + ctx->parent.next_token = ntlmclient_next_token; + ctx->parent.is_complete = ntlmclient_is_complete; + ctx->parent.free = ntlmclient_context_free; *out = (git_http_auth_context *)ctx; diff --git a/src/libgit2/transports/auth_sspi.c b/src/libgit2/transports/auth_sspi.c new file mode 100644 index 000000000..f8269365d --- /dev/null +++ b/src/libgit2/transports/auth_sspi.c @@ -0,0 +1,341 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "auth_ntlm.h" +#include "auth_negotiate.h" + +#ifdef GIT_WIN32 + +#define SECURITY_WIN32 + +#include "git2.h" +#include "auth.h" +#include "git2/sys/credential.h" + +#include <windows.h> +#include <security.h> + +typedef struct { + git_http_auth_context parent; + wchar_t *target; + + const char *package_name; + size_t package_name_len; + wchar_t *package_name_w; + SecPkgInfoW *package_info; + SEC_WINNT_AUTH_IDENTITY_W identity; + CredHandle cred; + CtxtHandle context; + + int has_identity : 1, + has_credentials : 1, + has_context : 1, + complete : 1; + git_str challenge; +} http_auth_sspi_context; + +static void sspi_reset_context(http_auth_sspi_context *ctx) +{ + if (ctx->has_identity) { + git__free(ctx->identity.User); + git__free(ctx->identity.Domain); + git__free(ctx->identity.Password); + + memset(&ctx->identity, 0, sizeof(SEC_WINNT_AUTH_IDENTITY_W)); + + ctx->has_identity = 0; + } + + if (ctx->has_credentials) { + FreeCredentialsHandle(&ctx->cred); + memset(&ctx->cred, 0, sizeof(CredHandle)); + + ctx->has_credentials = 0; + } + + if (ctx->has_context) { + DeleteSecurityContext(&ctx->context); + memset(&ctx->context, 0, sizeof(CtxtHandle)); + + ctx->has_context = 0; + } + + ctx->complete = 0; + + git_str_dispose(&ctx->challenge); +} + +static int sspi_set_challenge( + git_http_auth_context *c, + const char *challenge) +{ + http_auth_sspi_context *ctx = (http_auth_sspi_context *)c; + size_t challenge_len = strlen(challenge); + + git_str_clear(&ctx->challenge); + + if (strncmp(challenge, ctx->package_name, ctx->package_name_len) != 0) { + git_error_set(GIT_ERROR_NET, "invalid %s challenge from server", ctx->package_name); + return -1; + } + + /* + * A package type indicator without a base64 payload indicates the + * mechanism; it's not an actual challenge. Ignore it. + */ + if (challenge[ctx->package_name_len] == 0) { + return 0; + } else if (challenge[ctx->package_name_len] != ' ') { + git_error_set(GIT_ERROR_NET, "invalid %s challenge from server", ctx->package_name); + return -1; + } + + if (git_str_decode_base64(&ctx->challenge, + challenge + (ctx->package_name_len + 1), + challenge_len - (ctx->package_name_len + 1)) < 0) { + git_error_set(GIT_ERROR_NET, "invalid %s challenge from server", ctx->package_name); + return -1; + } + + GIT_ASSERT(ctx->challenge.size <= ULONG_MAX); + return 0; +} + +static int create_identity( + SEC_WINNT_AUTH_IDENTITY_W **out, + http_auth_sspi_context *ctx, + git_credential *cred) +{ + git_credential_userpass_plaintext *userpass; + wchar_t *username = NULL, *domain = NULL, *password = NULL; + int username_len = 0, domain_len = 0, password_len = 0; + const char *sep; + + if (cred->credtype == GIT_CREDENTIAL_DEFAULT) { + *out = NULL; + return 0; + } + + if (cred->credtype != GIT_CREDENTIAL_USERPASS_PLAINTEXT) { + git_error_set(GIT_ERROR_NET, "unknown credential type: %d", cred->credtype); + return -1; + } + + userpass = (git_credential_userpass_plaintext *)cred; + + if ((sep = strchr(userpass->username, '\\')) != NULL) { + GIT_ASSERT(sep - userpass->username < INT_MAX); + + username_len = git_utf8_to_16_alloc(&username, sep + 1); + domain_len = git_utf8_to_16_alloc_with_len(&domain, + userpass->username, (int)(sep - userpass->username)); + } else { + username_len = git_utf8_to_16_alloc(&username, + userpass->username); + } + + password_len = git_utf8_to_16_alloc(&password, userpass->password); + + if (username_len < 0 || domain_len < 0 || password_len < 0) { + git__free(username); + git__free(domain); + git__free(password); + return -1; + } + + ctx->identity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; + ctx->identity.User = username; + ctx->identity.UserLength = (unsigned long)username_len; + ctx->identity.Password = password; + ctx->identity.PasswordLength = (unsigned long)password_len; + ctx->identity.Domain = domain; + ctx->identity.DomainLength = (unsigned long)domain_len; + + ctx->has_identity = 1; + + *out = &ctx->identity; + + return 0; +} + +static int sspi_next_token( + git_str *buf, + git_http_auth_context *c, + git_credential *cred) +{ + http_auth_sspi_context *ctx = (http_auth_sspi_context *)c; + SEC_WINNT_AUTH_IDENTITY_W *identity = NULL; + TimeStamp timestamp; + DWORD context_flags; + SecBuffer input_buf = { 0, SECBUFFER_TOKEN, NULL }; + SecBuffer output_buf = { 0, SECBUFFER_TOKEN, NULL }; + SecBufferDesc input_buf_desc = { SECBUFFER_VERSION, 1, &input_buf }; + SecBufferDesc output_buf_desc = { SECBUFFER_VERSION, 1, &output_buf }; + SECURITY_STATUS status; + + if (ctx->complete) + sspi_reset_context(ctx); + + if (!ctx->has_context) { + if (create_identity(&identity, ctx, cred) < 0) + return -1; + + status = AcquireCredentialsHandleW(NULL, ctx->package_name_w, + SECPKG_CRED_BOTH, NULL, identity, NULL, + NULL, &ctx->cred, ×tamp); + + if (status != SEC_E_OK) { + git_error_set(GIT_ERROR_OS, "could not acquire credentials"); + return -1; + } + + ctx->has_credentials = 1; + } + + context_flags = ISC_REQ_ALLOCATE_MEMORY | + ISC_REQ_CONFIDENTIALITY | + ISC_REQ_MUTUAL_AUTH; + + if (ctx->challenge.size > 0) { + input_buf.BufferType = SECBUFFER_TOKEN; + input_buf.cbBuffer = (unsigned long)ctx->challenge.size; + input_buf.pvBuffer = ctx->challenge.ptr; + } + + status = InitializeSecurityContextW(&ctx->cred, + ctx->has_context ? &ctx->context : NULL, + ctx->target, + context_flags, + 0, + SECURITY_NETWORK_DREP, + ctx->has_context ? &input_buf_desc : NULL, + 0, + ctx->has_context ? NULL : &ctx->context, + &output_buf_desc, + &context_flags, + NULL); + + if (status == SEC_I_COMPLETE_AND_CONTINUE || + status == SEC_I_COMPLETE_NEEDED) + status = CompleteAuthToken(&ctx->context, &output_buf_desc); + + if (status == SEC_E_OK) { + ctx->complete = 1; + } else if (status != SEC_I_CONTINUE_NEEDED) { + git_error_set(GIT_ERROR_OS, "could not initialize security context"); + return -1; + } + + ctx->has_context = 1; + git_str_clear(&ctx->challenge); + + if (output_buf.cbBuffer > 0) { + git_str_put(buf, ctx->package_name, ctx->package_name_len); + git_str_putc(buf, ' '); + git_str_encode_base64(buf, output_buf.pvBuffer, output_buf.cbBuffer); + + FreeContextBuffer(output_buf.pvBuffer); + + if (git_str_oom(buf)) + return -1; + } + + return 0; +} + +static int sspi_is_complete(git_http_auth_context *c) +{ + http_auth_sspi_context *ctx = (http_auth_sspi_context *)c; + + return ctx->complete; +} + +static void sspi_context_free(git_http_auth_context *c) +{ + http_auth_sspi_context *ctx = (http_auth_sspi_context *)c; + + sspi_reset_context(ctx); + + FreeContextBuffer(ctx->package_info); + git__free(ctx->target); + git__free(ctx); +} + +static int sspi_init_context( + git_http_auth_context **out, + git_http_auth_t type, + const git_net_url *url) +{ + http_auth_sspi_context *ctx; + git_str target = GIT_STR_INIT; + + *out = NULL; + + ctx = git__calloc(1, sizeof(http_auth_sspi_context)); + GIT_ERROR_CHECK_ALLOC(ctx); + + switch (type) { + case GIT_HTTP_AUTH_NTLM: + ctx->package_name = "NTLM"; + ctx->package_name_len = CONST_STRLEN("NTLM"); + ctx->package_name_w = L"NTLM"; + ctx->parent.credtypes = GIT_CREDENTIAL_USERPASS_PLAINTEXT | + GIT_CREDENTIAL_DEFAULT; + break; + case GIT_HTTP_AUTH_NEGOTIATE: + ctx->package_name = "Negotiate"; + ctx->package_name_len = CONST_STRLEN("Negotiate"); + ctx->package_name_w = L"Negotiate"; + ctx->parent.credtypes = GIT_CREDENTIAL_DEFAULT; + break; + default: + git_error_set(GIT_ERROR_NET, "unknown SSPI auth type: %d", ctx->parent.type); + git__free(ctx); + return -1; + } + + if (QuerySecurityPackageInfoW(ctx->package_name_w, &ctx->package_info) != SEC_E_OK) { + git_error_set(GIT_ERROR_OS, "could not query security package"); + git__free(ctx); + return -1; + } + + if (git_str_printf(&target, "http/%s", url->host) < 0 || + git_utf8_to_16_alloc(&ctx->target, target.ptr) < 0) { + FreeContextBuffer(ctx->package_info); + git__free(ctx); + return -1; + } + + ctx->parent.type = type; + ctx->parent.connection_affinity = 1; + ctx->parent.set_challenge = sspi_set_challenge; + ctx->parent.next_token = sspi_next_token; + ctx->parent.is_complete = sspi_is_complete; + ctx->parent.free = sspi_context_free; + + *out = (git_http_auth_context *)ctx; + + git_str_dispose(&target); + return 0; +} + +int git_http_auth_negotiate( + git_http_auth_context **out, + const git_net_url *url) +{ + return sspi_init_context(out, GIT_HTTP_AUTH_NEGOTIATE, url); +} + +int git_http_auth_ntlm( + git_http_auth_context **out, + const git_net_url *url) +{ + return sspi_init_context(out, GIT_HTTP_AUTH_NTLM, url); +} + +#endif /* GIT_WIN32 */ diff --git a/src/libgit2/transports/winhttp.c b/src/libgit2/transports/winhttp.c index 098227607..de24a2a41 100644 --- a/src/libgit2/transports/winhttp.c +++ b/src/libgit2/transports/winhttp.c @@ -158,10 +158,10 @@ static int apply_userpass_credentials(HINTERNET request, DWORD target, int mecha goto done; } - if ((error = user_len = git__utf8_to_16_alloc(&user, c->username)) < 0) + if ((error = user_len = git_utf8_to_16_alloc(&user, c->username)) < 0) goto done; - if ((error = pass_len = git__utf8_to_16_alloc(&pass, c->password)) < 0) + if ((error = pass_len = git_utf8_to_16_alloc(&pass, c->password)) < 0) goto done; if (!WinHttpSetCredentials(request, target, native_scheme, user, pass, NULL)) { @@ -242,7 +242,7 @@ static int acquire_fallback_cred( HRESULT hCoInitResult; /* Convert URL to wide characters */ - if (git__utf8_to_16_alloc(&wide_url, url) < 0) { + if (git_utf8_to_16_alloc(&wide_url, url) < 0) { git_error_set(GIT_ERROR_OS, "failed to convert string to wide form"); return -1; } @@ -397,7 +397,7 @@ static int winhttp_stream_connect(winhttp_stream *s) return -1; /* Convert URL to wide characters */ - if (git__utf8_to_16_alloc(&s->request_uri, git_str_cstr(&buf)) < 0) { + if (git_utf8_to_16_alloc(&s->request_uri, git_str_cstr(&buf)) < 0) { git_error_set(GIT_ERROR_OS, "failed to convert string to wide form"); goto on_error; } @@ -473,7 +473,7 @@ static int winhttp_stream_connect(winhttp_stream *s) } /* Convert URL to wide characters */ - error = git__utf8_to_16_alloc(&proxy_wide, processed_url.ptr); + error = git_utf8_to_16_alloc(&proxy_wide, processed_url.ptr); git_str_dispose(&processed_url); if (error < 0) goto on_error; @@ -531,7 +531,7 @@ static int winhttp_stream_connect(winhttp_stream *s) s->service) < 0) goto on_error; - if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_str_cstr(&buf)) < 0) { + if (git_utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_str_cstr(&buf)) < 0) { git_error_set(GIT_ERROR_OS, "failed to convert content-type to wide characters"); goto on_error; } @@ -548,7 +548,7 @@ static int winhttp_stream_connect(winhttp_stream *s) s->service) < 0) goto on_error; - if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_str_cstr(&buf)) < 0) { + if (git_utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_str_cstr(&buf)) < 0) { git_error_set(GIT_ERROR_OS, "failed to convert accept header to wide characters"); goto on_error; } @@ -568,7 +568,7 @@ static int winhttp_stream_connect(winhttp_stream *s) git_str_puts(&buf, t->owner->connect_opts.custom_headers.strings[i]); /* Convert header to wide characters */ - if ((error = git__utf8_to_16_alloc(&custom_header_wide, git_str_cstr(&buf))) < 0) + if ((error = git_utf8_to_16_alloc(&custom_header_wide, git_str_cstr(&buf))) < 0) goto on_error; if (!WinHttpAddRequestHeaders(s->request, custom_header_wide, (ULONG)-1L, @@ -783,7 +783,7 @@ static int winhttp_connect( } /* Prepare host */ - if (git__utf8_to_16_alloc(&wide_host, host) < 0) { + if (git_utf8_to_16_alloc(&wide_host, host) < 0) { git_error_set(GIT_ERROR_OS, "unable to convert host to wide characters"); goto on_error; } @@ -792,7 +792,7 @@ static int winhttp_connect( if (git_http__user_agent(&ua) < 0) goto on_error; - if (git__utf8_to_16_alloc(&wide_ua, git_str_cstr(&ua)) < 0) { + if (git_utf8_to_16_alloc(&wide_ua, git_str_cstr(&ua)) < 0) { git_error_set(GIT_ERROR_OS, "unable to convert host to wide characters"); goto on_error; } @@ -1182,7 +1182,7 @@ replay: } /* Convert the Location header to UTF-8 */ - if (git__utf16_to_8_alloc(&location8, location) < 0) { + if (git_utf8_from_16_alloc(&location8, location) < 0) { git_error_set(GIT_ERROR_OS, "failed to convert Location header to UTF-8"); git__free(location); return -1; @@ -1254,7 +1254,7 @@ replay: else p_snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-advertisement", s->service); - if (git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8) < 0) { + if (git_utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8) < 0) { git_error_set(GIT_ERROR_OS, "failed to convert expected content-type to wide characters"); return -1; } diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 2207041ef..ee35eb961 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -9,7 +9,6 @@ configure_file(git2_features.h.in git2_features.h) set(UTIL_INCLUDES "${PROJECT_BINARY_DIR}/src/util" "${PROJECT_BINARY_DIR}/include" - "${PROJECT_BINARY_DIR}/include/git2" "${PROJECT_SOURCE_DIR}/src/util" "${PROJECT_SOURCE_DIR}/include") diff --git a/src/util/fs_path.c b/src/util/fs_path.c index b52867e77..e03fcf7c7 100644 --- a/src/util/fs_path.c +++ b/src/util/fs_path.c @@ -2015,7 +2015,7 @@ int git_fs_path_find_executable(git_str *fullpath, const char *executable) git_win32_path fullpath_w, executable_w; int error; - if (git__utf8_to_16(executable_w, GIT_WIN_PATH_MAX, executable) < 0) + if (git_utf8_to_16(executable_w, GIT_WIN_PATH_MAX, executable) < 0) return -1; error = git_win32_path_find_executable(fullpath_w, executable_w); diff --git a/src/util/git2_features.h.in b/src/util/git2_features.h.in index fbf0cab60..1575be641 100644 --- a/src/util/git2_features.h.in +++ b/src/util/git2_features.h.in @@ -41,6 +41,7 @@ #cmakedefine GIT_OPENSSL_DYNAMIC 1 #cmakedefine GIT_SECURE_TRANSPORT 1 #cmakedefine GIT_MBEDTLS 1 +#cmakedefine GIT_SCHANNEL 1 #cmakedefine GIT_SHA1_COLLISIONDETECT 1 #cmakedefine GIT_SHA1_WIN32 1 diff --git a/src/util/util.c b/src/util/util.c index aee95fddf..9c9f2c040 100644 --- a/src/util/util.c +++ b/src/util/util.c @@ -743,7 +743,7 @@ int git__getenv(git_str *out, const char *name) git_str_clear(out); - if (git__utf8_to_16_alloc(&wide_name, name) < 0) + if (git_utf8_to_16_alloc(&wide_name, name) < 0) return -1; if ((value_len = GetEnvironmentVariableW(wide_name, NULL, 0)) > 0) { diff --git a/src/util/win32/error.c b/src/util/win32/error.c index 3a52fb5a9..dfd6fa1e8 100644 --- a/src/util/win32/error.c +++ b/src/util/win32/error.c @@ -43,7 +43,7 @@ char *git_win32_get_error_message(DWORD error_code) (LPWSTR)&lpMsgBuf, 0, NULL)) { /* Convert the message to UTF-8. If this fails, we will * return NULL, which is a condition expected by the caller */ - if (git__utf16_to_8_alloc(&utf8_msg, lpMsgBuf) < 0) + if (git_utf8_from_16_alloc(&utf8_msg, lpMsgBuf) < 0) utf8_msg = NULL; LocalFree(lpMsgBuf); diff --git a/src/util/win32/path_w32.c b/src/util/win32/path_w32.c index d9fc8292b..7a559e45c 100644 --- a/src/util/win32/path_w32.c +++ b/src/util/win32/path_w32.c @@ -336,13 +336,13 @@ int git_win32_path_from_utf8(git_win32_path out, const char *src) /* See if this is an absolute path (beginning with a drive letter) */ if (git_fs_path_is_absolute(src)) { - if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src) < 0) + if (git_utf8_to_16(dest, GIT_WIN_PATH_MAX, src) < 0) goto on_error; } /* File-prefixed NT-style paths beginning with \\?\ */ else if (path__is_nt_namespace(src)) { /* Skip the NT prefix, the destination already contains it */ - if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src + PATH__NT_NAMESPACE_LEN) < 0) + if (git_utf8_to_16(dest, GIT_WIN_PATH_MAX, src + PATH__NT_NAMESPACE_LEN) < 0) goto on_error; } /* UNC paths */ @@ -351,7 +351,7 @@ int git_win32_path_from_utf8(git_win32_path out, const char *src) dest += 4; /* Skip the leading "\\" */ - if (git__utf8_to_16(dest, GIT_WIN_PATH_MAX - 2, src + 2) < 0) + if (git_utf8_to_16(dest, GIT_WIN_PATH_MAX - 2, src + 2) < 0) goto on_error; } /* Absolute paths omitting the drive letter */ @@ -365,7 +365,7 @@ int git_win32_path_from_utf8(git_win32_path out, const char *src) } /* Skip the drive letter specification ("C:") */ - if (git__utf8_to_16(dest + 2, GIT_WIN_PATH_MAX - 2, src) < 0) + if (git_utf8_to_16(dest + 2, GIT_WIN_PATH_MAX - 2, src) < 0) goto on_error; } /* Relative paths */ @@ -377,7 +377,7 @@ int git_win32_path_from_utf8(git_win32_path out, const char *src) dest[cwd_len++] = L'\\'; - if (git__utf8_to_16(dest + cwd_len, GIT_WIN_PATH_MAX - cwd_len, src) < 0) + if (git_utf8_to_16(dest + cwd_len, GIT_WIN_PATH_MAX - cwd_len, src) < 0) goto on_error; } @@ -404,7 +404,7 @@ int git_win32_path_relative_from_utf8(git_win32_path out, const char *src) return git_win32_path_from_utf8(out, src); } - if ((len = git__utf8_to_16(dest, GIT_WIN_PATH_MAX, src)) < 0) + if ((len = git_utf8_to_16(dest, GIT_WIN_PATH_MAX, src)) < 0) return -1; for (p = dest; p < (dest + len); p++) { @@ -433,7 +433,7 @@ int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src) } } - if ((len = git__utf16_to_8(out, GIT_WIN_PATH_UTF8, src)) < 0) + if ((len = git_utf8_from_16(out, GIT_WIN_PATH_UTF8, src)) < 0) return len; git_fs_path_mkposix(dest); @@ -471,7 +471,7 @@ char *git_win32_path_8dot3_name(const char *path) if (namelen > 12 || (shortname = git__malloc(namelen + 1)) == NULL) return NULL; - if ((len = git__utf16_to_8(shortname, namelen + 1, start)) < 0) + if ((len = git_utf8_from_16(shortname, namelen + 1, start)) < 0) return NULL; return shortname; diff --git a/src/util/win32/posix_w32.c b/src/util/win32/posix_w32.c index 5862e5c9a..3fec469a6 100644 --- a/src/util/win32/posix_w32.c +++ b/src/util/win32/posix_w32.c @@ -649,7 +649,7 @@ int p_getcwd(char *buffer_out, size_t size) git_win32_path_remove_namespace(cwd, wcslen(cwd)); /* Convert the working directory back to UTF-8 */ - if (git__utf16_to_8(buffer_out, size, cwd) < 0) { + if (git_utf8_from_16(buffer_out, size, cwd) < 0) { DWORD code = GetLastError(); if (code == ERROR_INSUFFICIENT_BUFFER) diff --git a/src/util/win32/utf-conv.c b/src/util/win32/utf-conv.c index 4bde3023a..ad35c0c35 100644 --- a/src/util/win32/utf-conv.c +++ b/src/util/win32/utf-conv.c @@ -15,108 +15,114 @@ GIT_INLINE(void) git__set_errno(void) errno = EINVAL; } -/** - * Converts a UTF-8 string to wide characters. - * - * @param dest The buffer to receive the wide string. - * @param dest_size The size of the buffer, in characters. - * @param src The UTF-8 string to convert. - * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure - */ -int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src) +int git_utf8_to_16(wchar_t *dest, size_t dest_size, const char *src) +{ + /* Length of -1 indicates NULL termination of the input string. */ + return git_utf8_to_16_with_len(dest, dest_size, src, -1); +} + +int git_utf8_to_16_with_len( + wchar_t *dest, + size_t _dest_size, + const char *src, + int src_len) { + int dest_size = (int)min(_dest_size, INT_MAX); int len; - /* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to - * turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's - * length. MultiByteToWideChar never returns int's minvalue, so underflow is not possible */ - if ((len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size) - 1) < 0) + /* + * Subtract 1 from the result to turn 0 into -1 (an error code) and + * to not count the NULL terminator as part of the string's length. + * MultiByteToWideChar never returns int's minvalue, so underflow + * is not possible. + */ + len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + src, src_len, dest, dest_size) - 1; + + if (len < 0) git__set_errno(); return len; } -/** - * Converts a wide string to UTF-8. - * - * @param dest The buffer to receive the UTF-8 string. - * @param dest_size The size of the buffer, in bytes. - * @param src The wide string to convert. - * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure - */ -int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src) +int git_utf8_from_16(char *dest, size_t dest_size, const wchar_t *src) { + /* Length of -1 indicates NULL termination of the input string. */ + return git_utf8_from_16_with_len(dest, dest_size, src, -1); +} + +int git_utf8_from_16_with_len( + char *dest, + size_t _dest_size, + const wchar_t *src, + int src_len) +{ + int dest_size = (int)min(_dest_size, INT_MAX); int len; - /* Length of -1 indicates NULL termination of the input string. Subtract 1 from the result to - * turn 0 into -1 (an error code) and to not count the NULL terminator as part of the string's - * length. WideCharToMultiByte never returns int's minvalue, so underflow is not possible */ - if ((len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, dest, (int)dest_size, NULL, NULL) - 1) < 0) + /* + * Subtract 1 from the result to turn 0 into -1 (an error code) and + * to not count the NULL terminator as part of the string's length. + * WideCharToMultiByte never returns int's minvalue, so underflow + * is not possible. + */ + len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, + src, src_len, dest, dest_size, NULL, NULL) - 1; + + if (len < 0) git__set_errno(); return len; } -/** - * Converts a UTF-8 string to wide characters. - * Memory is allocated to hold the converted string. - * The caller is responsible for freeing the string with git__free. - * - * @param dest Receives a pointer to the wide string. - * @param src The UTF-8 string to convert. - * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure - */ -int git__utf8_to_16_alloc(wchar_t **dest, const char *src) +int git_utf8_to_16_alloc(wchar_t **dest, const char *src) +{ + /* Length of -1 indicates NULL termination of the input string. */ + return git_utf8_to_16_alloc_with_len(dest, src, -1); +} + +int git_utf8_to_16_alloc_with_len(wchar_t **dest, const char *src, int src_len) { int utf16_size; *dest = NULL; - /* Length of -1 indicates NULL termination of the input string */ - utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, NULL, 0); + utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + src, src_len, NULL, 0); if (!utf16_size) { git__set_errno(); return -1; } - if (!(*dest = git__mallocarray(utf16_size, sizeof(wchar_t)))) { - errno = ENOMEM; - return -1; - } + *dest = git__mallocarray(utf16_size, sizeof(wchar_t)); + GIT_ERROR_CHECK_ALLOC(*dest); - utf16_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, *dest, utf16_size); - - if (!utf16_size) { - git__set_errno(); + utf16_size = git_utf8_to_16_with_len(*dest, (size_t)utf16_size, + src, src_len); + if (utf16_size < 0) { git__free(*dest); *dest = NULL; } - /* Subtract 1 from the result to turn 0 into -1 (an error code) and to not count the NULL - * terminator as part of the string's length. MultiByteToWideChar never returns int's minvalue, - * so underflow is not possible */ - return utf16_size - 1; + return utf16_size; } -/** - * Converts a wide string to UTF-8. - * Memory is allocated to hold the converted string. - * The caller is responsible for freeing the string with git__free. - * - * @param dest Receives a pointer to the UTF-8 string. - * @param src The wide string to convert. - * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure - */ -int git__utf16_to_8_alloc(char **dest, const wchar_t *src) +int git_utf8_from_16_alloc(char **dest, const wchar_t *src) +{ + /* Length of -1 indicates NULL termination of the input string. */ + return git_utf8_from_16_alloc_with_len(dest, src, -1); +} + +int git_utf8_from_16_alloc_with_len(char **dest, const wchar_t *src, int src_len) { int utf8_size; *dest = NULL; - /* Length of -1 indicates NULL termination of the input string */ - utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, NULL, 0, NULL, NULL); + utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, + src, src_len, NULL, 0, NULL, NULL); if (!utf8_size) { git__set_errno(); @@ -124,23 +130,15 @@ int git__utf16_to_8_alloc(char **dest, const wchar_t *src) } *dest = git__malloc(utf8_size); + GIT_ERROR_CHECK_ALLOC(*dest); - if (!*dest) { - errno = ENOMEM; - return -1; - } - - utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, src, -1, *dest, utf8_size, NULL, NULL); - - if (!utf8_size) { - git__set_errno(); + utf8_size = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, + src, src_len, *dest, utf8_size, NULL, NULL); + if (utf8_size < 0) { git__free(*dest); *dest = NULL; } - /* Subtract 1 from the result to turn 0 into -1 (an error code) and to not count the NULL - * terminator as part of the string's length. MultiByteToWideChar never returns int's minvalue, - * so underflow is not possible */ - return utf8_size - 1; + return utf8_size; } diff --git a/src/util/win32/utf-conv.h b/src/util/win32/utf-conv.h index 120d647ef..301f5a6d3 100644 --- a/src/util/win32/utf-conv.h +++ b/src/util/win32/utf-conv.h @@ -16,14 +16,45 @@ #endif /** + * Converts a NUL-terminated UTF-8 string to wide characters. This is a + * convenience function for `git_utf8_to_16_with_len`. + * + * @param dest The buffer to receive the wide string. + * @param dest_size The size of the buffer, in characters. + * @param src The UTF-8 string to convert. + * @return The length of the wide string, in characters + * (not counting the NULL terminator), or < 0 for failure + */ +int git_utf8_to_16(wchar_t *dest, size_t dest_size, const char *src); + +/** * Converts a UTF-8 string to wide characters. * * @param dest The buffer to receive the wide string. * @param dest_size The size of the buffer, in characters. * @param src The UTF-8 string to convert. - * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure + * @param src_len The length of the string to convert. + * @return The length of the wide string, in characters + * (not counting the NULL terminator), or < 0 for failure + */ +int git_utf8_to_16_with_len( + wchar_t *dest, + size_t dest_size, + const char *src, + int src_len); + +/** + * Converts a NUL-terminated wide string to UTF-8. This is a convenience + * function for `git_utf8_from_16_with_len`. + * + * @param dest The buffer to receive the UTF-8 string. + * @param dest_size The size of the buffer, in bytes. + * @param src The wide string to convert. + * @param src_len The length of the string to convert. + * @return The length of the UTF-8 string, in bytes + * (not counting the NULL terminator), or < 0 for failure */ -int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src); +int git_utf8_from_16(char *dest, size_t dest_size, const wchar_t *src); /** * Converts a wide string to UTF-8. @@ -31,30 +62,66 @@ int git__utf8_to_16(wchar_t *dest, size_t dest_size, const char *src); * @param dest The buffer to receive the UTF-8 string. * @param dest_size The size of the buffer, in bytes. * @param src The wide string to convert. - * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure + * @param src_len The length of the string to convert. + * @return The length of the UTF-8 string, in bytes + * (not counting the NULL terminator), or < 0 for failure */ -int git__utf16_to_8(char *dest, size_t dest_size, const wchar_t *src); +int git_utf8_from_16_with_len(char *dest, size_t dest_size, const wchar_t *src, int src_len); /** - * Converts a UTF-8 string to wide characters. - * Memory is allocated to hold the converted string. - * The caller is responsible for freeing the string with git__free. + * Converts a UTF-8 string to wide characters. Memory is allocated to hold + * the converted string. The caller is responsible for freeing the string + * with git__free. * * @param dest Receives a pointer to the wide string. * @param src The UTF-8 string to convert. - * @return The length of the wide string, in characters (not counting the NULL terminator), or < 0 for failure + * @return The length of the wide string, in characters + * (not counting the NULL terminator), or < 0 for failure */ -int git__utf8_to_16_alloc(wchar_t **dest, const char *src); +int git_utf8_to_16_alloc(wchar_t **dest, const char *src); /** - * Converts a wide string to UTF-8. - * Memory is allocated to hold the converted string. - * The caller is responsible for freeing the string with git__free. + * Converts a UTF-8 string to wide characters. Memory is allocated to hold + * the converted string. The caller is responsible for freeing the string + * with git__free. + * + * @param dest Receives a pointer to the wide string. + * @param src The UTF-8 string to convert. + * @param src_len The length of the string. + * @return The length of the wide string, in characters + * (not counting the NULL terminator), or < 0 for failure + */ +int git_utf8_to_16_alloc_with_len( + wchar_t **dest, + const char *src, + int src_len); + +/** + * Converts a wide string to UTF-8. Memory is allocated to hold the + * converted string. The caller is responsible for freeing the string + * with git__free. + * + * @param dest Receives a pointer to the UTF-8 string. + * @param src The wide string to convert. + * @return The length of the UTF-8 string, in bytes + * (not counting the NULL terminator), or < 0 for failure + */ +int git_utf8_from_16_alloc(char **dest, const wchar_t *src); + +/** + * Converts a wide string to UTF-8. Memory is allocated to hold the + * converted string. The caller is responsible for freeing the string + * with git__free. * * @param dest Receives a pointer to the UTF-8 string. * @param src The wide string to convert. - * @return The length of the UTF-8 string, in bytes (not counting the NULL terminator), or < 0 for failure + * @param src_len The length of the wide string. + * @return The length of the UTF-8 string, in bytes + * (not counting the NULL terminator), or < 0 for failure */ -int git__utf16_to_8_alloc(char **dest, const wchar_t *src); +int git_utf8_from_16_alloc_with_len( + char **dest, + const wchar_t *src, + int src_len); #endif diff --git a/src/util/win32/w32_util.c b/src/util/win32/w32_util.c index fe4b75bae..f5b006a19 100644 --- a/src/util/win32/w32_util.c +++ b/src/util/win32/w32_util.c @@ -115,7 +115,7 @@ int git_win32__file_attribute_to_stat( /* st_size gets the UTF-8 length of the target name, in bytes, * not counting the NULL terminator */ - if ((st->st_size = git__utf16_to_8(NULL, 0, target)) < 0) { + if ((st->st_size = git_utf8_from_16(NULL, 0, target)) < 0) { git_error_set(GIT_ERROR_OS, "could not convert reparse point name for '%ls'", path); return -1; } diff --git a/tests/clar/clar_libgit2.c b/tests/clar/clar_libgit2.c index 54122997d..a1b92fc33 100644 --- a/tests/clar/clar_libgit2.c +++ b/tests/clar/clar_libgit2.c @@ -103,10 +103,10 @@ int cl_setenv(const char *name, const char *value) { wchar_t *wide_name, *wide_value = NULL; - cl_assert(git__utf8_to_16_alloc(&wide_name, name) >= 0); + cl_assert(git_utf8_to_16_alloc(&wide_name, name) >= 0); if (value) { - cl_assert(git__utf8_to_16_alloc(&wide_value, value) >= 0); + cl_assert(git_utf8_to_16_alloc(&wide_value, value) >= 0); cl_assert(SetEnvironmentVariableW(wide_name, wide_value)); } else { /* Windows XP returns 0 (failed) when passing NULL for lpValue when diff --git a/tests/libgit2/config/find.c b/tests/libgit2/config/find.c new file mode 100644 index 000000000..7ca8ec767 --- /dev/null +++ b/tests/libgit2/config/find.c @@ -0,0 +1,11 @@ +#include "clar_libgit2.h" + +void test_config_find__one(void) +{ + git_buf buf = GIT_BUF_INIT; + + cl_git_fail_with(GIT_ENOTFOUND, git_config_find_global(&buf)); + cl_git_fail_with(GIT_ENOTFOUND, git_config_find_xdg(&buf)); + cl_git_fail_with(GIT_ENOTFOUND, git_config_find_system(&buf)); + cl_git_fail_with(GIT_ENOTFOUND, git_config_find_programdata(&buf)); +} diff --git a/tests/libgit2/core/opts.c b/tests/libgit2/core/opts.c index 486ff58c6..1aa095adf 100644 --- a/tests/libgit2/core/opts.c +++ b/tests/libgit2/core/opts.c @@ -50,9 +50,9 @@ void test_core_opts__extensions_add(void) cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out)); cl_assert_equal_sz(out.count, 3); - cl_assert_equal_s("noop", out.strings[0]); - cl_assert_equal_s("objectformat", out.strings[1]); - cl_assert_equal_s("foo", out.strings[2]); + cl_assert_equal_s("foo", out.strings[0]); + cl_assert_equal_s("noop", out.strings[1]); + cl_assert_equal_s("objectformat", out.strings[2]); git_strarray_dispose(&out); } @@ -66,9 +66,26 @@ void test_core_opts__extensions_remove(void) cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out)); cl_assert_equal_sz(out.count, 3); - cl_assert_equal_s("objectformat", out.strings[0]); - cl_assert_equal_s("bar", out.strings[1]); - cl_assert_equal_s("baz", out.strings[2]); + cl_assert_equal_s("bar", out.strings[0]); + cl_assert_equal_s("baz", out.strings[1]); + cl_assert_equal_s("objectformat", out.strings[2]); + + git_strarray_dispose(&out); +} + +void test_core_opts__extensions_uniq(void) +{ + const char *in[] = { "foo", "noop", "bar", "bar", "foo", "objectformat" }; + git_strarray out = { 0 }; + + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, in, ARRAY_SIZE(in))); + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, &out)); + + cl_assert_equal_sz(out.count, 4); + cl_assert_equal_s("bar", out.strings[0]); + cl_assert_equal_s("foo", out.strings[1]); + cl_assert_equal_s("noop", out.strings[2]); + cl_assert_equal_s("objectformat", out.strings[3]); git_strarray_dispose(&out); } diff --git a/tests/libgit2/index/addall.c b/tests/libgit2/index/addall.c index 6f95f6386..e76b6e81d 100644 --- a/tests/libgit2/index/addall.c +++ b/tests/libgit2/index/addall.c @@ -441,6 +441,52 @@ void test_index_addall__callback_filtering(void) git_index_free(index); } +void test_index_addall__handles_ignored_files_in_directory(void) +{ + git_index *index; + + g_repo = cl_git_sandbox_init_new(TEST_DIR); + + cl_git_mkfile(TEST_DIR "/file.foo", "a file"); + cl_git_mkfile(TEST_DIR "/file.bar", "another file"); + cl_must_pass(p_mkdir(TEST_DIR "/folder", 0777)); + cl_git_mkfile(TEST_DIR "/folder/asdf", "yet another file"); + + cl_git_mkfile(TEST_DIR "/.gitignore", "folder/\n"); + + check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL)); + + check_status(g_repo, 3, 0, 0, 0, 0, 0, 1, 0); + + git_index_free(index); +} + +void test_index_addall__force_adds_ignored_directories(void) +{ + git_index *index; + + g_repo = cl_git_sandbox_init_new(TEST_DIR); + + cl_git_mkfile(TEST_DIR "/file.foo", "a file"); + cl_git_mkfile(TEST_DIR "/file.bar", "another file"); + cl_must_pass(p_mkdir(TEST_DIR "/folder", 0777)); + cl_git_mkfile(TEST_DIR "/folder/asdf", "yet another file"); + + cl_git_mkfile(TEST_DIR "/.gitignore", "folder/\n"); + + check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_add_all(index, NULL, GIT_INDEX_ADD_FORCE, NULL, NULL)); + + check_status(g_repo, 4, 0, 0, 0, 0, 0, 0, 0); + + git_index_free(index); +} + void test_index_addall__adds_conflicts(void) { git_index *index; diff --git a/tests/libgit2/online/clone.c b/tests/libgit2/online/clone.c index 1a4cdb520..b635739b6 100644 --- a/tests/libgit2/online/clone.c +++ b/tests/libgit2/online/clone.c @@ -580,6 +580,17 @@ static int succeed_certificate_check(git_cert *cert, int valid, const char *host return 0; } +static int x509_succeed_certificate_check(git_cert *cert, int valid, const char *host, void *payload) +{ + GIT_UNUSED(valid); + GIT_UNUSED(payload); + + cl_assert_equal_s("github.com", host); + cl_assert_equal_i(GIT_CERT_X509, cert->cert_type); + + return 0; +} + static int fail_certificate_check(git_cert *cert, int valid, const char *host, void *payload) { GIT_UNUSED(cert); @@ -901,7 +912,7 @@ void test_online_clone__certificate_invalid(void) void test_online_clone__certificate_valid(void) { - g_options.fetch_opts.callbacks.certificate_check = succeed_certificate_check; + g_options.fetch_opts.callbacks.certificate_check = x509_succeed_certificate_check; cl_git_pass(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options)); } diff --git a/tests/libgit2/repo/discover.c b/tests/libgit2/repo/discover.c index 523fdf8e3..983d75e3a 100644 --- a/tests/libgit2/repo/discover.c +++ b/tests/libgit2/repo/discover.c @@ -122,7 +122,10 @@ void test_repo_discover__cleanup(void) void test_repo_discover__discovering_repo_with_exact_path_succeeds(void) { cl_git_pass(git_repository_discover(&discovered, DISCOVER_FOLDER, 0, ceiling_dirs.ptr)); + git_buf_dispose(&discovered); + cl_git_pass(git_repository_discover(&discovered, SUB_REPOSITORY_FOLDER, 0, ceiling_dirs.ptr)); + git_buf_dispose(&discovered); } void test_repo_discover__discovering_nonexistent_dir_fails(void) diff --git a/tests/libgit2/repo/env.c b/tests/libgit2/repo/env.c index 790ffd40f..0e6cc59d5 100644 --- a/tests/libgit2/repo/env.c +++ b/tests/libgit2/repo/env.c @@ -31,6 +31,10 @@ void test_repo_env__cleanup(void) if (git_fs_path_isdir("peeled.git")) git_futils_rmdir_r("peeled.git", NULL, GIT_RMDIR_REMOVE_FILES); + cl_fixture_cleanup("test_workdir"); + cl_fixture_cleanup("test_global_conf"); + cl_fixture_cleanup("test_system_conf"); + clear_git_env(); } @@ -275,3 +279,91 @@ void test_repo_env__open(void) clear_git_env(); } + +void test_repo_env__work_tree(void) +{ + git_repository *repo; + const char *test_path; + + cl_fixture_sandbox("attr"); + cl_git_pass(p_rename("attr/.gitted", "attr/.git")); + + cl_must_pass(p_mkdir("test_workdir", 0777)); + test_path = cl_git_sandbox_path(1, "test_workdir", NULL); + + cl_setenv("GIT_WORK_TREE", test_path); + cl_git_pass(git_repository_open_ext(&repo, "attr", GIT_REPOSITORY_OPEN_FROM_ENV, NULL)); + cl_assert_equal_s(test_path, git_repository_workdir(repo)); + git_repository_free(repo); + cl_setenv("GIT_WORK_TREE", NULL); +} + +void test_repo_env__commondir(void) +{ + git_repository *repo; + const char *test_path; + + cl_fixture_sandbox("attr"); + cl_git_pass(p_rename("attr/.gitted", "attr/.git")); + + cl_fixture_sandbox("testrepo.git"); + cl_git_pass(p_rename("testrepo.git", "test_commondir")); + + test_path = cl_git_sandbox_path(1, "test_commondir", NULL); + + cl_setenv("GIT_COMMON_DIR", test_path); + cl_git_pass(git_repository_open_ext(&repo, "attr", GIT_REPOSITORY_OPEN_FROM_ENV, NULL)); + cl_assert_equal_s(test_path, git_repository_commondir(repo)); + git_repository_free(repo); + cl_setenv("GIT_COMMON_DIR", NULL); +} + +void test_repo_env__config(void) +{ + git_repository *repo; + git_config *config; + const char *system_path, *global_path; + int s, g; + + cl_fixture_sandbox("attr"); + cl_git_pass(p_rename("attr/.gitted", "attr/.git")); + + cl_git_rewritefile("test_system_conf", "[tttest]\n\tsys = true\n"); + cl_git_rewritefile("test_global_conf", "[tttest]\n\tglb = true\n"); + + system_path = cl_git_sandbox_path(0, "test_system_conf", NULL); + cl_setenv("GIT_CONFIG_SYSTEM", system_path); + + global_path = cl_git_sandbox_path(0, "test_global_conf", NULL); + cl_setenv("GIT_CONFIG_GLOBAL", global_path); + + /* Ensure we can override the system and global files */ + + cl_git_pass(git_repository_open_ext(&repo, "attr", GIT_REPOSITORY_OPEN_FROM_ENV, NULL)); + cl_git_pass(git_repository_config(&config, repo)); + + cl_git_pass(git_config_get_bool(&s, config, "tttest.sys")); + cl_assert_equal_i(1, s); + cl_git_pass(git_config_get_bool(&g, config, "tttest.glb")); + cl_assert_equal_i(1, g); + + git_config_free(config); + git_repository_free(repo); + + /* Further ensure we can ignore the system file. */ + cl_setenv("GIT_CONFIG_NOSYSTEM", "TrUe"); + + cl_git_pass(git_repository_open_ext(&repo, "attr", GIT_REPOSITORY_OPEN_FROM_ENV, NULL)); + cl_git_pass(git_repository_config(&config, repo)); + + cl_git_fail_with(GIT_ENOTFOUND, git_config_get_bool(&s, config, "tttest.sys")); + cl_git_pass(git_config_get_bool(&g, config, "tttest.glb")); + cl_assert_equal_i(1, g); + + git_config_free(config); + git_repository_free(repo); + + cl_setenv("GIT_CONFIG_NOSYSTEM", NULL); + cl_setenv("GIT_CONFIG_SYSTEM", NULL); + cl_setenv("GIT_CONFIG_GLOBAL", NULL); +} diff --git a/tests/libgit2/stream/registration.c b/tests/libgit2/stream/registration.c index bf3c20502..ccaecee8c 100644 --- a/tests/libgit2/stream/registration.c +++ b/tests/libgit2/stream/registration.c @@ -81,10 +81,10 @@ void test_stream_registration__tls(void) cl_git_pass(git_stream_register(GIT_STREAM_TLS, NULL)); error = git_tls_stream_new(&stream, "localhost", "443"); - /* We don't have TLS support enabled, or we're on Windows, - * which has no arbitrary TLS stream support. + /* We don't have TLS support enabled, or we're on Windows + * with WinHTTP, which is not actually TLS stream support. */ -#if defined(GIT_WIN32) || !defined(GIT_HTTPS) +#if defined(GIT_WINHTTP) || !defined(GIT_HTTPS) cl_git_fail_with(-1, error); #else cl_git_pass(error); diff --git a/tests/util/link.c b/tests/util/link.c index 46cafada7..5909e26e3 100644 --- a/tests/util/link.c +++ b/tests/util/link.c @@ -98,7 +98,7 @@ static void do_junction(const char *old, const char *new) git_str_putc(&unparsed_buf, '\\'); - subst_utf16_len = git__utf8_to_16(NULL, 0, git_str_cstr(&unparsed_buf)); + subst_utf16_len = git_utf8_to_16(NULL, 0, git_str_cstr(&unparsed_buf)); subst_byte_len = subst_utf16_len * sizeof(WCHAR); print_utf16_len = subst_utf16_len - 4; @@ -124,11 +124,11 @@ static void do_junction(const char *old, const char *new) subst_utf16 = reparse_buf->ReparseBuffer.MountPoint.PathBuffer; print_utf16 = subst_utf16 + subst_utf16_len + 1; - ret = git__utf8_to_16(subst_utf16, subst_utf16_len + 1, + ret = git_utf8_to_16(subst_utf16, subst_utf16_len + 1, git_str_cstr(&unparsed_buf)); cl_assert_equal_i(subst_utf16_len, ret); - ret = git__utf8_to_16(print_utf16, + ret = git_utf8_to_16(print_utf16, print_utf16_len + 1, git_str_cstr(&unparsed_buf) + 4); cl_assert_equal_i(print_utf16_len, ret); |