]>
Commit | Line | Data |
---|---|---|
2f4038ab SP |
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 | } |