{"thread":{"id":"38590","subject":"[PATCH 01/10] wincred: fix get credential if username has \"@\"","startedAt":"2015-02-18T19:01:54Z","lastAt":"2015-02-18T19:03:06Z","messageCount":3,"participants":["Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":10},"messages":[{"id":"256297","messageId":"1424286123-12985-1-git-send-email-gitster@pobox.com","threadId":"38590","inReplyTo":null,"subject":"[PATCH 01/10] wincred: fix get credential if username has \"@\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-02-18T19:01:54Z","receivedAt":"2015-02-18T19:01:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"From: Aleksey Vasenev <margtu-fivt@ya.ru>\n\nSuch a username with \"@\" in it isn't all that unusual these days.\n\ncf. https://groups.google.com/forum/#!msg/msysgit/YVuCqmwwRyY/HULHj5OoE88J\n\nSigned-off-by: Aleksey Vasenev <margtu-fivt@ya.ru>\nAcked-by: Erik Faye-Lund <kusmabite@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n .../credential/wincred/git-credential-wincred.c    | 25 +++++++++++++++++++---\n 1 file changed, 22 insertions(+), 3 deletions(-)\n\ndiff --git a/contrib/credential/wincred/git-credential-wincred.c b/contrib/credential/wincred/git-credential-wincred.c\nindex a1d38f0..0061340 100644\n--- a/contrib/credential/wincred/git-credential-wincred.c\n+++ b/contrib/credential/wincred/git-credential-wincred.c\n@@ -111,14 +111,23 @@ static void write_item(const char *what, LPCWSTR wbuf, int wlen)\n  * Match an (optional) expected string and a delimiter in the target string,\n  * consuming the matched text by updating the target pointer.\n  */\n-static int match_part(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim)\n+\n+static LPCWSTR wcsstr_last(LPCWSTR str, LPCWSTR find)\n+{\n+\tLPCWSTR res = NULL, pos;\n+\tfor (pos = wcsstr(str, find); pos; pos = wcsstr(pos + 1, find))\n+\t\tres = pos;\n+\treturn res;\n+}\n+\n+static int match_part_with_last(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim, int last)\n {\n \tLPCWSTR delim_pos, start = *ptarget;\n \tint len;\n \n \t/* find start of delimiter (or end-of-string if delim is empty) */\n \tif (*delim)\n-\t\tdelim_pos = wcsstr(start, delim);\n+\t\tdelim_pos = last ? wcsstr_last(start, delim) : wcsstr(start, delim);\n \telse\n \t\tdelim_pos = start + wcslen(start);\n \n@@ -138,6 +147,16 @@ static int match_part(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim)\n \treturn !want || (!wcsncmp(want, start, len) && !want[len]);\n }\n \n+static int match_part(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim)\n+{\n+\treturn match_part_with_last(ptarget, want, delim, 0);\n+}\n+\n+static int match_part_last(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim)\n+{\n+\treturn match_part_with_last(ptarget, want, delim, 1);\n+}\n+\n static int match_cred(const CREDENTIALW *cred)\n {\n \tLPCWSTR target = cred->TargetName;\n@@ -146,7 +165,7 @@ static int match_cred(const CREDENTIALW *cred)\n \n \treturn match_part(&target, L\"git\", L\":\") &&\n \t\tmatch_part(&target, protocol, L\"://\") &&\n-\t\tmatch_part(&target, wusername, L\"@\") &&\n+\t\tmatch_part_last(&target, wusername, L\"@\") &&\n \t\tmatch_part(&target, host, L\"/\") &&\n \t\tmatch_part(&target, path, L\"\");\n }\n-- \n2.3.0-301-g71e72fe\n"},{"id":"256298","messageId":"1424286123-12985-2-git-send-email-gitster@pobox.com","threadId":"38590","inReplyTo":"1424286123-12985-1-git-send-email-gitster@pobox.com","subject":"[PATCH 02/10] http: add Accept-Language header if possible","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-02-18T19:01:55Z","receivedAt":"2015-02-18T19:01:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"From: Yi EungJun <eungjun.yi@navercorp.com>\n\nAdd an Accept-Language header which indicates the user's preferred\nlanguages defined by $LANGUAGE, $LC_ALL, $LC_MESSAGES and $LANG.\n\nExamples:\n  LANGUAGE= -> \"\"\n  LANGUAGE=ko:en -> \"Accept-Language: ko, en;q=0.9, *;q=0.1\"\n  LANGUAGE=ko LANG=en_US.UTF-8 -> \"Accept-Language: ko, *;q=0.1\"\n  LANGUAGE= LANG=en_US.UTF-8 -> \"Accept-Language: en-US, *;q=0.1\"\n\nThis gives git servers a chance to display remote error messages in\nthe user's preferred language.\n\nLimit the number of languages to 1,000 because q-value must not be\nsmaller than 0.001, and limit the length of Accept-Language header to\n4,000 bytes for some HTTP servers which cannot accept such long header.\n\nSigned-off-by: Yi EungJun <eungjun.yi@navercorp.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n http.c                     | 147 +++++++++++++++++++++++++++++++++++++++++++++\n remote-curl.c              |   2 +\n t/t5550-http-fetch-dumb.sh |  42 +++++++++++++\n 3 files changed, 191 insertions(+)\n\ndiff --git a/http.c b/http.c\nindex 040f362..8b659b6 100644\n--- a/http.c\n+++ b/http.c\n@@ -68,6 +68,8 @@ static struct curl_slist *no_pragma_header;\n \n static struct active_request_slot *active_queue_head;\n \n+static char *cached_accept_language;\n+\n size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_)\n {\n \tsize_t size = eltsize * nmemb;\n@@ -515,6 +517,9 @@ void http_cleanup(void)\n \t\tcert_auth.password = NULL;\n \t}\n \tssl_cert_password_required = 0;\n+\n+\tfree(cached_accept_language);\n+\tcached_accept_language = NULL;\n }\n \n struct active_request_slot *get_active_slot(void)\n@@ -986,6 +991,142 @@ static void extract_content_type(struct strbuf *raw, struct strbuf *type,\n \t\tstrbuf_addstr(charset, \"ISO-8859-1\");\n }\n \n+/*\n+ * Guess the user's preferred languages from the value in LANGUAGE environment\n+ * variable and LC_MESSAGES locale category if NO_GETTEXT is not defined.\n+ *\n+ * The result can be a colon-separated list like \"ko:ja:en\".\n+ */\n+static const char *get_preferred_languages(void)\n+{\n+\tconst char *retval;\n+\n+\tretval = getenv(\"LANGUAGE\");\n+\tif (retval && *retval)\n+\t\treturn retval;\n+\n+#ifndef NO_GETTEXT\n+\tretval = setlocale(LC_MESSAGES, NULL);\n+\tif (retval && *retval &&\n+\t\tstrcmp(retval, \"C\") &&\n+\t\tstrcmp(retval, \"POSIX\"))\n+\t\treturn retval;\n+#endif\n+\n+\treturn NULL;\n+}\n+\n+static void write_accept_language(struct strbuf *buf)\n+{\n+\t/*\n+\t * MAX_DECIMAL_PLACES must not be larger than 3. If it is larger than\n+\t * that, q-value will be smaller than 0.001, the minimum q-value the\n+\t * HTTP specification allows. See\n+\t * http://tools.ietf.org/html/rfc7231#section-5.3.1 for q-value.\n+\t */\n+\tconst int MAX_DECIMAL_PLACES = 3;\n+\tconst int MAX_LANGUAGE_TAGS = 1000;\n+\tconst int MAX_ACCEPT_LANGUAGE_HEADER_SIZE = 4000;\n+\tchar **language_tags = NULL;\n+\tint num_langs = 0;\n+\tconst char *s = get_preferred_languages();\n+\tint i;\n+\tstruct strbuf tag = STRBUF_INIT;\n+\n+\t/* Don't add Accept-Language header if no language is preferred. */\n+\tif (!s)\n+\t\treturn;\n+\n+\t/*\n+\t * Split the colon-separated string of preferred languages into\n+\t * language_tags array.\n+\t */\n+\tdo {\n+\t\t/* collect language tag */\n+\t\tfor (; *s && (isalnum(*s) || *s == '_'); s++)\n+\t\t\tstrbuf_addch(&tag, *s == '_' ? '-' : *s);\n+\n+\t\t/* skip .codeset, @modifier and any other unnecessary parts */\n+\t\twhile (*s && *s != ':')\n+\t\t\ts++;\n+\n+\t\tif (tag.len) {\n+\t\t\tnum_langs++;\n+\t\t\tREALLOC_ARRAY(language_tags, num_langs);\n+\t\t\tlanguage_tags[num_langs - 1] = strbuf_detach(&tag, NULL);\n+\t\t\tif (num_langs >= MAX_LANGUAGE_TAGS - 1) /* -1 for '*' */\n+\t\t\t\tbreak;\n+\t\t}\n+\t} while (*s++);\n+\n+\t/* write Accept-Language header into buf */\n+\tif (num_langs) {\n+\t\tint last_buf_len = 0;\n+\t\tint max_q;\n+\t\tint decimal_places;\n+\t\tchar q_format[32];\n+\n+\t\t/* add '*' */\n+\t\tREALLOC_ARRAY(language_tags, num_langs + 1);\n+\t\tlanguage_tags[num_langs++] = \"*\"; /* it's OK; this won't be freed */\n+\n+\t\t/* compute decimal_places */\n+\t\tfor (max_q = 1, decimal_places = 0;\n+\t\t     max_q < num_langs && decimal_places <= MAX_DECIMAL_PLACES;\n+\t\t     decimal_places++, max_q *= 10)\n+\t\t\t;\n+\n+\t\tsprintf(q_format, \";q=0.%%0%dd\", decimal_places);\n+\n+\t\tstrbuf_addstr(buf, \"Accept-Language: \");\n+\n+\t\tfor (i = 0; i < num_langs; i++) {\n+\t\t\tif (i > 0)\n+\t\t\t\tstrbuf_addstr(buf, \", \");\n+\n+\t\t\tstrbuf_addstr(buf, language_tags[i]);\n+\n+\t\t\tif (i > 0)\n+\t\t\t\tstrbuf_addf(buf, q_format, max_q - i);\n+\n+\t\t\tif (buf->len > MAX_ACCEPT_LANGUAGE_HEADER_SIZE) {\n+\t\t\t\tstrbuf_remove(buf, last_buf_len, buf->len - last_buf_len);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\n+\t\t\tlast_buf_len = buf->len;\n+\t\t}\n+\t}\n+\n+\t/* free language tags -- last one is a static '*' */\n+\tfor (i = 0; i < num_langs - 1; i++)\n+\t\tfree(language_tags[i]);\n+\tfree(language_tags);\n+}\n+\n+/*\n+ * Get an Accept-Language header which indicates user's preferred languages.\n+ *\n+ * Examples:\n+ *   LANGUAGE= -> \"\"\n+ *   LANGUAGE=ko:en -> \"Accept-Language: ko, en; q=0.9, *; q=0.1\"\n+ *   LANGUAGE=ko_KR.UTF-8:sr@latin -> \"Accept-Language: ko-KR, sr; q=0.9, *; q=0.1\"\n+ *   LANGUAGE=ko LANG=en_US.UTF-8 -> \"Accept-Language: ko, *; q=0.1\"\n+ *   LANGUAGE= LANG=en_US.UTF-8 -> \"Accept-Language: en-US, *; q=0.1\"\n+ *   LANGUAGE= LANG=C -> \"\"\n+ */\n+static const char *get_accept_language(void)\n+{\n+\tif (!cached_accept_language) {\n+\t\tstruct strbuf buf = STRBUF_INIT;\n+\t\twrite_accept_language(&buf);\n+\t\tif (buf.len > 0)\n+\t\t\tcached_accept_language = strbuf_detach(&buf, NULL);\n+\t}\n+\n+\treturn cached_accept_language;\n+}\n+\n /* http_request() targets */\n #define HTTP_REQUEST_STRBUF\t0\n #define HTTP_REQUEST_FILE\t1\n@@ -998,6 +1139,7 @@ static int http_request(const char *url,\n \tstruct slot_results results;\n \tstruct curl_slist *headers = NULL;\n \tstruct strbuf buf = STRBUF_INIT;\n+\tconst char *accept_language;\n \tint ret;\n \n \tslot = get_active_slot();\n@@ -1023,6 +1165,11 @@ static int http_request(const char *url,\n \t\t\t\t\t fwrite_buffer);\n \t}\n \n+\taccept_language = get_accept_language();\n+\n+\tif (accept_language)\n+\t\theaders = curl_slist_append(headers, accept_language);\n+\n \tstrbuf_addstr(&buf, \"Pragma:\");\n \tif (options && options->no_cache)\n \t\tstrbuf_addstr(&buf, \" no-cache\");\ndiff --git a/remote-curl.c b/remote-curl.c\nindex dd63bc2..04989e5 100644\n--- a/remote-curl.c\n+++ b/remote-curl.c\n@@ -962,6 +962,8 @@ int main(int argc, const char **argv)\n \tstruct strbuf buf = STRBUF_INIT;\n \tint nongit;\n \n+\tgit_setup_gettext();\n+\n \tgit_extract_argv0_path(argv[0]);\n \tsetup_git_directory_gently(&nongit);\n \tif (argc < 2) {\ndiff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh\nindex ac71418..e1e2938 100755\n--- a/t/t5550-http-fetch-dumb.sh\n+++ b/t/t5550-http-fetch-dumb.sh\n@@ -196,5 +196,47 @@ test_expect_success 'reencoding is robust to whitespace oddities' '\n \tgrep \"this is the error message\" stderr\n '\n \n+check_language () {\n+\tcase \"$2\" in\n+\t'')\n+\t\t>expect\n+\t\t;;\n+\t?*)\n+\t\techo \"Accept-Language: $1\" >expect\n+\t\t;;\n+\tesac &&\n+\tGIT_CURL_VERBOSE=1 \\\n+\tLANGUAGE=$2 \\\n+\tgit ls-remote \"$HTTPD_URL/dumb/repo.git\" >output 2>&1 &&\n+\ttr -d '\\015' <output |\n+\tsort -u |\n+\tsed -ne '/^Accept-Language:/ p' >actual &&\n+\ttest_cmp expect actual\n+}\n+\n+test_expect_success 'git client sends Accept-Language based on LANGUAGE' '\n+\tcheck_language \"ko-KR, *;q=0.9\" ko_KR.UTF-8'\n+\n+test_expect_success 'git client sends Accept-Language correctly with unordinary LANGUAGE' '\n+\tcheck_language \"ko-KR, *;q=0.9\" \"ko_KR:\" &&\n+\tcheck_language \"ko-KR, en-US;q=0.9, *;q=0.8\" \"ko_KR::en_US\" &&\n+\tcheck_language \"ko-KR, *;q=0.9\" \":::ko_KR\" &&\n+\tcheck_language \"ko-KR, en-US;q=0.9, *;q=0.8\" \"ko_KR!!:en_US\" &&\n+\tcheck_language \"ko-KR, ja-JP;q=0.9, *;q=0.8\" \"ko_KR en_US:ja_JP\"'\n+\n+test_expect_success 'git client sends Accept-Language with many preferred languages' '\n+\tcheck_language \"ko-KR, en-US;q=0.9, fr-CA;q=0.8, de;q=0.7, sr;q=0.6, \\\n+ja;q=0.5, zh;q=0.4, sv;q=0.3, pt;q=0.2, *;q=0.1\" \\\n+\t\tko_KR.EUC-KR:en_US.UTF-8:fr_CA:de.UTF-8@euro:sr@latin:ja:zh:sv:pt &&\n+\tcheck_language \"ko-KR, en-US;q=0.99, fr-CA;q=0.98, de;q=0.97, sr;q=0.96, \\\n+ja;q=0.95, zh;q=0.94, sv;q=0.93, pt;q=0.92, nb;q=0.91, *;q=0.90\" \\\n+\t\tko_KR.EUC-KR:en_US.UTF-8:fr_CA:de.UTF-8@euro:sr@latin:ja:zh:sv:pt:nb\n+'\n+\n+test_expect_success 'git client does not send an empty Accept-Language' '\n+\tGIT_CURL_VERBOSE=1 LANGUAGE= git ls-remote \"$HTTPD_URL/dumb/repo.git\" 2>stderr &&\n+\t! grep \"^Accept-Language:\" stderr\n+'\n+\n stop_httpd\n test_done\n-- \n2.3.0-301-g71e72fe\n"},{"id":"256299","messageId":"xmqqlhjv2gb9.fsf@gitster.dls.corp.google.com","threadId":"38590","inReplyTo":"1424286123-12985-1-git-send-email-gitster@pobox.com","subject":"Re: [PATCH 01/10] wincred: fix get credential if username has \"@\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2015-02-18T19:03:06Z","receivedAt":"2015-02-18T19:03:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Sorry, please disregard these sent by mistake.\n"}]}