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