http: extract type/subtype portion of content-type

When we get a content-type from curl, we get the whole header line, including any parameters, and without any normalization (like downcasing or whitespace) applied. If we later try to match it with strcmp() or even strcasecmp(), we may get false negatives. This could cause two visible behaviors: 1. We might fail to recognize a smart-http server by its content-type. 2. We might fail to relay text/plain error messages to users (especially if they contain a charset parameter). This patch teaches the http code to extract and normalize just the type/subtype portion of the string. This is technically passing out less information to the callers, who can no longer see the parameters. But none of the current callers cares, and a future patch will add back an easier-to-use method for accessing those parameters. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed May 22, 2014 at 05:29 UTC bf197fd7eebcb3579dd659af35822ce88adc66c8
4 files changed +48 -5
http.c
+35 -3
@@ -906,6 +906,35 @@ static CURLcode curlinfo_strbuf(CURL *curl, CURLINFO info, struct strbuf *buf)
906 return ret;
907 }
908
909 +/*
910 + * Extract a normalized version of the content type, with any
911 + * spaces suppressed, all letters lowercased, and no trailing ";"
912 + * or parameters.
913 + *
914 + * Note that we will silently remove even invalid whitespace. For
915 + * example, "text / plain" is specifically forbidden by RFC 2616,
916 + * but "text/plain" is the only reasonable output, and this keeps
917 + * our code simple.
918 + *
919 + * Example:
920 + * "TEXT/PLAIN; charset=utf-8" -> "text/plain"
921 + * "text / plain" -> "text/plain"
922 + */
923 +static void extract_content_type(struct strbuf *raw, struct strbuf *type)
924 +{
925 + const char *p;
926 +
927 + strbuf_reset(type);
928 + strbuf_grow(type, raw->len);
929 + for (p = raw->buf; *p; p++) {
930 + if (isspace(*p))
931 + continue;
932 + if (*p == ';')
933 + break;
934 + strbuf_addch(type, tolower(*p));
935 + }
936 +}
937 +
938 /* http_request() targets */
939 #define HTTP_REQUEST_STRBUF 0
940 #define HTTP_REQUEST_FILE 1
@@ -957,9 +986,12 @@ static int http_request(const char *url,
986
987 ret = run_one_slot(slot, &results);
988
960 - if (options && options->content_type)
961 - curlinfo_strbuf(slot->curl, CURLINFO_CONTENT_TYPE,
962 - options->content_type);
989 + if (options && options->content_type) {
990 + struct strbuf raw = STRBUF_INIT;
991 + curlinfo_strbuf(slot->curl, CURLINFO_CONTENT_TYPE, &raw);
992 + extract_content_type(&raw, options->content_type);
993 + strbuf_release(&raw);
994 + }
995
996 if (options && options->effective_url)
997 curlinfo_strbuf(slot->curl, CURLINFO_EFFECTIVE_URL,
remote-curl.c
+1 -1
@@ -205,7 +205,7 @@ static int show_http_message(struct strbuf *type, struct strbuf *msg)
205 * TODO should handle "; charset=XXX", and re-encode into
206 * logoutputencoding
207 */
208 - if (strcasecmp(type->buf, "text/plain"))
208 + if (strcmp(type->buf, "text/plain"))
209 return -1;
210
211 strbuf_trim(msg);
t/lib-httpd/error.sh
+7 -1
@@ -3,6 +3,7 @@
3 printf "Status: 500 Intentional Breakage\n"
4
5 printf "Content-Type: "
6 +charset=iso-8859-1
7 case "$PATH_INFO" in
8 *html*)
9 printf "text/html"
@@ -10,8 +11,13 @@ case "$PATH_INFO" in
11 *text*)
12 printf "text/plain"
13 ;;
14 +*charset*)
15 + printf "text/plain; charset=utf-8"
16 + charset=utf-8
17 + ;;
18 esac
19 printf "\n"
20
21 printf "\n"
17 -printf "this is the error message\n"
22 +printf "this is the error message\n" |
23 +iconv -f us-ascii -t $charset
t/t5550-http-fetch-dumb.sh
+5
@@ -181,5 +181,10 @@ test_expect_success 'git client does not show html errors' '
181 ! grep "this is the error message" stderr
182 '
183
184 +test_expect_success 'git client shows text/plain with a charset' '
185 + test_must_fail git clone "$HTTPD_URL/error/charset" 2>stderr &&
186 + grep "this is the error message" stderr
187 +'
188 +
189 stop_httpd
190 test_done