@matheusbsbs / matheus-bagliano / commits / 2f4038ab33
Fork 0

Git-aware CGI to provide dumb HTTP transport

The git-http-backend CGI can be configured into any Apache server using ScriptAlias, such as with the following configuration: LoadModule cgi_module /usr/libexec/apache2/mod_cgi.so LoadModule alias_module /usr/libexec/apache2/mod_alias.so ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/ Repositories are accessed via the translated PATH_INFO. The CGI is backwards compatible with the dumb client, allowing all older HTTP clients to continue to download repositories which are managed by the CGI. 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 2f4038ab337e55017d4ff21ddbae9427544ca02c
4 files changed +396
.gitignore
+1
@@ -55,6 +55,7 @@ git-get-tar-commit-id
55 git-grep
56 git-hash-object
57 git-help
58 +git-http-backend
59 git-http-fetch
60 git-http-push
61 git-imap-send
Documentation/git-http-backend.txt new
+105
@@ -0,0 +1,105 @@
1 +git-http-backend(1)
2 +===================
3 +
4 +NAME
5 +----
6 +git-http-backend - Server side implementation of Git over HTTP
7 +
8 +SYNOPSIS
9 +--------
10 +[verse]
11 +'git-http-backend'
12 +
13 +DESCRIPTION
14 +-----------
15 +A simple CGI program to serve the contents of a Git repository to Git
16 +clients accessing the repository over http:// and https:// protocols.
17 +
18 +By default, only the `upload-pack` service is enabled, which serves
19 +'git-fetch-pack' and 'git-ls-remote' clients, which are invoked from
20 +'git-fetch', 'git-pull', and 'git-clone'.
21 +
22 +This is ideally suited for read-only updates, i.e., pulling from
23 +git repositories.
24 +
25 +URL TRANSLATION
26 +---------------
27 +'git-http-backend' relies on the invoking web server to perform
28 +URL to path translation, and store the repository path into the
29 +PATH_TRANSLATED environment variable. Most web servers will do
30 +this translation automatically, resolving the suffix after the
31 +CGI name relative to the server's document root.
32 +
33 +EXAMPLES
34 +--------
35 +
36 +Apache 2.x::
37 + To serve all Git repositories contained within the '/git/'
38 + subdirectory of the DocumentRoot, ensure mod_cgi and
39 + mod_alias are enabled, and create a ScriptAlias to the CGI:
40 ++
41 +----------------------------------------------------------------
42 +ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/git/
43 +
44 +<Directory /usr/libexec/git-core>
45 + Options None
46 +</Directory>
47 +<Files /usr/libexec/git-core/git-http-backend>
48 + Options ExecCGI
49 +</Files>
50 +----------------------------------------------------------------
51 ++
52 +To require authentication for reads, use a Directory
53 +directive around the repository, or one of its parent directories:
54 ++
55 +----------------------------------------------------------------
56 +<Directory /var/www/git/private>
57 + AuthType Basic
58 + AuthName "Private Git Access"
59 + Require group committers
60 + ...
61 +</Directory>
62 +----------------------------------------------------------------
63 +
64 +Accelerated static Apache 2.x::
65 + Similar to the above, but Apache can be used to return static
66 + files that are stored on disk. On many systems this may
67 + be more efficient as Apache can ask the kernel to copy the
68 + file contents from the file system directly to the network:
69 ++
70 +----------------------------------------------------------------
71 +DocumentRoot /var/www
72 +
73 +ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/git/
74 +Alias /git_static/ /var/www/git/
75 +
76 +RewriteEngine on
77 +RewriteRule ^/git/(.*/objects/[0-9a-f]{2}/[0-9a-f]{38})$ /git_static/$1 [PT]
78 +RewriteRule ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.pack)$ /git_static/$1 [PT]
79 +RewriteRule ^/git/(.*/objects/pack/pack-[0-9a-f]{40}.idx)$ /git_static/$1 [PT]
80 +----------------------------------------------------------------
81 +
82 +
83 +ENVIRONMENT
84 +-----------
85 +'git-http-backend' relies upon the CGI environment variables set
86 +by the invoking web server, including:
87 +
88 +* PATH_TRANSLATED
89 +* REMOTE_USER
90 +* REMOTE_ADDR
91 +* CONTENT_TYPE
92 +* QUERY_STRING
93 +* REQUEST_METHOD
94 +
95 +Author
96 +------
97 +Written by Shawn O. Pearce <spearce@spearce.org>.
98 +
99 +Documentation
100 +--------------
101 +Documentation by Shawn O. Pearce <spearce@spearce.org>.
102 +
103 +GIT
104 +---
105 +Part of the linkgit:git[1] suite
Makefile
+1
@@ -365,6 +365,7 @@ PROGRAMS += git-show-index$X
365 PROGRAMS += git-unpack-file$X
366 PROGRAMS += git-upload-pack$X
367 PROGRAMS += git-var$X
368 +PROGRAMS += git-http-backend$X
369
370 # List built-in command $C whose implementation cmd_$C() is not in
371 # builtin-$C.o but is linked in as part of some other command.
http-backend.c new
+289
@@ -0,0 +1,289 @@
1 +#include "cache.h"
2 +#include "refs.h"
3 +#include "pkt-line.h"
4 +#include "object.h"
5 +#include "tag.h"
6 +#include "exec_cmd.h"
7 +
8 +static const char content_type[] = "Content-Type";
9 +static const char content_length[] = "Content-Length";
10 +static const char last_modified[] = "Last-Modified";
11 +
12 +static void format_write(int fd, const char *fmt, ...)
13 +{
14 + static char buffer[1024];
15 +
16 + va_list args;
17 + unsigned n;
18 +
19 + va_start(args, fmt);
20 + n = vsnprintf(buffer, sizeof(buffer), fmt, args);
21 + va_end(args);
22 + if (n >= sizeof(buffer))
23 + die("protocol error: impossibly long line");
24 +
25 + safe_write(fd, buffer, n);
26 +}
27 +
28 +static void http_status(unsigned code, const char *msg)
29 +{
30 + format_write(1, "Status: %u %s\r\n", code, msg);
31 +}
32 +
33 +static void hdr_str(const char *name, const char *value)
34 +{
35 + format_write(1, "%s: %s\r\n", name, value);
36 +}
37 +
38 +static void hdr_int(const char *name, size_t value)
39 +{
40 + format_write(1, "%s: %" PRIuMAX "\r\n", name, value);
41 +}
42 +
43 +static void hdr_date(const char *name, unsigned long when)
44 +{
45 + const char *value = show_date(when, 0, DATE_RFC2822);
46 + hdr_str(name, value);
47 +}
48 +
49 +static void hdr_nocache(void)
50 +{
51 + hdr_str("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
52 + hdr_str("Pragma", "no-cache");
53 + hdr_str("Cache-Control", "no-cache, max-age=0, must-revalidate");
54 +}
55 +
56 +static void hdr_cache_forever(void)
57 +{
58 + unsigned long now = time(NULL);
59 + hdr_date("Date", now);
60 + hdr_date("Expires", now + 31536000);
61 + hdr_str("Cache-Control", "public, max-age=31536000");
62 +}
63 +
64 +static void end_headers(void)
65 +{
66 + safe_write(1, "\r\n", 2);
67 +}
68 +
69 +static NORETURN void not_found(const char *err, ...)
70 +{
71 + va_list params;
72 +
73 + http_status(404, "Not Found");
74 + hdr_nocache();
75 + end_headers();
76 +
77 + va_start(params, err);
78 + if (err && *err)
79 + vfprintf(stderr, err, params);
80 + va_end(params);
81 + exit(0);
82 +}
83 +
84 +static void send_strbuf(const char *type, struct strbuf *buf)
85 +{
86 + hdr_int(content_length, buf->len);
87 + hdr_str(content_type, type);
88 + end_headers();
89 + safe_write(1, buf->buf, buf->len);
90 +}
91 +
92 +static void send_file(const char *the_type, const char *name)
93 +{
94 + const char *p = git_path("%s", name);
95 + size_t buf_alloc = 8192;
96 + char *buf = xmalloc(buf_alloc);
97 + int fd;
98 + struct stat sb;
99 + size_t size;
100 +
101 + fd = open(p, O_RDONLY);
102 + if (fd < 0)
103 + not_found("Cannot open '%s': %s", p, strerror(errno));
104 + if (fstat(fd, &sb) < 0)
105 + die_errno("Cannot stat '%s'", p);
106 +
107 + size = xsize_t(sb.st_size);
108 +
109 + hdr_int(content_length, size);
110 + hdr_str(content_type, the_type);
111 + hdr_date(last_modified, sb.st_mtime);
112 + end_headers();
113 +
114 + while (size) {
115 + ssize_t n = xread(fd, buf, buf_alloc);
116 + if (n < 0)
117 + die_errno("Cannot read '%s'", p);
118 + if (!n)
119 + break;
120 + safe_write(1, buf, n);
121 + }
122 + close(fd);
123 + free(buf);
124 +}
125 +
126 +static void get_text_file(char *name)
127 +{
128 + hdr_nocache();
129 + send_file("text/plain", name);
130 +}
131 +
132 +static void get_loose_object(char *name)
133 +{
134 + hdr_cache_forever();
135 + send_file("application/x-git-loose-object", name);
136 +}
137 +
138 +static void get_pack_file(char *name)
139 +{
140 + hdr_cache_forever();
141 + send_file("application/x-git-packed-objects", name);
142 +}
143 +
144 +static void get_idx_file(char *name)
145 +{
146 + hdr_cache_forever();
147 + send_file("application/x-git-packed-objects-toc", name);
148 +}
149 +
150 +static int show_text_ref(const char *name, const unsigned char *sha1,
151 + int flag, void *cb_data)
152 +{
153 + struct strbuf *buf = cb_data;
154 + struct object *o = parse_object(sha1);
155 + if (!o)
156 + return 0;
157 +
158 + strbuf_addf(buf, "%s\t%s\n", sha1_to_hex(sha1), name);
159 + if (o->type == OBJ_TAG) {
160 + o = deref_tag(o, name, 0);
161 + if (!o)
162 + return 0;
163 + strbuf_addf(buf, "%s\t%s^{}\n", sha1_to_hex(o->sha1), name);
164 + }
165 + return 0;
166 +}
167 +
168 +static void get_info_refs(char *arg)
169 +{
170 + struct strbuf buf = STRBUF_INIT;
171 +
172 + for_each_ref(show_text_ref, &buf);
173 + hdr_nocache();
174 + send_strbuf("text/plain", &buf);
175 + strbuf_release(&buf);
176 +}
177 +
178 +static void get_info_packs(char *arg)
179 +{
180 + size_t objdirlen = strlen(get_object_directory());
181 + struct strbuf buf = STRBUF_INIT;
182 + struct packed_git *p;
183 + size_t cnt = 0;
184 +
185 + prepare_packed_git();
186 + for (p = packed_git; p; p = p->next) {
187 + if (p->pack_local)
188 + cnt++;
189 + }
190 +
191 + strbuf_grow(&buf, cnt * 53 + 2);
192 + for (p = packed_git; p; p = p->next) {
193 + if (p->pack_local)
194 + strbuf_addf(&buf, "P %s\n", p->pack_name + objdirlen + 6);
195 + }
196 + strbuf_addch(&buf, '\n');
197 +
198 + hdr_nocache();
199 + send_strbuf("text/plain; charset=utf-8", &buf);
200 + strbuf_release(&buf);
201 +}
202 +
203 +static NORETURN void die_webcgi(const char *err, va_list params)
204 +{
205 + char buffer[1000];
206 +
207 + http_status(500, "Internal Server Error");
208 + hdr_nocache();
209 + end_headers();
210 +
211 + vsnprintf(buffer, sizeof(buffer), err, params);
212 + fprintf(stderr, "fatal: %s\n", buffer);
213 + exit(0);
214 +}
215 +
216 +static struct service_cmd {
217 + const char *method;
218 + const char *pattern;
219 + void (*imp)(char *);
220 +} services[] = {
221 + {"GET", "/HEAD$", get_text_file},
222 + {"GET", "/info/refs$", get_info_refs},
223 + {"GET", "/objects/info/alternates$", get_text_file},
224 + {"GET", "/objects/info/http-alternates$", get_text_file},
225 + {"GET", "/objects/info/packs$", get_info_packs},
226 + {"GET", "/objects/[0-9a-f]{2}/[0-9a-f]{38}$", get_loose_object},
227 + {"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}
229 +};
230 +
231 +int main(int argc, char **argv)
232 +{
233 + char *method = getenv("REQUEST_METHOD");
234 + char *dir = getenv("PATH_TRANSLATED");
235 + struct service_cmd *cmd = NULL;
236 + char *cmd_arg = NULL;
237 + int i;
238 +
239 + git_extract_argv0_path(argv[0]);
240 + set_die_routine(die_webcgi);
241 +
242 + if (!method)
243 + die("No REQUEST_METHOD from server");
244 + if (!strcmp(method, "HEAD"))
245 + method = "GET";
246 + if (!dir)
247 + die("No PATH_TRANSLATED from server");
248 +
249 + for (i = 0; i < ARRAY_SIZE(services); i++) {
250 + struct service_cmd *c = &services[i];
251 + regex_t re;
252 + regmatch_t out[1];
253 +
254 + if (regcomp(&re, c->pattern, REG_EXTENDED))
255 + die("Bogus regex in service table: %s", c->pattern);
256 + if (!regexec(&re, dir, 1, out, 0)) {
257 + size_t n = out[0].rm_eo - out[0].rm_so;
258 +
259 + if (strcmp(method, c->method)) {
260 + const char *proto = getenv("SERVER_PROTOCOL");
261 + if (proto && !strcmp(proto, "HTTP/1.1"))
262 + http_status(405, "Method Not Allowed");
263 + else
264 + http_status(400, "Bad Request");
265 + hdr_nocache();
266 + end_headers();
267 + return 0;
268 + }
269 +
270 + cmd = c;
271 + cmd_arg = xmalloc(n);
272 + strncpy(cmd_arg, dir + out[0].rm_so + 1, n);
273 + cmd_arg[n] = '\0';
274 + dir[out[0].rm_so] = 0;
275 + break;
276 + }
277 + regfree(&re);
278 + }
279 +
280 + if (!cmd)
281 + not_found("Request not supported: '%s'", dir);
282 +
283 + setup_path();
284 + if (!enter_repo(dir, 0))
285 + not_found("Not a git repository: '%s'", dir);
286 +
287 + cmd->imp(cmd_arg);
288 + return 0;
289 +}