Added support for http.cookieFile Added support for non-windows system. Borrowed and modified code from libcurl cookie library to parse cookie Change-Id: I598553aa65641925c2de9e157bfdf1120a16078e
diff --git a/src/transports/cookie.c b/src/transports/cookie.c new file mode 100644 index 0000000..5e2f8d0 --- /dev/null +++ b/src/transports/cookie.c
@@ -0,0 +1,757 @@ +/*************************************************************************** + * Copyright (C) 1998 - 2016, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#ifndef GIT_WINHTTP + +#include "cookie.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <arpa/inet.h> + +#define ISBLANK(x) (int)((((unsigned char)x) == ' ') || \ + (((unsigned char)x) == '\t')) +/* + * get_line() makes sure to only return complete whole lines that fit in 'len' + * bytes and end with a newline. + */ +static char *get_line(char *buf, int len, FILE *input) +{ + bool partial = false; + while(1) { + char *b = fgets(buf, len, input); + if(b) { + size_t rlen = strlen(b); + if(rlen && (b[rlen-1] == '\n')) { + if(partial) { + partial = false; + continue; + } + return b; + } + /* read a partial, discard the next piece that ends with newline */ + partial = true; + } + else + break; + } + return NULL; +} + +static void freecookie(struct Cookie *co) +{ + free(co->expirestr); + free(co->domain); + free(co->path); + free(co->spath); + free(co->name); + free(co->value); + free(co->maxage); + free(co->version); + free(co); +} + +/* + * cookie path sanitize + */ +static char *sanitize_cookie_path(const char *cookie_path) +{ + size_t len; + char *new_path = strdup(cookie_path); + if(!new_path) + return NULL; + + /* some stupid site sends path attribute with '"'. */ + len = strlen(new_path); + if(new_path[0] == '\"') { + memmove((void *)new_path, (const void *)(new_path + 1), len); + len--; + } + if(len && (new_path[len - 1] == '\"')) { + new_path[len - 1] = 0x0; + len--; + } + + /* RFC6265 5.2.4 The Path Attribute */ + if(new_path[0] != '/') { + /* Let cookie-path be the default-path. */ + free(new_path); + new_path = strdup("/"); + return new_path; + } + + /* convert /hoge/ to /hoge */ + if(len && new_path[len - 1] == '/') { + new_path[len - 1] = 0x0; + } + + return new_path; +} + +/* + * remove_expired() removes expired cookies. + */ +static void remove_expired(struct CookieInfo *cookies) +{ + struct Cookie *co, *nx, *pv; + git_off_t now = (git_off_t)time(NULL); + + co = cookies->cookies; + pv = NULL; + while(co) { + nx = co->next; + if(co->expires && co->expires < now) { + if(co == cookies->cookies) { + cookies->cookies = co->next; + } + else { + pv->next = co->next; + } + cookies->numcookies--; + freecookie(co); + } + else { + pv = co; + } + co = nx; + } +} + +/* Portable, consistent toupper (remember EBCDIC). Do not use toupper() because + its behavior is altered by the current locale. */ +char raw_toupper(char in) +{ + switch(in) { + case 'a': + return 'A'; + case 'b': + return 'B'; + case 'c': + return 'C'; + case 'd': + return 'D'; + case 'e': + return 'E'; + case 'f': + return 'F'; + case 'g': + return 'G'; + case 'h': + return 'H'; + case 'i': + return 'I'; + case 'j': + return 'J'; + case 'k': + return 'K'; + case 'l': + return 'L'; + case 'm': + return 'M'; + case 'n': + return 'N'; + case 'o': + return 'O'; + case 'p': + return 'P'; + case 'q': + return 'Q'; + case 'r': + return 'R'; + case 's': + return 'S'; + case 't': + return 'T'; + case 'u': + return 'U'; + case 'v': + return 'V'; + case 'w': + return 'W'; + case 'x': + return 'X'; + case 'y': + return 'Y'; + case 'z': + return 'Z'; + } + return in; +} + +int strcasecompare(const char *first, const char *second) +{ + while(*first && *second) { + if(raw_toupper(*first) != raw_toupper(*second)) + /* get out of the loop as soon as they don't + * match */ + break; + first++; + second++; + } + /* we do the comparison here (possibly again), just to make sure + * that if the + * loop above is skipped because one of the strings reached + * zero, we must not + * return this as a successful match */ + return (raw_toupper(*first) == raw_toupper(*second)); +} + +/* + * Return true if the given string is an IP(v4|v6) address. + */ +static bool isip(const char *domain) +{ + struct in_addr addr; +#ifdef ENABLE_IPV6 + struct in6_addr addr6; +#endif + + if(inet_pton(AF_INET, domain, &addr) +#ifdef ENABLE_IPV6 + || inet_pton(AF_INET6, domain, &addr6) +#endif + ) { + /* domain name given as IP address */ + return true; + } + + return false; +} + +/* + * matching cookie path and url path + * RFC6265 5.1.4 Paths and Path-Match + */ +static bool pathmatch(const char *cookie_path, const char *request_uri) +{ + size_t cookie_path_len; + size_t uri_path_len; + char *uri_path = NULL; + char *pos; + bool ret = false; + + /* cookie_path must not have last '/' separator. ex: /sample */ + cookie_path_len = strlen(cookie_path); + if(1 == cookie_path_len) { + /* cookie_path must be '/' */ + return true; + } + + uri_path = strdup(request_uri); + if(!uri_path) + return false; + pos = strchr(uri_path, '?'); + if(pos) + *pos = 0x0; + + /* #-fragments are already cut off! */ + if(0 == strlen(uri_path) || uri_path[0] != '/') { + free(uri_path); + uri_path = strdup("/"); + if(!uri_path) + return false; + } + + /* here, RFC6265 5.1.4 says + 4. Output the characters of the uri-path from the first character up + to, but not including, the right-most %x2F ("/"). + but URL path /hoge?fuga=xxx means /hoge/index.cgi?fuga=xxx in some site + without redirect. + Ignore this algorithm because /hoge is uri path for this case + (uri path is not /). + */ + + uri_path_len = strlen(uri_path); + + if(uri_path_len < cookie_path_len) { + ret = false; + goto pathmatched; + } + + /* not using checkprefix() because matching should be case-sensitive */ + if(strncmp(cookie_path, uri_path, cookie_path_len)) { + ret = false; + goto pathmatched; + } + + /* The cookie-path and the uri-path are identical. */ + if(cookie_path_len == uri_path_len) { + ret = true; + goto pathmatched; + } + + /* here, cookie_path_len < url_path_len */ + if(uri_path[cookie_path_len] == '/') { + ret = true; + goto pathmatched; + } + + ret = false; + +pathmatched: + free(uri_path); + return ret; +} + +/* sort this so that the longest path gets before the shorter path */ +static int cookie_sort(const void *p1, const void *p2) +{ + struct Cookie *c1 = *(struct Cookie **)p1; + struct Cookie *c2 = *(struct Cookie **)p2; + size_t l1, l2; + + /* 1 - compare cookie path lengths */ + l1 = c1->path ? strlen(c1->path) : 0; + l2 = c2->path ? strlen(c2->path) : 0; + + if(l1 != l2) + return (l2 > l1) ? 1 : -1 ; /* avoid size_t <=> int conversions */ + + /* 2 - compare cookie domain lengths */ + l1 = c1->domain ? strlen(c1->domain) : 0; + l2 = c2->domain ? strlen(c2->domain) : 0; + + if(l1 != l2) + return (l2 > l1) ? 1 : -1 ; /* avoid size_t <=> int conversions */ + + /* 3 - compare cookie names */ + if(c1->name && c2->name) + return strcmp(c1->name, c2->name); + + /* sorry, can't be more deterministic */ + return 0; +} + +#define CLONE(field) \ + do { \ + if(src->field) { \ + d->field = strdup(src->field); \ + if(!d->field) \ + goto fail; \ + } \ + } while(0) +static struct Cookie *dup_cookie(struct Cookie *src) +{ + struct Cookie *d = calloc(sizeof(struct Cookie), 1); + if(d) { + CLONE(expirestr); + CLONE(domain); + CLONE(path); + CLONE(spath); + CLONE(name); + CLONE(value); + CLONE(maxage); + CLONE(version); + d->expires = src->expires; + d->tailmatch = src->tailmatch; + d->secure = src->secure; + d->httponly = src->httponly; + } + return d; + + fail: + freecookie(d); + return NULL; +} + +static bool tailmatch(const char *cooke_domain, const char *hostname) +{ + size_t cookie_domain_len = strlen(cooke_domain); + size_t hostname_len = strlen(hostname); + + if(hostname_len < cookie_domain_len) + return false; + + if(!strcasecompare(cooke_domain, hostname+hostname_len-cookie_domain_len)) + return false; + + /* A lead char of cookie_domain is not '.'. + RFC6265 4.1.2.3. The Domain Attribute says: + For example, if the value of the Domain attribute is + "example.com", the user agent will include the cookie in the Cookie + header when making HTTP requests to example.com, www.example.com, and + www.corp.example.com. + */ + if(hostname_len == cookie_domain_len) + return true; + if('.' == *(hostname + hostname_len - cookie_domain_len - 1)) + return true; + return false; +} + +struct Cookie *cookie_getlist(struct CookieInfo *c, + const char *host, const char *path, + bool secure) +{ + struct Cookie *newco; + struct Cookie *co; + time_t now = time(NULL); + struct Cookie *mainco=NULL; + size_t matches = 0; + bool is_ip; + + if(!c || !c->cookies) + return NULL; /* no cookie struct or no cookies in the struct */ + + /* at first, remove expired cookies */ + remove_expired(c); + + /* check if host is an IP(v4|v6) address */ + is_ip = isip(host); + + co = c->cookies; + + while(co) { + /* only process this cookie if it is not expired or had no expire + date AND that if the cookie requires we're secure we must only + continue if we are! */ + if((!co->expires || (co->expires > now)) && + (co->secure?secure:true)) { + + /* now check if the domain is correct */ + if(!co->domain || + (co->tailmatch && !is_ip && tailmatch(co->domain, host)) || + ((!co->tailmatch || is_ip) && strcasecompare(host, co->domain)) ) { + /* the right part of the host matches the domain stuff in the + cookie data */ + + /* now check the left part of the path with the cookies path + requirement */ + if(!co->spath || pathmatch(co->spath, path) ) { + + /* and now, we know this is a match and we should create an + entry for the return-linked-list */ + + newco = dup_cookie(co); + if(newco) { + /* then modify our next */ + newco->next = mainco; + + /* point the main to us */ + mainco = newco; + + matches++; + } + else { + fail: + /* failure, clear up the allocated chain and return NULL */ + cookie_freelist(mainco); + return NULL; + } + } + } + } + co = co->next; + } + + if(matches) { + /* Now we need to make sure that if there is a name appearing more than + once, the longest specified path version comes first. To make this + the swiftest way, we just sort them all based on path length. */ + struct Cookie **array; + size_t i; + + /* alloc an array and store all cookie pointers */ + array = malloc(sizeof(struct Cookie *) * matches); + if(!array) + goto fail; + + co = mainco; + + for(i=0; co; co = co->next) + array[i++] = co; + + /* now sort the cookie pointers in path length order */ + qsort(array, matches, sizeof(struct Cookie *), cookie_sort); + + /* remake the linked list order according to the new order */ + + mainco = array[0]; /* start here */ + for(i=0; i<matches-1; i++) + array[i]->next = array[i+1]; + array[matches-1]->next = NULL; /* terminate the list */ + + free(array); /* remove the temporary data again */ + } + + return mainco; /* return the new list */ +} + +struct CookieInfo *cookie_loadfile(const char *cookie_file) +{ + struct CookieInfo *c; + FILE *fp = NULL; + char *line = NULL; + + c = calloc(1, sizeof(struct CookieInfo)); + if(!c) + return NULL; /* failed to get memory */ + + fp = fopen(cookie_file, "r"); + + if(!fp) { + goto fail; + } + + if(fp) { + char *lineptr; + + line = malloc(MAX_COOKIE_LINE); + if(!line) + goto fail; + while(get_line(line, MAX_COOKIE_LINE, fp)) { + lineptr=line; + while(*lineptr && ISBLANK(*lineptr)) + lineptr++; + + cookie_add(c, lineptr); + } + free(line); /* free the line buffer */ + + fclose(fp); + } + return c; + +fail: + cookie_cleanup(c); + if(line) + free(line); + if(fp) + fclose(fp); + return NULL; /* out of memory */ +} + +void cookie_cleanup(struct CookieInfo *c) +{ + if(c) { + cookie_freelist(c->cookies); + free(c); /* free the base struct as well */ + } +} + +void cookie_freelist(struct Cookie *co) +{ + struct Cookie *next; + while(co) { + next = co->next; + freecookie(co); + co = next; + } +} + +struct Cookie *cookie_add(struct CookieInfo *c, char *lineptr) +{ + struct Cookie *clist; + struct Cookie *co; + struct Cookie *lastc = NULL; + bool replace_old = false; + bool badcookie = false; /* cookies are good by default. mmmmm yummy */ + char *ptr; + char *firstptr; + char *tok_buf = NULL; + int fields; + + /* First, alloc and init a new struct for it */ + co = calloc(1, sizeof(struct Cookie)); + if (!co) return NULL; /* bail out if we're this low on memory */ + + /* IE introduced HTTP-only cookies to prevent XSS attacks. Cookies + marked with httpOnly after the domain name are not accessible + from javascripts, but since curl does not operate at javascript + level, we include them anyway. In Firefox's cookie files, these + lines are preceded with #HttpOnly_ and then everything is + as usual, so we skip 10 characters of the line.. + */ + if (strncmp(lineptr, "#HttpOnly_", 10) == 0) { + lineptr += 10; + co->httponly = true; + } + + if (lineptr[0] == '#') { + /* don't even try the comments */ + free(co); + return NULL; + } + /* strip off the possible end-of-line characters */ + ptr = strchr(lineptr, '\r'); + if (ptr) *ptr = 0; /* clear it */ + ptr = strchr(lineptr, '\n'); + if (ptr) *ptr = 0; /* clear it */ + + firstptr = strtok_r(lineptr, "\t", &tok_buf); /* tokenize it on the TAB */ + + /* Now loop through the fields and init the struct we already have + allocated */ + for (ptr = firstptr, fields = 0; ptr && !badcookie; + ptr = strtok_r(NULL, "\t", &tok_buf), fields++) { + switch (fields) { + case 0: + if (ptr[0] == '.') /* skip preceding dots */ + ptr++; + co->domain = strdup(ptr); + if (!co->domain) badcookie = true; + break; + case 1: + /* This field got its explanation on the 23rd of May 2001 by + Andrés García: + + flag: A true/false value indicating if all machines within a given + domain can access the variable. This value is set automatically by + the browser, depending on the value you set for the domain. + + As far as I can see, it is set to true when the cookie says + .domain.com and to false when the domain is complete www.domain.com + */ + co->tailmatch = strcasecompare(ptr, "true") ? true : false; + break; + case 2: + /* It turns out, that sometimes the file format allows the path + field to remain not filled in, we try to detect this and work + around it! Andrés García made us aware of this... */ + if (strcmp("true", ptr) && strcmp("false", ptr)) { + /* only if the path doesn't look like a boolean option! */ + co->path = strdup(ptr); + if (!co->path) + badcookie = true; + else { + co->spath = sanitize_cookie_path(co->path); + if (!co->spath) { + badcookie = true; /* out of memory bad */ + } + } + break; + } + /* this doesn't look like a path, make one up! */ + co->path = strdup("/"); + if (!co->path) badcookie = true; + co->spath = strdup("/"); + if (!co->spath) badcookie = true; + fields++; /* add a field and fall down to secure */ + /* FALLTHROUGH */ + case 3: + co->secure = strcasecompare(ptr, "true") ? true : false; + break; + case 4: + co->expires = strtol(ptr, NULL, 10); + break; + case 5: + co->name = strdup(ptr); + if (!co->name) badcookie = true; + break; + case 6: + co->value = strdup(ptr); + if (!co->value) badcookie = true; + break; + } + } + if (6 == fields) { + /* we got a cookie with blank contents, fix it */ + co->value = strdup(""); + if (!co->value) + badcookie = true; + else + fields++; + } + + if (!badcookie && (7 != fields)) + /* we did not find the sufficient number of fields */ + badcookie = true; + + if (badcookie) { + freecookie(co); + return NULL; + } + + /* now, we have parsed the incoming line, we must now check if this + superceeds an already existing cookie, which it may if the previous have + the same domain and path as this */ + + /* at first, remove expired cookies */ + remove_expired(c); + + clist = c->cookies; + replace_old = false; + while (clist) { + if (strcasecompare(clist->name, co->name)) { + /* the names are identical */ + + if (clist->domain && co->domain) { + if (strcasecompare(clist->domain, co->domain) && + (clist->tailmatch == co->tailmatch)) + /* The domains are identical */ + replace_old = true; + } else if (!clist->domain && !co->domain) + replace_old = true; + + if (replace_old) { + /* the domains were identical */ + + if (clist->spath && co->spath) { + if (strcasecompare(clist->spath, co->spath)) { + replace_old = true; + } else + replace_old = false; + } else if (!clist->spath && !co->spath) + replace_old = true; + else + replace_old = false; + } + + if (replace_old) { + co->next = clist->next; /* get the next-pointer first */ + + /* then free all the old pointers */ + free(clist->name); + free(clist->value); + free(clist->domain); + free(clist->path); + free(clist->spath); + free(clist->expirestr); + free(clist->version); + free(clist->maxage); + + *clist = *co; /* then store all the new data */ + + free(co); /* free the newly alloced memory */ + co = clist; /* point to the previous struct instead */ + + /* We have replaced a cookie, now skip the rest of the list but + make sure the 'lastc' pointer is properly set */ + do { + lastc = clist; + clist = clist->next; + } while (clist); + break; + } + } + lastc = clist; + clist = clist->next; + } + + if (!replace_old) { + /* then make the last item point on this new one */ + if (lastc) + lastc->next = co; + else + c->cookies = co; + c->numcookies++; /* one more cookie in the jar */ + } + + return co; +} + +#endif /* !GIT_WINHTTP */
diff --git a/src/transports/cookie.h b/src/transports/cookie.h new file mode 100644 index 0000000..bacba31 --- /dev/null +++ b/src/transports/cookie.h
@@ -0,0 +1,75 @@ +#ifndef HEADER_CURL_COOKIE_H +#define HEADER_CURL_COOKIE_H +/*************************************************************************** + * Copyright (C) 1998 - 2016, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +#include <stdbool.h> +#include <git2/types.h> + +struct Cookie { + struct Cookie *next; /* next in the chain */ + char *name; /* <this> = value */ + char *value; /* name = <this> */ + char *path; /* path = <this> which is in Set-Cookie: */ + char *spath; /* sanitized cookie path */ + char *domain; /* domain = <this> */ + git_off_t expires; /* expires = <this> */ + char *expirestr; /* the plain text version */ + bool tailmatch; /* weather we do tail-matchning of the domain name */ + + /* RFC 2109 keywords. Version=1 means 2109-compliant cookie sending */ + char *version; /* Version = <value> */ + char *maxage; /* Max-Age = <value> */ + + bool secure; /* whether the 'secure' keyword was used */ + bool httponly; /* true if the httponly directive is present */ +}; + +struct CookieInfo { + /* linked list of cookies we know of */ + struct Cookie *cookies; + + long numcookies; /* number of cookies in the "jar" */ +}; + +/* This is the maximum line length we accept for a cookie line. RFC 2109 + section 6.3 says: + + "at least 4096 bytes per cookie (as measured by the size of the characters + that comprise the cookie non-terminal in the syntax description of the + Set-Cookie header)" + +*/ +#define MAX_COOKIE_LINE 5000 +#define MAX_COOKIE_LINE_TXT "4999" + +/* This is the maximum length of a cookie name we deal with: */ +#define MAX_NAME 1024 +#define MAX_NAME_TXT "1023" + +/* + * Add a cookie to the internal list of cookies. The domain and path arguments + * are only used if the header boolean is TRUE. + */ + +struct Cookie *cookie_add(struct CookieInfo *, char *lineptr); +struct Cookie *cookie_getlist(struct CookieInfo *, const char *, + const char *, bool); +void cookie_freelist(struct Cookie *cookies); +void cookie_cleanup(struct CookieInfo *); +struct CookieInfo *cookie_loadfile(const char *cookie_file); + +#endif /* HEADER_CURL_COOKIE_H */
diff --git a/src/transports/http.c b/src/transports/http.c index cb4a6d0..9d00e91 100644 --- a/src/transports/http.c +++ b/src/transports/http.c
@@ -18,6 +18,7 @@ #include "tls_stream.h" #include "socket_stream.h" #include "curl_stream.h" +#include "cookie.h" git_http_auth_scheme auth_schemes[] = { { GIT_AUTHTYPE_NEGOTIATE, "Negotiate", GIT_CREDTYPE_DEFAULT, git_http_auth_negotiate }, @@ -205,7 +206,10 @@ http_subtransport *t = OWNING_SUBTRANSPORT(s); const char *path = t->connection_data.path ? t->connection_data.path : "/"; size_t i; - + int error; + const char *cookie_file = NULL; + int cookie_count = 0; + git_config *config; git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, path, s->service_url); git_buf_printf(buf, "User-Agent: git/2.0 (%s)\r\n", user_agent()); @@ -222,6 +226,39 @@ } else git_buf_puts(buf, "Accept: */*\r\n"); + if ((error = git_repository_config_snapshot(&config, t->owner->owner->repo)) < 0) + return error; + if ((error = git_config_get_string(&cookie_file, config, "http.cookieFile")) < 0) { + if (error == GIT_ENOTFOUND) { + giterr_clear(); + error = 0; + } else { + git_config_free(config); + return error; + } + } + git_config_free(config); + if (cookie_file) { + struct CookieInfo *c = cookie_loadfile(cookie_file); + if (c){ + struct Cookie *co = cookie_getlist(c, t->connection_data.host, t->connection_data.path, t->connection_data.use_ssl); + while (co) { + if(co->value) { + if (0 == cookie_count) { + git_buf_printf(buf, "Cookie: "); + } + git_buf_printf(buf, "%s%s=%s", cookie_count ? "; " : "", co->name, co->value); + cookie_count++; + } + co = co->next; + } + cookie_cleanup(c); + } + } + if (cookie_count){ + git_buf_printf(buf, "\r\n"); + } + for (i = 0; i < t->owner->custom_headers.count; i++) { if (t->owner->custom_headers.strings[i]) git_buf_printf(buf, "%s\r\n", t->owner->custom_headers.strings[i]);
diff --git a/tests/online/fetch.c b/tests/online/fetch.c index 827cb23..b2fa89f 100644 --- a/tests/online/fetch.c +++ b/tests/online/fetch.c
@@ -84,11 +84,11 @@ cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); cl_git_pass(git_remote_download(remote, NULL, NULL)); git_remote_disconnect(remote); - + git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL); cl_git_pass(git_remote_download(remote, NULL, NULL)); git_remote_disconnect(remote); - + git_remote_free(remote); } @@ -207,3 +207,36 @@ git_remote_free(remote); } + +void test_online_fetch__fake_httpcookiefile(void) +{ + git_remote *remote; + git_config *cfg; + + cl_git_pass(git_remote_create(&remote, _repo, "test", "http://github.com/libgit2/TestGitRepository.git")); + cl_git_pass(git_repository_config(&cfg, _repo)); + cl_git_pass(git_config_set_string(cfg, "http.cookieFile", "fake.cookie")); + git_config_free(cfg); + cl_git_pass(git_remote_fetch(remote, NULL, NULL, NULL)); + + git_remote_free(remote); +} + +void test_online_fetch__with_httpcookiefile(void) +{ + git_remote *remote; + git_config *cfg; + struct stat st; + const char *cookie = cl_fixture("cookie"); + + // make sure file exists + cl_git_pass(p_stat(cookie, &st)); + cl_git_pass(git_remote_create(&remote, _repo, "test", "http://github.com/libgit2/TestGitRepository.git")); + cl_git_pass(git_repository_config(&cfg, _repo)); + + cl_git_pass(git_config_set_string(cfg, "http.cookieFile", cookie)); + git_config_free(cfg); + cl_git_pass(git_remote_fetch(remote, NULL, NULL, NULL)); + + git_remote_free(remote); +}
diff --git a/tests/resources/cookie b/tests/resources/cookie new file mode 100644 index 0000000..3594477 --- /dev/null +++ b/tests/resources/cookie
@@ -0,0 +1 @@ +github.com FALSE / TRUE 2147483647 o git-user.github.com=1/QWmb-q5VvGj3v2CRqwhQucRQqUrdkfZPblgQ2WDxhSY