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];
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);
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
{
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
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];
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)