@matheusbsbs / matheus-bagliano / commits / 556cfa3b6d
Fork 0

Smart fetch and push over HTTP: server side

Requests for $GIT_URL/git-receive-pack and $GIT_URL/git-upload-pack are forwarded to the corresponding backend process by directly executing it and leaving stdin and stdout connected to the invoking web server. Prior to starting the backend process the HTTP response headers are sent, thereby freeing the backend from needing to know about the HTTP protocol. Requests that are encoded with Content-Encoding: gzip are automatically inflated before being streamed into the backend. This is primarily useful for the git-upload-pack backend, which receives highly repetitive text data from clients that easily compresses to 50% of its original size. Signed-off-by: Shawn O. Pearce <spearce@spearce.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Shawn O. Pearce committed Oct 30, 2009 at 17:47 UTC 556cfa3b6d316074d41cd73c4659402fdb6207c8
2 files changed +359 -4
Documentation/git-http-backend.txt
+38 -1
@@ -22,6 +22,23 @@ By default, only the `upload-pack` service is enabled, which serves
22 This is ideally suited for read-only updates, i.e., pulling from
23 git repositories.
24
25 +SERVICES
26 +--------
27 +These services can be enabled/disabled using the per-repository
28 +configuration file:
29 +
30 +http.uploadpack::
31 + This serves 'git-fetch-pack' and 'git-ls-remote' clients.
32 + It is enabled by default, but a repository can disable it
33 + by setting this configuration item to `false`.
34 +
35 +http.receivepack::
36 + This serves 'git-send-pack' clients, allowing push. It is
37 + disabled by default for anonymous users, and enabled by
38 + default for users authenticated by the web server. It can be
39 + disabled by setting this item to `false`, or enabled for all
40 + users, including anonymous users, by setting it to `true`.
41 +
42 URL TRANSLATION
43 ---------------
44 'git-http-backend' relies on the invoking web server to perform
@@ -49,7 +66,19 @@ ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/git/
66 </Files>
67 ----------------------------------------------------------------
68 +
52 -To require authentication for reads, use a Directory
69 +To enable anonymous read access but authenticated write access,
70 +require authorization with a LocationMatch directive:
71 ++
72 +----------------------------------------------------------------
73 +<LocationMatch ".*/git-receive-pack$">
74 + AuthType Basic
75 + AuthName "Git Access"
76 + Require group committers
77 + ...
78 +</LocationMatch>
79 +----------------------------------------------------------------
80 ++
81 +To require authentication for both reads and writes, use a Directory
82 directive around the repository, or one of its parent directories:
83 +
84 ----------------------------------------------------------------
@@ -92,6 +121,14 @@ by the invoking web server, including:
121 * QUERY_STRING
122 * REQUEST_METHOD
123
124 +The backend process sets GIT_COMMITTER_NAME to '$REMOTE_USER' and
125 +GIT_COMMITTER_EMAIL to '$\{REMOTE_USER}@http.$\{REMOTE_ADDR\}',
126 +ensuring that any reflogs created by 'git-receive-pack' contain some
127 +identifying information of the remote user who performed the push.
128 +
129 +All CGI environment variables are available to each of the hooks
130 +invoked by the 'git-receive-pack'.
131 +
132 Author
133 ------
134 Written by Shawn O. Pearce <spearce@spearce.org>.
http-backend.c
+321 -3
@@ -4,11 +4,109 @@
4 #include "object.h"
5 #include "tag.h"
6 #include "exec_cmd.h"
7 +#include "run-command.h"
8 +#include "string-list.h"
9
10 static const char content_type[] = "Content-Type";
11 static const char content_length[] = "Content-Length";
12 static const char last_modified[] = "Last-Modified";
13
14 +static struct string_list *query_params;
15 +
16 +struct rpc_service {
17 + const char *name;
18 + const char *config_name;
19 + signed enabled : 2;
20 +};
21 +
22 +static struct rpc_service rpc_service[] = {
23 + { "upload-pack", "uploadpack", 1 },
24 + { "receive-pack", "receivepack", -1 },
25 +};
26 +
27 +static int decode_char(const char *q)
28 +{
29 + int i;
30 + unsigned char val = 0;
31 + for (i = 0; i < 2; i++) {
32 + unsigned char c = *q++;
33 + val <<= 4;
34 + if (c >= '0' && c <= '9')
35 + val += c - '0';
36 + else if (c >= 'a' && c <= 'f')
37 + val += c - 'a' + 10;
38 + else if (c >= 'A' && c <= 'F')
39 + val += c - 'A' + 10;
40 + else
41 + return -1;
42 + }
43 + return val;
44 +}
45 +
46 +static char *decode_parameter(const char **query, int is_name)
47 +{
48 + const char *q = *query;
49 + struct strbuf out;
50 +
51 + strbuf_init(&out, 16);
52 + do {
53 + unsigned char c = *q;
54 +
55 + if (!c)
56 + break;
57 + if (c == '&' || (is_name && c == '=')) {
58 + q++;
59 + break;
60 + }
61 +
62 + if (c == '%') {
63 + int val = decode_char(q + 1);
64 + if (0 <= val) {
65 + strbuf_addch(&out, val);
66 + q += 3;
67 + continue;
68 + }
69 + }
70 +
71 + if (c == '+')
72 + strbuf_addch(&out, ' ');
73 + else
74 + strbuf_addch(&out, c);
75 + q++;
76 + } while (1);
77 + *query = q;
78 + return strbuf_detach(&out, NULL);
79 +}
80 +
81 +static struct string_list *get_parameters(void)
82 +{
83 + if (!query_params) {
84 + const char *query = getenv("QUERY_STRING");
85 +
86 + query_params = xcalloc(1, sizeof(*query_params));
87 + while (query && *query) {
88 + char *name = decode_parameter(&query, 1);
89 + char *value = decode_parameter(&query, 0);
90 + struct string_list_item *i;
91 +
92 + i = string_list_lookup(name, query_params);
93 + if (!i)
94 + i = string_list_insert(name, query_params);
95 + else
96 + free(i->util);
97 + i->util = value;
98 + }
99 + }
100 + return query_params;
101 +}
102 +
103 +static const char *get_parameter(const char *name)
104 +{
105 + struct string_list_item *i;
106 + i = string_list_lookup(name, get_parameters());
107 + return i ? i->util : NULL;
108 +}
109 +
110 static void format_write(int fd, const char *fmt, ...)
111 {
112 static char buffer[1024];
@@ -81,6 +179,21 @@ static NORETURN void not_found(const char *err, ...)
179 exit(0);
180 }
181
182 +static NORETURN void forbidden(const char *err, ...)
183 +{
184 + va_list params;
185 +
186 + http_status(403, "Forbidden");
187 + hdr_nocache();
188 + end_headers();
189 +
190 + va_start(params, err);
191 + if (err && *err)
192 + vfprintf(stderr, err, params);
193 + va_end(params);
194 + exit(0);
195 +}
196 +
197 static void send_strbuf(const char *type, struct strbuf *buf)
198 {
199 hdr_int(content_length, buf->len);
@@ -147,6 +260,145 @@ static void get_idx_file(char *name)
260 send_file("application/x-git-packed-objects-toc", name);
261 }
262
263 +static int http_config(const char *var, const char *value, void *cb)
264 +{
265 + struct rpc_service *svc = cb;
266 +
267 + if (!prefixcmp(var, "http.") &&
268 + !strcmp(var + 5, svc->config_name)) {
269 + svc->enabled = git_config_bool(var, value);
270 + return 0;
271 + }
272 +
273 + /* we are not interested in parsing any other configuration here */
274 + return 0;
275 +}
276 +
277 +static struct rpc_service *select_service(const char *name)
278 +{
279 + struct rpc_service *svc = NULL;
280 + int i;
281 +
282 + if (prefixcmp(name, "git-"))
283 + forbidden("Unsupported service: '%s'", name);
284 +
285 + for (i = 0; i < ARRAY_SIZE(rpc_service); i++) {
286 + struct rpc_service *s = &rpc_service[i];
287 + if (!strcmp(s->name, name + 4)) {
288 + svc = s;
289 + break;
290 + }
291 + }
292 +
293 + if (!svc)
294 + forbidden("Unsupported service: '%s'", name);
295 +
296 + git_config(http_config, svc);
297 + if (svc->enabled < 0) {
298 + const char *user = getenv("REMOTE_USER");
299 + svc->enabled = (user && *user) ? 1 : 0;
300 + }
301 + if (!svc->enabled)
302 + forbidden("Service not enabled: '%s'", svc->name);
303 + return svc;
304 +}
305 +
306 +static void inflate_request(const char *prog_name, int out)
307 +{
308 + z_stream stream;
309 + unsigned char in_buf[8192];
310 + unsigned char out_buf[8192];
311 + unsigned long cnt = 0;
312 + int ret;
313 +
314 + memset(&stream, 0, sizeof(stream));
315 + ret = inflateInit2(&stream, (15 + 16));
316 + if (ret != Z_OK)
317 + die("cannot start zlib inflater, zlib err %d", ret);
318 +
319 + while (1) {
320 + ssize_t n = xread(0, in_buf, sizeof(in_buf));
321 + if (n <= 0)
322 + die("request ended in the middle of the gzip stream");
323 +
324 + stream.next_in = in_buf;
325 + stream.avail_in = n;
326 +
327 + while (0 < stream.avail_in) {
328 + int ret;
329 +
330 + stream.next_out = out_buf;
331 + stream.avail_out = sizeof(out_buf);
332 +
333 + ret = inflate(&stream, Z_NO_FLUSH);
334 + if (ret != Z_OK && ret != Z_STREAM_END)
335 + die("zlib error inflating request, result %d", ret);
336 +
337 + n = stream.total_out - cnt;
338 + if (write_in_full(out, out_buf, n) != n)
339 + die("%s aborted reading request", prog_name);
340 + cnt += n;
341 +
342 + if (ret == Z_STREAM_END)
343 + goto done;
344 + }
345 + }
346 +
347 +done:
348 + inflateEnd(&stream);
349 + close(out);
350 +}
351 +
352 +static void run_service(const char **argv)
353 +{
354 + const char *encoding = getenv("HTTP_CONTENT_ENCODING");
355 + const char *user = getenv("REMOTE_USER");
356 + const char *host = getenv("REMOTE_ADDR");
357 + char *env[3];
358 + struct strbuf buf = STRBUF_INIT;
359 + int gzipped_request = 0;
360 + struct child_process cld;
361 +
362 + if (encoding && !strcmp(encoding, "gzip"))
363 + gzipped_request = 1;
364 + else if (encoding && !strcmp(encoding, "x-gzip"))
365 + gzipped_request = 1;
366 +
367 + if (!user || !*user)
368 + user = "anonymous";
369 + if (!host || !*host)
370 + host = "(none)";
371 +
372 + memset(&env, 0, sizeof(env));
373 + strbuf_addf(&buf, "GIT_COMMITTER_NAME=%s", user);
374 + env[0] = strbuf_detach(&buf, NULL);
375 +
376 + strbuf_addf(&buf, "GIT_COMMITTER_EMAIL=%s@http.%s", user, host);
377 + env[1] = strbuf_detach(&buf, NULL);
378 + env[2] = NULL;
379 +
380 + memset(&cld, 0, sizeof(cld));
381 + cld.argv = argv;
382 + cld.env = (const char *const *)env;
383 + if (gzipped_request)
384 + cld.in = -1;
385 + cld.git_cmd = 1;
386 + if (start_command(&cld))
387 + exit(1);
388 +
389 + close(1);
390 + if (gzipped_request)
391 + inflate_request(argv[0], cld.in);
392 + else
393 + close(0);
394 +
395 + if (finish_command(&cld))
396 + exit(1);
397 + free(env[0]);
398 + free(env[1]);
399 + strbuf_release(&buf);
400 +}
401 +
402 static int show_text_ref(const char *name, const unsigned char *sha1,
403 int flag, void *cb_data)
404 {
@@ -167,11 +419,32 @@ static int show_text_ref(const char *name, const unsigned char *sha1,
419
420 static void get_info_refs(char *arg)
421 {
422 + const char *service_name = get_parameter("service");
423 struct strbuf buf = STRBUF_INIT;
424
172 - for_each_ref(show_text_ref, &buf);
425 hdr_nocache();
174 - send_strbuf("text/plain", &buf);
426 +
427 + if (service_name) {
428 + const char *argv[] = {NULL /* service name */,
429 + "--stateless-rpc", "--advertise-refs",
430 + ".", NULL};
431 + struct rpc_service *svc = select_service(service_name);
432 +
433 + strbuf_addf(&buf, "application/x-git-%s-advertisement",
434 + svc->name);
435 + hdr_str(content_type, buf.buf);
436 + end_headers();
437 +
438 + packet_write(1, "# service=git-%s\n", svc->name);
439 + packet_flush(1);
440 +
441 + argv[0] = svc->name;
442 + run_service(argv);
443 +
444 + } else {
445 + for_each_ref(show_text_ref, &buf);
446 + send_strbuf("text/plain", &buf);
447 + }
448 strbuf_release(&buf);
449 }
450
@@ -200,6 +473,48 @@ static void get_info_packs(char *arg)
473 strbuf_release(&buf);
474 }
475
476 +static void check_content_type(const char *accepted_type)
477 +{
478 + const char *actual_type = getenv("CONTENT_TYPE");
479 +
480 + if (!actual_type)
481 + actual_type = "";
482 +
483 + if (strcmp(actual_type, accepted_type)) {
484 + http_status(415, "Unsupported Media Type");
485 + hdr_nocache();
486 + end_headers();
487 + format_write(1,
488 + "Expected POST with Content-Type '%s',"
489 + " but received '%s' instead.\n",
490 + accepted_type, actual_type);
491 + exit(0);
492 + }
493 +}
494 +
495 +static void service_rpc(char *service_name)
496 +{
497 + const char *argv[] = {NULL, "--stateless-rpc", ".", NULL};
498 + struct rpc_service *svc = select_service(service_name);
499 + struct strbuf buf = STRBUF_INIT;
500 +
501 + strbuf_reset(&buf);
502 + strbuf_addf(&buf, "application/x-git-%s-request", svc->name);
503 + check_content_type(buf.buf);
504 +
505 + hdr_nocache();
506 +
507 + strbuf_reset(&buf);
508 + strbuf_addf(&buf, "application/x-git-%s-result", svc->name);
509 + hdr_str(content_type, buf.buf);
510 +
511 + end_headers();
512 +
513 + argv[0] = svc->name;
514 + run_service(argv);
515 + strbuf_release(&buf);
516 +}
517 +
518 static NORETURN void die_webcgi(const char *err, va_list params)
519 {
520 char buffer[1000];
@@ -225,7 +540,10 @@ static struct service_cmd {
540 {"GET", "/objects/info/packs$", get_info_packs},
541 {"GET", "/objects/[0-9a-f]{2}/[0-9a-f]{38}$", get_loose_object},
542 {"GET", "/objects/pack/pack-[0-9a-f]{40}\\.pack$", get_pack_file},
228 - {"GET", "/objects/pack/pack-[0-9a-f]{40}\\.idx$", get_idx_file}
543 + {"GET", "/objects/pack/pack-[0-9a-f]{40}\\.idx$", get_idx_file},
544 +
545 + {"POST", "/git-upload-pack$", service_rpc},
546 + {"POST", "/git-receive-pack$", service_rpc}
547 };
548
549 int main(int argc, char **argv)