1#include "cache.h"
2#include "commit.h"
3
4#include "fetch.h"
5
6#include <curl/curl.h>
7#include <curl/easy.h>
8
9#if LIBCURL_VERSION_NUM < 0x070704
10#define curl_global_cleanup() do { /* nothing */ } while(0)
11#endif
12#if LIBCURL_VERSION_NUM < 0x070800
13#define curl_global_init(a) do { /* nothing */ } while(0)
14#endif
15
16static CURL *curl;
17static struct curl_slist *no_pragma_header;
18static char curl_errorstr[CURL_ERROR_SIZE];
19
20static char *initial_base;
21
22struct alt_base
23{
24 char *base;
25 int got_indices;
26 struct packed_git *packs;
27 struct alt_base *next;
28};
29
30struct alt_base *alt = NULL;
31
32static SHA_CTX c;
33static z_stream stream;
34
35static int local;
36static int zret;
37
38static int curl_ssl_verify;
39
40struct buffer
41{
42 size_t posn;
43 size_t size;
44 void *buffer;
45};
46
47static size_t fwrite_buffer(void *ptr, size_t eltsize, size_t nmemb,
48 struct buffer *buffer)
49{
50 size_t size = eltsize * nmemb;
51 if (size > buffer->size - buffer->posn)
52 size = buffer->size - buffer->posn;
53 memcpy(buffer->buffer + buffer->posn, ptr, size);
54 buffer->posn += size;
55 return size;
56}
57
58static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
59 void *data)
60{
61 unsigned char expn[4096];
62 size_t size = eltsize * nmemb;
63 int posn = 0;
64 do {
65 ssize_t retval = write(local, ptr + posn, size - posn);
66 if (retval < 0)
67 return posn;
68 posn += retval;
69 } while (posn < size);
70
71 stream.avail_in = size;
72 stream.next_in = ptr;
73 do {
74 stream.next_out = expn;
75 stream.avail_out = sizeof(expn);
76 zret = inflate(&stream, Z_SYNC_FLUSH);
77 SHA1_Update(&c, expn, sizeof(expn) - stream.avail_out);
78 } while (stream.avail_in && zret == Z_OK);
79 return size;
80}
81
82void prefetch(unsigned char *sha1)
83{
84}
85
86static int got_alternates = 0;
87
88static int fetch_index(struct alt_base *repo, unsigned char *sha1)
89{
90 char *filename;
91 char *url;
92
93 FILE *indexfile;
94
95 if (has_pack_index(sha1))
96 return 0;
97
98 if (get_verbosely)
99 fprintf(stderr, "Getting index for pack %s\n",
100 sha1_to_hex(sha1));
101
102 url = xmalloc(strlen(repo->base) + 64);
103 sprintf(url, "%s/objects/pack/pack-%s.idx",
104 repo->base, sha1_to_hex(sha1));
105
106 filename = sha1_pack_index_name(sha1);
107 indexfile = fopen(filename, "w");
108 if (!indexfile)
109 return error("Unable to open local file %s for pack index",
110 filename);
111
112 curl_easy_setopt(curl, CURLOPT_FILE, indexfile);
113 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
114 curl_easy_setopt(curl, CURLOPT_URL, url);
115 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_pragma_header);
116 curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errorstr);
117
118 if (curl_easy_perform(curl)) {
119 fclose(indexfile);
120 return error("Unable to get pack index %s\n%s", url,
121 curl_errorstr);
122 }
123
124 fclose(indexfile);
125 return 0;
126}
127
128static int setup_index(struct alt_base *repo, unsigned char *sha1)
129{
130 struct packed_git *new_pack;
131 if (has_pack_file(sha1))
132 return 0; // don't list this as something we can get
133
134 if (fetch_index(repo, sha1))
135 return -1;
136
137 new_pack = parse_pack_index(sha1);
138 new_pack->next = repo->packs;
139 repo->packs = new_pack;
140 return 0;
141}
142
143static int fetch_alternates(char *base)
144{
145 int ret = 0;
146 struct buffer buffer;
147 char *url;
148 char *data;
149 int i = 0;
150 int http_specific = 1;
151 if (got_alternates)
152 return 0;
153 data = xmalloc(4096);
154 buffer.size = 4095;
155 buffer.posn = 0;
156 buffer.buffer = data;
157
158 if (get_verbosely)
159 fprintf(stderr, "Getting alternates list\n");
160
161 url = xmalloc(strlen(base) + 31);
162 sprintf(url, "%s/objects/info/http-alternates", base);
163
164 curl_easy_setopt(curl, CURLOPT_FILE, &buffer);
165 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
166 curl_easy_setopt(curl, CURLOPT_URL, url);
167
168 if (curl_easy_perform(curl) || !buffer.posn) {
169 http_specific = 0;
170
171 sprintf(url, "%s/objects/info/alternates", base);
172
173 curl_easy_setopt(curl, CURLOPT_FILE, &buffer);
174 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
175 curl_easy_setopt(curl, CURLOPT_URL, url);
176
177 if (curl_easy_perform(curl)) {
178 return 0;
179 }
180 }
181
182 data[buffer.posn] = '\0';
183
184 while (i < buffer.posn) {
185 int posn = i;
186 while (posn < buffer.posn && data[posn] != '\n')
187 posn++;
188 if (data[posn] == '\n') {
189 int okay = 0;
190 int serverlen = 0;
191 struct alt_base *newalt;
192 char *target = NULL;
193 if (data[i] == '/') {
194 serverlen = strchr(base + 8, '/') - base;
195 okay = 1;
196 } else if (!memcmp(data + i, "../", 3)) {
197 i += 3;
198 serverlen = strlen(base);
199 while (i + 2 < posn &&
200 !memcmp(data + i, "../", 3)) {
201 do {
202 serverlen--;
203 } while (serverlen &&
204 base[serverlen - 1] != '/');
205 i += 3;
206 }
207 // If the server got removed, give up.
208 okay = strchr(base, ':') - base + 3 <
209 serverlen;
210 } else if (http_specific) {
211 char *colon = strchr(data + i, ':');
212 char *slash = strchr(data + i, '/');
213 if (colon && slash && colon < data + posn &&
214 slash < data + posn && colon < slash) {
215 okay = 1;
216 }
217 }
218 // skip 'objects' at end
219 if (okay) {
220 target = xmalloc(serverlen + posn - i - 6);
221 strncpy(target, base, serverlen);
222 strncpy(target + serverlen, data + i,
223 posn - i - 7);
224 target[serverlen + posn - i - 7] = '\0';
225 if (get_verbosely)
226 fprintf(stderr,
227 "Also look at %s\n", target);
228 newalt = xmalloc(sizeof(*newalt));
229 newalt->next = alt;
230 newalt->base = target;
231 newalt->got_indices = 0;
232 newalt->packs = NULL;
233 alt = newalt;
234 ret++;
235 }
236 }
237 i = posn + 1;
238 }
239 got_alternates = 1;
240
241 return ret;
242}
243
244static int fetch_indices(struct alt_base *repo)
245{
246 unsigned char sha1[20];
247 char *url;
248 struct buffer buffer;
249 char *data;
250 int i = 0;
251
252 if (repo->got_indices)
253 return 0;
254
255 data = xmalloc(4096);
256 buffer.size = 4096;
257 buffer.posn = 0;
258 buffer.buffer = data;
259
260 if (get_verbosely)
261 fprintf(stderr, "Getting pack list\n");
262
263 url = xmalloc(strlen(repo->base) + 21);
264 sprintf(url, "%s/objects/info/packs", repo->base);
265
266 curl_easy_setopt(curl, CURLOPT_FILE, &buffer);
267 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
268 curl_easy_setopt(curl, CURLOPT_URL, url);
269 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL);
270 curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errorstr);
271
272 if (curl_easy_perform(curl))
273 return error("%s", curl_errorstr);
274
275 while (i < buffer.posn) {
276 switch (data[i]) {
277 case 'P':
278 i++;
279 if (i + 52 < buffer.posn &&
280 !strncmp(data + i, " pack-", 6) &&
281 !strncmp(data + i + 46, ".pack\n", 6)) {
282 get_sha1_hex(data + i + 6, sha1);
283 setup_index(repo, sha1);
284 i += 51;
285 break;
286 }
287 default:
288 while (data[i] != '\n')
289 i++;
290 }
291 i++;
292 }
293
294 repo->got_indices = 1;
295 return 0;
296}
297
298static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
299{
300 char *url;
301 struct packed_git *target;
302 struct packed_git **lst;
303 FILE *packfile;
304 char *filename;
305
306 if (fetch_indices(repo))
307 return -1;
308 target = find_sha1_pack(sha1, repo->packs);
309 if (!target)
310 return -1;
311
312 if (get_verbosely) {
313 fprintf(stderr, "Getting pack %s\n",
314 sha1_to_hex(target->sha1));
315 fprintf(stderr, " which contains %s\n",
316 sha1_to_hex(sha1));
317 }
318
319 url = xmalloc(strlen(repo->base) + 65);
320 sprintf(url, "%s/objects/pack/pack-%s.pack",
321 repo->base, sha1_to_hex(target->sha1));
322
323 filename = sha1_pack_name(target->sha1);
324 packfile = fopen(filename, "w");
325 if (!packfile)
326 return error("Unable to open local file %s for pack",
327 filename);
328
329 curl_easy_setopt(curl, CURLOPT_FILE, packfile);
330 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite);
331 curl_easy_setopt(curl, CURLOPT_URL, url);
332 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_pragma_header);
333 curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errorstr);
334
335 if (curl_easy_perform(curl)) {
336 fclose(packfile);
337 return error("Unable to get pack file %s\n%s", url,
338 curl_errorstr);
339 }
340
341 fclose(packfile);
342
343 lst = &repo->packs;
344 while (*lst != target)
345 lst = &((*lst)->next);
346 *lst = (*lst)->next;
347
348 install_packed_git(target);
349
350 return 0;
351}
352
353int fetch_object(struct alt_base *repo, unsigned char *sha1)
354{
355 char *hex = sha1_to_hex(sha1);
356 char *filename = sha1_file_name(sha1);
357 unsigned char real_sha1[20];
358 char tmpfile[PATH_MAX];
359 int ret;
360 char *url;
361 char *posn;
362
363 snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX",
364 get_object_directory());
365
366 local = mkstemp(tmpfile);
367 if (local < 0)
368 return error("Couldn't create temporary file %s for %s: %s\n",
369 tmpfile, filename, strerror(errno));
370
371 memset(&stream, 0, sizeof(stream));
372
373 inflateInit(&stream);
374
375 SHA1_Init(&c);
376
377 curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
378 curl_easy_setopt(curl, CURLOPT_FILE, NULL);
379 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
380 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, no_pragma_header);
381 curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errorstr);
382
383 url = xmalloc(strlen(repo->base) + 50);
384 strcpy(url, repo->base);
385 posn = url + strlen(repo->base);
386 strcpy(posn, "objects/");
387 posn += 8;
388 memcpy(posn, hex, 2);
389 posn += 2;
390 *(posn++) = '/';
391 strcpy(posn, hex + 2);
392
393 curl_easy_setopt(curl, CURLOPT_URL, url);
394
395 if (curl_easy_perform(curl)) {
396 unlink(filename);
397 return error("%s", curl_errorstr);
398 }
399
400 fchmod(local, 0444);
401 close(local);
402 inflateEnd(&stream);
403 SHA1_Final(real_sha1, &c);
404 if (zret != Z_STREAM_END) {
405 unlink(tmpfile);
406 return error("File %s (%s) corrupt\n", hex, url);
407 }
408 if (memcmp(sha1, real_sha1, 20)) {
409 unlink(tmpfile);
410 return error("File %s has bad hash\n", hex);
411 }
412 ret = link(tmpfile, filename);
413 if (ret < 0) {
414 /* Same Coda hack as in write_sha1_file(sha1_file.c) */
415 ret = errno;
416 if (ret == EXDEV && !rename(tmpfile, filename))
417 goto out;
418 }
419 unlink(tmpfile);
420 if (ret) {
421 if (ret != EEXIST)
422 return error("unable to write sha1 filename %s: %s",
423 filename, strerror(ret));
424 }
425 out:
426 pull_say("got %s\n", hex);
427 return 0;
428}
429
430int fetch(unsigned char *sha1)
431{
432 struct alt_base *altbase = alt;
433 while (altbase) {
434 if (!fetch_object(altbase, sha1))
435 return 0;
436 if (!fetch_pack(altbase, sha1))
437 return 0;
438 if (fetch_alternates(altbase->base) > 0) {
439 altbase = alt;
440 continue;
441 }
442 altbase = altbase->next;
443 }
444 return error("Unable to find %s under %s\n", sha1_to_hex(sha1),
445 initial_base);
446}
447
448int fetch_ref(char *ref, unsigned char *sha1)
449{
450 char *url, *posn;
451 char hex[42];
452 struct buffer buffer;
453 char *base = initial_base;
454 buffer.size = 41;
455 buffer.posn = 0;
456 buffer.buffer = hex;
457 hex[41] = '\0';
458
459 curl_easy_setopt(curl, CURLOPT_FILE, &buffer);
460 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
461 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL);
462 curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, curl_errorstr);
463
464 url = xmalloc(strlen(base) + 6 + strlen(ref));
465 strcpy(url, base);
466 posn = url + strlen(base);
467 strcpy(posn, "refs/");
468 posn += 5;
469 strcpy(posn, ref);
470
471 curl_easy_setopt(curl, CURLOPT_URL, url);
472
473 if (curl_easy_perform(curl))
474 return error("Couldn't get %s for %s\n%s",
475 url, ref, curl_errorstr);
476
477 hex[40] = '\0';
478 get_sha1_hex(hex, sha1);
479 return 0;
480}
481
482int main(int argc, char **argv)
483{
484 char *commit_id;
485 char *url;
486 int arg = 1;
487
488 while (arg < argc && argv[arg][0] == '-') {
489 if (argv[arg][1] == 't') {
490 get_tree = 1;
491 } else if (argv[arg][1] == 'c') {
492 get_history = 1;
493 } else if (argv[arg][1] == 'a') {
494 get_all = 1;
495 get_tree = 1;
496 get_history = 1;
497 } else if (argv[arg][1] == 'v') {
498 get_verbosely = 1;
499 } else if (argv[arg][1] == 'w') {
500 write_ref = argv[arg + 1];
501 arg++;
502 } else if (!strcmp(argv[arg], "--recover")) {
503 get_recover = 1;
504 }
505 arg++;
506 }
507 if (argc < arg + 2) {
508 usage("git-http-fetch [-c] [-t] [-a] [-d] [-v] [--recover] [-w ref] commit-id url");
509 return 1;
510 }
511 commit_id = argv[arg];
512 url = argv[arg + 1];
513
514 curl_global_init(CURL_GLOBAL_ALL);
515
516 curl = curl_easy_init();
517 no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
518
519 curl_ssl_verify = getenv("GIT_SSL_NO_VERIFY") ? 0 : 1;
520 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, curl_ssl_verify);
521#if LIBCURL_VERSION_NUM >= 0x070907
522 curl_easy_setopt(curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
523#endif
524
525 alt = xmalloc(sizeof(*alt));
526 alt->base = url;
527 alt->got_indices = 0;
528 alt->packs = NULL;
529 alt->next = NULL;
530 initial_base = url;
531
532 if (pull(commit_id))
533 return 1;
534
535 curl_slist_free_all(no_pragma_header);
536 curl_global_cleanup();
537 return 0;
538}