Merge branch 'jk/http-errors'
Propagate the error messages from the webserver better to the client coming over the HTTP transport. * jk/http-errors: http: default text charset to iso-8859-1 remote-curl: reencode http error messages strbuf: add strbuf_reencode helper http: optionally extract charset parameter from content-type http: extract type/subtype portion of content-type t5550: test display of remote http error messages t/lib-httpd: use write_script to copy CGI scripts test-lib: preserve GIT_CURL_VERBOSE from the environment
This commit is contained in:
commit
2075a0c27f
@ -134,6 +134,11 @@ Functions
|
|||||||
|
|
||||||
Strip whitespace from the beginning of a string.
|
Strip whitespace from the beginning of a string.
|
||||||
|
|
||||||
|
`strbuf_reencode`::
|
||||||
|
|
||||||
|
Replace the contents of the strbuf with a reencoded form. Returns -1
|
||||||
|
on error, 0 on success.
|
||||||
|
|
||||||
`strbuf_tolower`::
|
`strbuf_tolower`::
|
||||||
|
|
||||||
Lowercase each character in the buffer using `tolower`.
|
Lowercase each character in the buffer using `tolower`.
|
||||||
|
87
http.c
87
http.c
@ -906,6 +906,83 @@ static CURLcode curlinfo_strbuf(CURL *curl, CURLINFO info, struct strbuf *buf)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check for and extract a content-type parameter. "raw"
|
||||||
|
* should be positioned at the start of the potential
|
||||||
|
* parameter, with any whitespace already removed.
|
||||||
|
*
|
||||||
|
* "name" is the name of the parameter. The value is appended
|
||||||
|
* to "out".
|
||||||
|
*/
|
||||||
|
static int extract_param(const char *raw, const char *name,
|
||||||
|
struct strbuf *out)
|
||||||
|
{
|
||||||
|
size_t len = strlen(name);
|
||||||
|
|
||||||
|
if (strncasecmp(raw, name, len))
|
||||||
|
return -1;
|
||||||
|
raw += len;
|
||||||
|
|
||||||
|
if (*raw != '=')
|
||||||
|
return -1;
|
||||||
|
raw++;
|
||||||
|
|
||||||
|
while (*raw && !isspace(*raw))
|
||||||
|
strbuf_addch(out, *raw++);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Extract a normalized version of the content type, with any
|
||||||
|
* spaces suppressed, all letters lowercased, and no trailing ";"
|
||||||
|
* or parameters.
|
||||||
|
*
|
||||||
|
* Note that we will silently remove even invalid whitespace. For
|
||||||
|
* example, "text / plain" is specifically forbidden by RFC 2616,
|
||||||
|
* but "text/plain" is the only reasonable output, and this keeps
|
||||||
|
* our code simple.
|
||||||
|
*
|
||||||
|
* If the "charset" argument is not NULL, store the value of any
|
||||||
|
* charset parameter there.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* "TEXT/PLAIN; charset=utf-8" -> "text/plain", "utf-8"
|
||||||
|
* "text / plain" -> "text/plain"
|
||||||
|
*/
|
||||||
|
static void extract_content_type(struct strbuf *raw, struct strbuf *type,
|
||||||
|
struct strbuf *charset)
|
||||||
|
{
|
||||||
|
const char *p;
|
||||||
|
|
||||||
|
strbuf_reset(type);
|
||||||
|
strbuf_grow(type, raw->len);
|
||||||
|
for (p = raw->buf; *p; p++) {
|
||||||
|
if (isspace(*p))
|
||||||
|
continue;
|
||||||
|
if (*p == ';') {
|
||||||
|
p++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
strbuf_addch(type, tolower(*p));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!charset)
|
||||||
|
return;
|
||||||
|
|
||||||
|
strbuf_reset(charset);
|
||||||
|
while (*p) {
|
||||||
|
while (isspace(*p))
|
||||||
|
p++;
|
||||||
|
if (!extract_param(p, "charset", charset))
|
||||||
|
return;
|
||||||
|
while (*p && !isspace(*p))
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!charset->len && starts_with(type->buf, "text/"))
|
||||||
|
strbuf_addstr(charset, "ISO-8859-1");
|
||||||
|
}
|
||||||
|
|
||||||
/* http_request() targets */
|
/* http_request() targets */
|
||||||
#define HTTP_REQUEST_STRBUF 0
|
#define HTTP_REQUEST_STRBUF 0
|
||||||
#define HTTP_REQUEST_FILE 1
|
#define HTTP_REQUEST_FILE 1
|
||||||
@ -957,9 +1034,13 @@ static int http_request(const char *url,
|
|||||||
|
|
||||||
ret = run_one_slot(slot, &results);
|
ret = run_one_slot(slot, &results);
|
||||||
|
|
||||||
if (options && options->content_type)
|
if (options && options->content_type) {
|
||||||
curlinfo_strbuf(slot->curl, CURLINFO_CONTENT_TYPE,
|
struct strbuf raw = STRBUF_INIT;
|
||||||
options->content_type);
|
curlinfo_strbuf(slot->curl, CURLINFO_CONTENT_TYPE, &raw);
|
||||||
|
extract_content_type(&raw, options->content_type,
|
||||||
|
options->charset);
|
||||||
|
strbuf_release(&raw);
|
||||||
|
}
|
||||||
|
|
||||||
if (options && options->effective_url)
|
if (options && options->effective_url)
|
||||||
curlinfo_strbuf(slot->curl, CURLINFO_EFFECTIVE_URL,
|
curlinfo_strbuf(slot->curl, CURLINFO_EFFECTIVE_URL,
|
||||||
|
7
http.h
7
http.h
@ -143,6 +143,13 @@ struct http_get_options {
|
|||||||
/* If non-NULL, returns the content-type of the response. */
|
/* If non-NULL, returns the content-type of the response. */
|
||||||
struct strbuf *content_type;
|
struct strbuf *content_type;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If non-NULL, and content_type above is non-NULL, returns
|
||||||
|
* the charset parameter from the content-type. If none is
|
||||||
|
* present, returns an empty string.
|
||||||
|
*/
|
||||||
|
struct strbuf *charset;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If non-NULL, returns the URL we ended up at, including any
|
* If non-NULL, returns the URL we ended up at, including any
|
||||||
* redirects we followed.
|
* redirects we followed.
|
||||||
|
@ -194,19 +194,19 @@ static void free_discovery(struct discovery *d)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int show_http_message(struct strbuf *type, struct strbuf *msg)
|
static int show_http_message(struct strbuf *type, struct strbuf *charset,
|
||||||
|
struct strbuf *msg)
|
||||||
{
|
{
|
||||||
const char *p, *eol;
|
const char *p, *eol;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We only show text/plain parts, as other types are likely
|
* We only show text/plain parts, as other types are likely
|
||||||
* to be ugly to look at on the user's terminal.
|
* to be ugly to look at on the user's terminal.
|
||||||
*
|
|
||||||
* TODO should handle "; charset=XXX", and re-encode into
|
|
||||||
* logoutputencoding
|
|
||||||
*/
|
*/
|
||||||
if (strcasecmp(type->buf, "text/plain"))
|
if (strcmp(type->buf, "text/plain"))
|
||||||
return -1;
|
return -1;
|
||||||
|
if (charset->len)
|
||||||
|
strbuf_reencode(msg, charset->buf, get_log_output_encoding());
|
||||||
|
|
||||||
strbuf_trim(msg);
|
strbuf_trim(msg);
|
||||||
if (!msg->len)
|
if (!msg->len)
|
||||||
@ -225,6 +225,7 @@ static struct discovery* discover_refs(const char *service, int for_push)
|
|||||||
{
|
{
|
||||||
struct strbuf exp = STRBUF_INIT;
|
struct strbuf exp = STRBUF_INIT;
|
||||||
struct strbuf type = STRBUF_INIT;
|
struct strbuf type = STRBUF_INIT;
|
||||||
|
struct strbuf charset = STRBUF_INIT;
|
||||||
struct strbuf buffer = STRBUF_INIT;
|
struct strbuf buffer = STRBUF_INIT;
|
||||||
struct strbuf refs_url = STRBUF_INIT;
|
struct strbuf refs_url = STRBUF_INIT;
|
||||||
struct strbuf effective_url = STRBUF_INIT;
|
struct strbuf effective_url = STRBUF_INIT;
|
||||||
@ -249,6 +250,7 @@ static struct discovery* discover_refs(const char *service, int for_push)
|
|||||||
|
|
||||||
memset(&options, 0, sizeof(options));
|
memset(&options, 0, sizeof(options));
|
||||||
options.content_type = &type;
|
options.content_type = &type;
|
||||||
|
options.charset = &charset;
|
||||||
options.effective_url = &effective_url;
|
options.effective_url = &effective_url;
|
||||||
options.base_url = &url;
|
options.base_url = &url;
|
||||||
options.no_cache = 1;
|
options.no_cache = 1;
|
||||||
@ -259,13 +261,13 @@ static struct discovery* discover_refs(const char *service, int for_push)
|
|||||||
case HTTP_OK:
|
case HTTP_OK:
|
||||||
break;
|
break;
|
||||||
case HTTP_MISSING_TARGET:
|
case HTTP_MISSING_TARGET:
|
||||||
show_http_message(&type, &buffer);
|
show_http_message(&type, &charset, &buffer);
|
||||||
die("repository '%s' not found", url.buf);
|
die("repository '%s' not found", url.buf);
|
||||||
case HTTP_NOAUTH:
|
case HTTP_NOAUTH:
|
||||||
show_http_message(&type, &buffer);
|
show_http_message(&type, &charset, &buffer);
|
||||||
die("Authentication failed for '%s'", url.buf);
|
die("Authentication failed for '%s'", url.buf);
|
||||||
default:
|
default:
|
||||||
show_http_message(&type, &buffer);
|
show_http_message(&type, &charset, &buffer);
|
||||||
die("unable to access '%s': %s", url.buf, curl_errorstr);
|
die("unable to access '%s': %s", url.buf, curl_errorstr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,6 +312,7 @@ static struct discovery* discover_refs(const char *service, int for_push)
|
|||||||
strbuf_release(&refs_url);
|
strbuf_release(&refs_url);
|
||||||
strbuf_release(&exp);
|
strbuf_release(&exp);
|
||||||
strbuf_release(&type);
|
strbuf_release(&type);
|
||||||
|
strbuf_release(&charset);
|
||||||
strbuf_release(&effective_url);
|
strbuf_release(&effective_url);
|
||||||
strbuf_release(&buffer);
|
strbuf_release(&buffer);
|
||||||
last_discovery = last;
|
last_discovery = last;
|
||||||
|
17
strbuf.c
17
strbuf.c
@ -1,5 +1,6 @@
|
|||||||
#include "cache.h"
|
#include "cache.h"
|
||||||
#include "refs.h"
|
#include "refs.h"
|
||||||
|
#include "utf8.h"
|
||||||
|
|
||||||
int starts_with(const char *str, const char *prefix)
|
int starts_with(const char *str, const char *prefix)
|
||||||
{
|
{
|
||||||
@ -99,6 +100,22 @@ void strbuf_ltrim(struct strbuf *sb)
|
|||||||
sb->buf[sb->len] = '\0';
|
sb->buf[sb->len] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int strbuf_reencode(struct strbuf *sb, const char *from, const char *to)
|
||||||
|
{
|
||||||
|
char *out;
|
||||||
|
int len;
|
||||||
|
|
||||||
|
if (same_encoding(from, to))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
out = reencode_string_len(sb->buf, sb->len, to, from, &len);
|
||||||
|
if (!out)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
strbuf_attach(sb, out, len, len);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void strbuf_tolower(struct strbuf *sb)
|
void strbuf_tolower(struct strbuf *sb)
|
||||||
{
|
{
|
||||||
char *p = sb->buf, *end = sb->buf + sb->len;
|
char *p = sb->buf, *end = sb->buf + sb->len;
|
||||||
|
1
strbuf.h
1
strbuf.h
@ -45,6 +45,7 @@ static inline void strbuf_setlen(struct strbuf *sb, size_t len)
|
|||||||
extern void strbuf_trim(struct strbuf *);
|
extern void strbuf_trim(struct strbuf *);
|
||||||
extern void strbuf_rtrim(struct strbuf *);
|
extern void strbuf_rtrim(struct strbuf *);
|
||||||
extern void strbuf_ltrim(struct strbuf *);
|
extern void strbuf_ltrim(struct strbuf *);
|
||||||
|
extern int strbuf_reencode(struct strbuf *sb, const char *from, const char *to);
|
||||||
extern void strbuf_tolower(struct strbuf *sb);
|
extern void strbuf_tolower(struct strbuf *sb);
|
||||||
extern int strbuf_cmp(const struct strbuf *, const struct strbuf *);
|
extern int strbuf_cmp(const struct strbuf *, const struct strbuf *);
|
||||||
|
|
||||||
|
@ -110,10 +110,15 @@ else
|
|||||||
"Could not identify web server at '$LIB_HTTPD_PATH'"
|
"Could not identify web server at '$LIB_HTTPD_PATH'"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
install_script () {
|
||||||
|
write_script "$HTTPD_ROOT_PATH/$1" <"$TEST_PATH/$1"
|
||||||
|
}
|
||||||
|
|
||||||
prepare_httpd() {
|
prepare_httpd() {
|
||||||
mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH"
|
mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH"
|
||||||
cp "$TEST_PATH"/passwd "$HTTPD_ROOT_PATH"
|
cp "$TEST_PATH"/passwd "$HTTPD_ROOT_PATH"
|
||||||
cp "$TEST_PATH"/broken-smart-http.sh "$HTTPD_ROOT_PATH"
|
install_script broken-smart-http.sh
|
||||||
|
install_script error.sh
|
||||||
|
|
||||||
ln -s "$LIB_HTTPD_MODULE_PATH" "$HTTPD_ROOT_PATH/modules"
|
ln -s "$LIB_HTTPD_MODULE_PATH" "$HTTPD_ROOT_PATH/modules"
|
||||||
|
|
||||||
|
@ -97,12 +97,16 @@ Alias /auth/dumb/ www/auth/dumb/
|
|||||||
</LocationMatch>
|
</LocationMatch>
|
||||||
ScriptAliasMatch /smart_*[^/]*/(.*) ${GIT_EXEC_PATH}/git-http-backend/$1
|
ScriptAliasMatch /smart_*[^/]*/(.*) ${GIT_EXEC_PATH}/git-http-backend/$1
|
||||||
ScriptAlias /broken_smart/ broken-smart-http.sh/
|
ScriptAlias /broken_smart/ broken-smart-http.sh/
|
||||||
|
ScriptAlias /error/ error.sh/
|
||||||
<Directory ${GIT_EXEC_PATH}>
|
<Directory ${GIT_EXEC_PATH}>
|
||||||
Options FollowSymlinks
|
Options FollowSymlinks
|
||||||
</Directory>
|
</Directory>
|
||||||
<Files broken-smart-http.sh>
|
<Files broken-smart-http.sh>
|
||||||
Options ExecCGI
|
Options ExecCGI
|
||||||
</Files>
|
</Files>
|
||||||
|
<Files error.sh>
|
||||||
|
Options ExecCGI
|
||||||
|
</Files>
|
||||||
<Files ${GIT_EXEC_PATH}/git-http-backend>
|
<Files ${GIT_EXEC_PATH}/git-http-backend>
|
||||||
Options ExecCGI
|
Options ExecCGI
|
||||||
</Files>
|
</Files>
|
||||||
|
1
t/lib-httpd/broken-smart-http.sh
Executable file → Normal file
1
t/lib-httpd/broken-smart-http.sh
Executable file → Normal file
@ -1,4 +1,3 @@
|
|||||||
#!/bin/sh
|
|
||||||
printf "Content-Type: text/%s\n" "html"
|
printf "Content-Type: text/%s\n" "html"
|
||||||
echo
|
echo
|
||||||
printf "%s\n" "001e# service=git-upload-pack"
|
printf "%s\n" "001e# service=git-upload-pack"
|
||||||
|
27
t/lib-httpd/error.sh
Executable file
27
t/lib-httpd/error.sh
Executable file
@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
printf "Status: 500 Intentional Breakage\n"
|
||||||
|
|
||||||
|
printf "Content-Type: "
|
||||||
|
charset=iso-8859-1
|
||||||
|
case "$PATH_INFO" in
|
||||||
|
*html*)
|
||||||
|
printf "text/html"
|
||||||
|
;;
|
||||||
|
*text*)
|
||||||
|
printf "text/plain"
|
||||||
|
;;
|
||||||
|
*charset*)
|
||||||
|
printf "text/plain; charset=utf-8"
|
||||||
|
charset=utf-8
|
||||||
|
;;
|
||||||
|
*utf16*)
|
||||||
|
printf "text/plain; charset=utf-16"
|
||||||
|
charset=utf-16
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
printf "\n"
|
||||||
|
|
||||||
|
printf "\n"
|
||||||
|
printf "this is the error message\n" |
|
||||||
|
iconv -f us-ascii -t $charset
|
@ -171,5 +171,25 @@ test_expect_success 'did not use upload-pack service' '
|
|||||||
test_cmp exp act
|
test_cmp exp act
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git client shows text/plain errors' '
|
||||||
|
test_must_fail git clone "$HTTPD_URL/error/text" 2>stderr &&
|
||||||
|
grep "this is the error message" stderr
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git client does not show html errors' '
|
||||||
|
test_must_fail git clone "$HTTPD_URL/error/html" 2>stderr &&
|
||||||
|
! grep "this is the error message" stderr
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git client shows text/plain with a charset' '
|
||||||
|
test_must_fail git clone "$HTTPD_URL/error/charset" 2>stderr &&
|
||||||
|
grep "this is the error message" stderr
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'http error messages are reencoded' '
|
||||||
|
test_must_fail git clone "$HTTPD_URL/error/utf16" 2>stderr &&
|
||||||
|
grep "this is the error message" stderr
|
||||||
|
'
|
||||||
|
|
||||||
stop_httpd
|
stop_httpd
|
||||||
test_done
|
test_done
|
||||||
|
@ -91,6 +91,7 @@ unset VISUAL EMAIL LANGUAGE COLUMNS $("$PERL_PATH" -e '
|
|||||||
VALGRIND
|
VALGRIND
|
||||||
UNZIP
|
UNZIP
|
||||||
PERF_
|
PERF_
|
||||||
|
CURL_VERBOSE
|
||||||
));
|
));
|
||||||
my @vars = grep(/^GIT_/ && !/^GIT_($ok)/o, @env);
|
my @vars = grep(/^GIT_/ && !/^GIT_($ok)/o, @env);
|
||||||
print join("\n", @vars);
|
print join("\n", @vars);
|
||||||
|
Loading…
Reference in New Issue
Block a user