Merge branch 'jk/send-pack' into HEAD
[git] / http-walker.c
1 #include "cache.h"
2 #include "commit.h"
3 #include "pack.h"
4 #include "walker.h"
5 #include "http.h"
6
7 #define PREV_BUF_SIZE 4096
8 #define RANGE_HEADER_SIZE 30
9
10 struct alt_base
11 {
12         char *base;
13         int got_indices;
14         struct packed_git *packs;
15         struct alt_base *next;
16 };
17
18 enum object_request_state {
19         WAITING,
20         ABORTED,
21         ACTIVE,
22         COMPLETE,
23 };
24
25 struct object_request
26 {
27         struct walker *walker;
28         unsigned char sha1[20];
29         struct alt_base *repo;
30         char *url;
31         char filename[PATH_MAX];
32         char tmpfile[PATH_MAX];
33         int local;
34         enum object_request_state state;
35         CURLcode curl_result;
36         char errorstr[CURL_ERROR_SIZE];
37         long http_code;
38         unsigned char real_sha1[20];
39         SHA_CTX c;
40         z_stream stream;
41         int zret;
42         int rename;
43         struct active_request_slot *slot;
44         struct object_request *next;
45 };
46
47 struct alternates_request {
48         struct walker *walker;
49         const char *base;
50         char *url;
51         struct buffer *buffer;
52         struct active_request_slot *slot;
53         int http_specific;
54 };
55
56 struct walker_data {
57         const char *url;
58         int got_alternates;
59         struct alt_base *alt;
60         struct curl_slist *no_pragma_header;
61 };
62
63 static struct object_request *object_queue_head;
64
65 static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
66                                void *data)
67 {
68         unsigned char expn[4096];
69         size_t size = eltsize * nmemb;
70         int posn = 0;
71         struct object_request *obj_req = (struct object_request *)data;
72         do {
73                 ssize_t retval = xwrite(obj_req->local,
74                                        (char *) ptr + posn, size - posn);
75                 if (retval < 0)
76                         return posn;
77                 posn += retval;
78         } while (posn < size);
79
80         obj_req->stream.avail_in = size;
81         obj_req->stream.next_in = ptr;
82         do {
83                 obj_req->stream.next_out = expn;
84                 obj_req->stream.avail_out = sizeof(expn);
85                 obj_req->zret = inflate(&obj_req->stream, Z_SYNC_FLUSH);
86                 SHA1_Update(&obj_req->c, expn,
87                             sizeof(expn) - obj_req->stream.avail_out);
88         } while (obj_req->stream.avail_in && obj_req->zret == Z_OK);
89         data_received++;
90         return size;
91 }
92
93 static int missing__target(int code, int result)
94 {
95         return  /* file:// URL -- do we ever use one??? */
96                 (result == CURLE_FILE_COULDNT_READ_FILE) ||
97                 /* http:// and https:// URL */
98                 (code == 404 && result == CURLE_HTTP_RETURNED_ERROR) ||
99                 /* ftp:// URL */
100                 (code == 550 && result == CURLE_FTP_COULDNT_RETR_FILE)
101                 ;
102 }
103
104 #define missing_target(a) missing__target((a)->http_code, (a)->curl_result)
105
106 static void fetch_alternates(struct walker *walker, const char *base);
107
108 static void process_object_response(void *callback_data);
109
110 static void start_object_request(struct walker *walker,
111                                  struct object_request *obj_req)
112 {
113         char *hex = sha1_to_hex(obj_req->sha1);
114         char prevfile[PATH_MAX];
115         char *url;
116         char *posn;
117         int prevlocal;
118         unsigned char prev_buf[PREV_BUF_SIZE];
119         ssize_t prev_read = 0;
120         long prev_posn = 0;
121         char range[RANGE_HEADER_SIZE];
122         struct curl_slist *range_header = NULL;
123         struct active_request_slot *slot;
124         struct walker_data *data = walker->data;
125
126         snprintf(prevfile, sizeof(prevfile), "%s.prev", obj_req->filename);
127         unlink(prevfile);
128         rename(obj_req->tmpfile, prevfile);
129         unlink(obj_req->tmpfile);
130
131         if (obj_req->local != -1)
132                 error("fd leakage in start: %d", obj_req->local);
133         obj_req->local = open(obj_req->tmpfile,
134                               O_WRONLY | O_CREAT | O_EXCL, 0666);
135         /* This could have failed due to the "lazy directory creation";
136          * try to mkdir the last path component.
137          */
138         if (obj_req->local < 0 && errno == ENOENT) {
139                 char *dir = strrchr(obj_req->tmpfile, '/');
140                 if (dir) {
141                         *dir = 0;
142                         mkdir(obj_req->tmpfile, 0777);
143                         *dir = '/';
144                 }
145                 obj_req->local = open(obj_req->tmpfile,
146                                       O_WRONLY | O_CREAT | O_EXCL, 0666);
147         }
148
149         if (obj_req->local < 0) {
150                 obj_req->state = ABORTED;
151                 error("Couldn't create temporary file %s for %s: %s",
152                       obj_req->tmpfile, obj_req->filename, strerror(errno));
153                 return;
154         }
155
156         memset(&obj_req->stream, 0, sizeof(obj_req->stream));
157
158         inflateInit(&obj_req->stream);
159
160         SHA1_Init(&obj_req->c);
161
162         url = xmalloc(strlen(obj_req->repo->base) + 51);
163         obj_req->url = xmalloc(strlen(obj_req->repo->base) + 51);
164         strcpy(url, obj_req->repo->base);
165         posn = url + strlen(obj_req->repo->base);
166         strcpy(posn, "/objects/");
167         posn += 9;
168         memcpy(posn, hex, 2);
169         posn += 2;
170         *(posn++) = '/';
171         strcpy(posn, hex + 2);
172         strcpy(obj_req->url, url);
173
174         /* If a previous temp file is present, process what was already
175            fetched. */
176         prevlocal = open(prevfile, O_RDONLY);
177         if (prevlocal != -1) {
178                 do {
179                         prev_read = xread(prevlocal, prev_buf, PREV_BUF_SIZE);
180                         if (prev_read>0) {
181                                 if (fwrite_sha1_file(prev_buf,
182                                                      1,
183                                                      prev_read,
184                                                      obj_req) == prev_read) {
185                                         prev_posn += prev_read;
186                                 } else {
187                                         prev_read = -1;
188                                 }
189                         }
190                 } while (prev_read > 0);
191                 close(prevlocal);
192         }
193         unlink(prevfile);
194
195         /* Reset inflate/SHA1 if there was an error reading the previous temp
196            file; also rewind to the beginning of the local file. */
197         if (prev_read == -1) {
198                 memset(&obj_req->stream, 0, sizeof(obj_req->stream));
199                 inflateInit(&obj_req->stream);
200                 SHA1_Init(&obj_req->c);
201                 if (prev_posn>0) {
202                         prev_posn = 0;
203                         lseek(obj_req->local, 0, SEEK_SET);
204                         ftruncate(obj_req->local, 0);
205                 }
206         }
207
208         slot = get_active_slot();
209         slot->callback_func = process_object_response;
210         slot->callback_data = obj_req;
211         obj_req->slot = slot;
212
213         curl_easy_setopt(slot->curl, CURLOPT_FILE, obj_req);
214         curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file);
215         curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, obj_req->errorstr);
216         curl_easy_setopt(slot->curl, CURLOPT_URL, url);
217         curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, data->no_pragma_header);
218
219         /* If we have successfully processed data from a previous fetch
220            attempt, only fetch the data we don't already have. */
221         if (prev_posn>0) {
222                 if (walker->get_verbosely)
223                         fprintf(stderr,
224                                 "Resuming fetch of object %s at byte %ld\n",
225                                 hex, prev_posn);
226                 sprintf(range, "Range: bytes=%ld-", prev_posn);
227                 range_header = curl_slist_append(range_header, range);
228                 curl_easy_setopt(slot->curl,
229                                  CURLOPT_HTTPHEADER, range_header);
230         }
231
232         /* Try to get the request started, abort the request on error */
233         obj_req->state = ACTIVE;
234         if (!start_active_slot(slot)) {
235                 obj_req->state = ABORTED;
236                 obj_req->slot = NULL;
237                 close(obj_req->local); obj_req->local = -1;
238                 free(obj_req->url);
239                 return;
240         }
241 }
242
243 static void finish_object_request(struct object_request *obj_req)
244 {
245         struct stat st;
246
247         fchmod(obj_req->local, 0444);
248         close(obj_req->local); obj_req->local = -1;
249
250         if (obj_req->http_code == 416) {
251                 fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n");
252         } else if (obj_req->curl_result != CURLE_OK) {
253                 if (stat(obj_req->tmpfile, &st) == 0)
254                         if (st.st_size == 0)
255                                 unlink(obj_req->tmpfile);
256                 return;
257         }
258
259         inflateEnd(&obj_req->stream);
260         SHA1_Final(obj_req->real_sha1, &obj_req->c);
261         if (obj_req->zret != Z_STREAM_END) {
262                 unlink(obj_req->tmpfile);
263                 return;
264         }
265         if (hashcmp(obj_req->sha1, obj_req->real_sha1)) {
266                 unlink(obj_req->tmpfile);
267                 return;
268         }
269         obj_req->rename =
270                 move_temp_to_file(obj_req->tmpfile, obj_req->filename);
271
272         if (obj_req->rename == 0)
273                 walker_say(obj_req->walker, "got %s\n", sha1_to_hex(obj_req->sha1));
274 }
275
276 static void process_object_response(void *callback_data)
277 {
278         struct object_request *obj_req =
279                 (struct object_request *)callback_data;
280         struct walker *walker = obj_req->walker;
281         struct walker_data *data = walker->data;
282         struct alt_base *alt = data->alt;
283
284         obj_req->curl_result = obj_req->slot->curl_result;
285         obj_req->http_code = obj_req->slot->http_code;
286         obj_req->slot = NULL;
287         obj_req->state = COMPLETE;
288
289         /* Use alternates if necessary */
290         if (missing_target(obj_req)) {
291                 fetch_alternates(walker, alt->base);
292                 if (obj_req->repo->next != NULL) {
293                         obj_req->repo =
294                                 obj_req->repo->next;
295                         close(obj_req->local);
296                         obj_req->local = -1;
297                         start_object_request(walker, obj_req);
298                         return;
299                 }
300         }
301
302         finish_object_request(obj_req);
303 }
304
305 static void release_object_request(struct object_request *obj_req)
306 {
307         struct object_request *entry = object_queue_head;
308
309         if (obj_req->local != -1)
310                 error("fd leakage in release: %d", obj_req->local);
311         if (obj_req == object_queue_head) {
312                 object_queue_head = obj_req->next;
313         } else {
314                 while (entry->next != NULL && entry->next != obj_req)
315                         entry = entry->next;
316                 if (entry->next == obj_req)
317                         entry->next = entry->next->next;
318         }
319
320         free(obj_req->url);
321         free(obj_req);
322 }
323
324 #ifdef USE_CURL_MULTI
325 static int fill_active_slot(struct walker *walker)
326 {
327         struct object_request *obj_req;
328
329         for (obj_req = object_queue_head; obj_req; obj_req = obj_req->next) {
330                 if (obj_req->state == WAITING) {
331                         if (has_sha1_file(obj_req->sha1))
332                                 obj_req->state = COMPLETE;
333                         else {
334                                 start_object_request(walker, obj_req);
335                                 return 1;
336                         }
337                 }
338         }
339         return 0;
340 }
341 #endif
342
343 static void prefetch(struct walker *walker, unsigned char *sha1)
344 {
345         struct object_request *newreq;
346         struct object_request *tail;
347         struct walker_data *data = walker->data;
348         char *filename = sha1_file_name(sha1);
349
350         newreq = xmalloc(sizeof(*newreq));
351         newreq->walker = walker;
352         hashcpy(newreq->sha1, sha1);
353         newreq->repo = data->alt;
354         newreq->url = NULL;
355         newreq->local = -1;
356         newreq->state = WAITING;
357         snprintf(newreq->filename, sizeof(newreq->filename), "%s", filename);
358         snprintf(newreq->tmpfile, sizeof(newreq->tmpfile),
359                  "%s.temp", filename);
360         newreq->slot = NULL;
361         newreq->next = NULL;
362
363         if (object_queue_head == NULL) {
364                 object_queue_head = newreq;
365         } else {
366                 tail = object_queue_head;
367                 while (tail->next != NULL) {
368                         tail = tail->next;
369                 }
370                 tail->next = newreq;
371         }
372
373 #ifdef USE_CURL_MULTI
374         fill_active_slots();
375         step_active_slots();
376 #endif
377 }
378
379 static int fetch_index(struct walker *walker, struct alt_base *repo, unsigned char *sha1)
380 {
381         char *hex = sha1_to_hex(sha1);
382         char *filename;
383         char *url;
384         char tmpfile[PATH_MAX];
385         long prev_posn = 0;
386         char range[RANGE_HEADER_SIZE];
387         struct curl_slist *range_header = NULL;
388         struct walker_data *data = walker->data;
389
390         FILE *indexfile;
391         struct active_request_slot *slot;
392         struct slot_results results;
393
394         if (has_pack_index(sha1))
395                 return 0;
396
397         if (walker->get_verbosely)
398                 fprintf(stderr, "Getting index for pack %s\n", hex);
399
400         url = xmalloc(strlen(repo->base) + 64);
401         sprintf(url, "%s/objects/pack/pack-%s.idx", repo->base, hex);
402
403         filename = sha1_pack_index_name(sha1);
404         snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
405         indexfile = fopen(tmpfile, "a");
406         if (!indexfile)
407                 return error("Unable to open local file %s for pack index",
408                              filename);
409
410         slot = get_active_slot();
411         slot->results = &results;
412         curl_easy_setopt(slot->curl, CURLOPT_FILE, indexfile);
413         curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
414         curl_easy_setopt(slot->curl, CURLOPT_URL, url);
415         curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, data->no_pragma_header);
416         slot->local = indexfile;
417
418         /* If there is data present from a previous transfer attempt,
419            resume where it left off */
420         prev_posn = ftell(indexfile);
421         if (prev_posn>0) {
422                 if (walker->get_verbosely)
423                         fprintf(stderr,
424                                 "Resuming fetch of index for pack %s at byte %ld\n",
425                                 hex, prev_posn);
426                 sprintf(range, "Range: bytes=%ld-", prev_posn);
427                 range_header = curl_slist_append(range_header, range);
428                 curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
429         }
430
431         if (start_active_slot(slot)) {
432                 run_active_slot(slot);
433                 if (results.curl_result != CURLE_OK) {
434                         fclose(indexfile);
435                         return error("Unable to get pack index %s\n%s", url,
436                                      curl_errorstr);
437                 }
438         } else {
439                 fclose(indexfile);
440                 return error("Unable to start request");
441         }
442
443         fclose(indexfile);
444
445         return move_temp_to_file(tmpfile, filename);
446 }
447
448 static int setup_index(struct walker *walker, struct alt_base *repo, unsigned char *sha1)
449 {
450         struct packed_git *new_pack;
451         if (has_pack_file(sha1))
452                 return 0; /* don't list this as something we can get */
453
454         if (fetch_index(walker, repo, sha1))
455                 return -1;
456
457         new_pack = parse_pack_index(sha1);
458         new_pack->next = repo->packs;
459         repo->packs = new_pack;
460         return 0;
461 }
462
463 static void process_alternates_response(void *callback_data)
464 {
465         struct alternates_request *alt_req =
466                 (struct alternates_request *)callback_data;
467         struct walker *walker = alt_req->walker;
468         struct walker_data *cdata = walker->data;
469         struct active_request_slot *slot = alt_req->slot;
470         struct alt_base *tail = cdata->alt;
471         const char *base = alt_req->base;
472         static const char null_byte = '\0';
473         char *data;
474         int i = 0;
475
476         if (alt_req->http_specific) {
477                 if (slot->curl_result != CURLE_OK ||
478                     !alt_req->buffer->posn) {
479
480                         /* Try reusing the slot to get non-http alternates */
481                         alt_req->http_specific = 0;
482                         sprintf(alt_req->url, "%s/objects/info/alternates",
483                                 base);
484                         curl_easy_setopt(slot->curl, CURLOPT_URL,
485                                          alt_req->url);
486                         active_requests++;
487                         slot->in_use = 1;
488                         if (slot->finished != NULL)
489                                 (*slot->finished) = 0;
490                         if (!start_active_slot(slot)) {
491                                 cdata->got_alternates = -1;
492                                 slot->in_use = 0;
493                                 if (slot->finished != NULL)
494                                         (*slot->finished) = 1;
495                         }
496                         return;
497                 }
498         } else if (slot->curl_result != CURLE_OK) {
499                 if (!missing_target(slot)) {
500                         cdata->got_alternates = -1;
501                         return;
502                 }
503         }
504
505         fwrite_buffer(&null_byte, 1, 1, alt_req->buffer);
506         alt_req->buffer->posn--;
507         data = alt_req->buffer->buffer;
508
509         while (i < alt_req->buffer->posn) {
510                 int posn = i;
511                 while (posn < alt_req->buffer->posn && data[posn] != '\n')
512                         posn++;
513                 if (data[posn] == '\n') {
514                         int okay = 0;
515                         int serverlen = 0;
516                         struct alt_base *newalt;
517                         char *target = NULL;
518                         if (data[i] == '/') {
519                                 /* This counts
520                                  * http://git.host/pub/scm/linux.git/
521                                  * -----------here^
522                                  * so memcpy(dst, base, serverlen) will
523                                  * copy up to "...git.host".
524                                  */
525                                 const char *colon_ss = strstr(base,"://");
526                                 if (colon_ss) {
527                                         serverlen = (strchr(colon_ss + 3, '/')
528                                                      - base);
529                                         okay = 1;
530                                 }
531                         } else if (!memcmp(data + i, "../", 3)) {
532                                 /* Relative URL; chop the corresponding
533                                  * number of subpath from base (and ../
534                                  * from data), and concatenate the result.
535                                  *
536                                  * The code first drops ../ from data, and
537                                  * then drops one ../ from data and one path
538                                  * from base.  IOW, one extra ../ is dropped
539                                  * from data than path is dropped from base.
540                                  *
541                                  * This is not wrong.  The alternate in
542                                  *     http://git.host/pub/scm/linux.git/
543                                  * to borrow from
544                                  *     http://git.host/pub/scm/linus.git/
545                                  * is ../../linus.git/objects/.  You need
546                                  * two ../../ to borrow from your direct
547                                  * neighbour.
548                                  */
549                                 i += 3;
550                                 serverlen = strlen(base);
551                                 while (i + 2 < posn &&
552                                        !memcmp(data + i, "../", 3)) {
553                                         do {
554                                                 serverlen--;
555                                         } while (serverlen &&
556                                                  base[serverlen - 1] != '/');
557                                         i += 3;
558                                 }
559                                 /* If the server got removed, give up. */
560                                 okay = strchr(base, ':') - base + 3 <
561                                         serverlen;
562                         } else if (alt_req->http_specific) {
563                                 char *colon = strchr(data + i, ':');
564                                 char *slash = strchr(data + i, '/');
565                                 if (colon && slash && colon < data + posn &&
566                                     slash < data + posn && colon < slash) {
567                                         okay = 1;
568                                 }
569                         }
570                         /* skip "objects\n" at end */
571                         if (okay) {
572                                 target = xmalloc(serverlen + posn - i - 6);
573                                 memcpy(target, base, serverlen);
574                                 memcpy(target + serverlen, data + i,
575                                        posn - i - 7);
576                                 target[serverlen + posn - i - 7] = 0;
577                                 if (walker->get_verbosely)
578                                         fprintf(stderr,
579                                                 "Also look at %s\n", target);
580                                 newalt = xmalloc(sizeof(*newalt));
581                                 newalt->next = NULL;
582                                 newalt->base = target;
583                                 newalt->got_indices = 0;
584                                 newalt->packs = NULL;
585
586                                 while (tail->next != NULL)
587                                         tail = tail->next;
588                                 tail->next = newalt;
589                         }
590                 }
591                 i = posn + 1;
592         }
593
594         cdata->got_alternates = 1;
595 }
596
597 static void fetch_alternates(struct walker *walker, const char *base)
598 {
599         struct buffer buffer;
600         char *url;
601         char *data;
602         struct active_request_slot *slot;
603         struct alternates_request alt_req;
604         struct walker_data *cdata = walker->data;
605
606         /* If another request has already started fetching alternates,
607            wait for them to arrive and return to processing this request's
608            curl message */
609 #ifdef USE_CURL_MULTI
610         while (cdata->got_alternates == 0) {
611                 step_active_slots();
612         }
613 #endif
614
615         /* Nothing to do if they've already been fetched */
616         if (cdata->got_alternates == 1)
617                 return;
618
619         /* Start the fetch */
620         cdata->got_alternates = 0;
621
622         data = xmalloc(4096);
623         buffer.size = 4096;
624         buffer.posn = 0;
625         buffer.buffer = data;
626
627         if (walker->get_verbosely)
628                 fprintf(stderr, "Getting alternates list for %s\n", base);
629
630         url = xmalloc(strlen(base) + 31);
631         sprintf(url, "%s/objects/info/http-alternates", base);
632
633         /* Use a callback to process the result, since another request
634            may fail and need to have alternates loaded before continuing */
635         slot = get_active_slot();
636         slot->callback_func = process_alternates_response;
637         alt_req.walker = walker;
638         slot->callback_data = &alt_req;
639
640         curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
641         curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
642         curl_easy_setopt(slot->curl, CURLOPT_URL, url);
643
644         alt_req.base = base;
645         alt_req.url = url;
646         alt_req.buffer = &buffer;
647         alt_req.http_specific = 1;
648         alt_req.slot = slot;
649
650         if (start_active_slot(slot))
651                 run_active_slot(slot);
652         else
653                 cdata->got_alternates = -1;
654
655         free(data);
656         free(url);
657 }
658
659 static int fetch_indices(struct walker *walker, struct alt_base *repo)
660 {
661         unsigned char sha1[20];
662         char *url;
663         struct buffer buffer;
664         char *data;
665         int i = 0;
666
667         struct active_request_slot *slot;
668         struct slot_results results;
669
670         if (repo->got_indices)
671                 return 0;
672
673         data = xmalloc(4096);
674         buffer.size = 4096;
675         buffer.posn = 0;
676         buffer.buffer = data;
677
678         if (walker->get_verbosely)
679                 fprintf(stderr, "Getting pack list for %s\n", repo->base);
680
681         url = xmalloc(strlen(repo->base) + 21);
682         sprintf(url, "%s/objects/info/packs", repo->base);
683
684         slot = get_active_slot();
685         slot->results = &results;
686         curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
687         curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
688         curl_easy_setopt(slot->curl, CURLOPT_URL, url);
689         curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
690         if (start_active_slot(slot)) {
691                 run_active_slot(slot);
692                 if (results.curl_result != CURLE_OK) {
693                         if (missing_target(&results)) {
694                                 repo->got_indices = 1;
695                                 free(buffer.buffer);
696                                 return 0;
697                         } else {
698                                 repo->got_indices = 0;
699                                 free(buffer.buffer);
700                                 return error("%s", curl_errorstr);
701                         }
702                 }
703         } else {
704                 repo->got_indices = 0;
705                 free(buffer.buffer);
706                 return error("Unable to start request");
707         }
708
709         data = buffer.buffer;
710         while (i < buffer.posn) {
711                 switch (data[i]) {
712                 case 'P':
713                         i++;
714                         if (i + 52 <= buffer.posn &&
715                             !prefixcmp(data + i, " pack-") &&
716                             !prefixcmp(data + i + 46, ".pack\n")) {
717                                 get_sha1_hex(data + i + 6, sha1);
718                                 setup_index(walker, repo, sha1);
719                                 i += 51;
720                                 break;
721                         }
722                 default:
723                         while (i < buffer.posn && data[i] != '\n')
724                                 i++;
725                 }
726                 i++;
727         }
728
729         free(buffer.buffer);
730         repo->got_indices = 1;
731         return 0;
732 }
733
734 static int fetch_pack(struct walker *walker, struct alt_base *repo, unsigned char *sha1)
735 {
736         char *url;
737         struct packed_git *target;
738         struct packed_git **lst;
739         FILE *packfile;
740         char *filename;
741         char tmpfile[PATH_MAX];
742         int ret;
743         long prev_posn = 0;
744         char range[RANGE_HEADER_SIZE];
745         struct curl_slist *range_header = NULL;
746         struct walker_data *data = walker->data;
747
748         struct active_request_slot *slot;
749         struct slot_results results;
750
751         if (fetch_indices(walker, repo))
752                 return -1;
753         target = find_sha1_pack(sha1, repo->packs);
754         if (!target)
755                 return -1;
756
757         if (walker->get_verbosely) {
758                 fprintf(stderr, "Getting pack %s\n",
759                         sha1_to_hex(target->sha1));
760                 fprintf(stderr, " which contains %s\n",
761                         sha1_to_hex(sha1));
762         }
763
764         url = xmalloc(strlen(repo->base) + 65);
765         sprintf(url, "%s/objects/pack/pack-%s.pack",
766                 repo->base, sha1_to_hex(target->sha1));
767
768         filename = sha1_pack_name(target->sha1);
769         snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
770         packfile = fopen(tmpfile, "a");
771         if (!packfile)
772                 return error("Unable to open local file %s for pack",
773                              filename);
774
775         slot = get_active_slot();
776         slot->results = &results;
777         curl_easy_setopt(slot->curl, CURLOPT_FILE, packfile);
778         curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
779         curl_easy_setopt(slot->curl, CURLOPT_URL, url);
780         curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, data->no_pragma_header);
781         slot->local = packfile;
782
783         /* If there is data present from a previous transfer attempt,
784            resume where it left off */
785         prev_posn = ftell(packfile);
786         if (prev_posn>0) {
787                 if (walker->get_verbosely)
788                         fprintf(stderr,
789                                 "Resuming fetch of pack %s at byte %ld\n",
790                                 sha1_to_hex(target->sha1), prev_posn);
791                 sprintf(range, "Range: bytes=%ld-", prev_posn);
792                 range_header = curl_slist_append(range_header, range);
793                 curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header);
794         }
795
796         if (start_active_slot(slot)) {
797                 run_active_slot(slot);
798                 if (results.curl_result != CURLE_OK) {
799                         fclose(packfile);
800                         return error("Unable to get pack file %s\n%s", url,
801                                      curl_errorstr);
802                 }
803         } else {
804                 fclose(packfile);
805                 return error("Unable to start request");
806         }
807
808         target->pack_size = ftell(packfile);
809         fclose(packfile);
810
811         ret = move_temp_to_file(tmpfile, filename);
812         if (ret)
813                 return ret;
814
815         lst = &repo->packs;
816         while (*lst != target)
817                 lst = &((*lst)->next);
818         *lst = (*lst)->next;
819
820         if (verify_pack(target, 0))
821                 return -1;
822         install_packed_git(target);
823
824         return 0;
825 }
826
827 static void abort_object_request(struct object_request *obj_req)
828 {
829         if (obj_req->local >= 0) {
830                 close(obj_req->local);
831                 obj_req->local = -1;
832         }
833         unlink(obj_req->tmpfile);
834         if (obj_req->slot) {
835                 release_active_slot(obj_req->slot);
836                 obj_req->slot = NULL;
837         }
838         release_object_request(obj_req);
839 }
840
841 static int fetch_object(struct walker *walker, struct alt_base *repo, unsigned char *sha1)
842 {
843         char *hex = sha1_to_hex(sha1);
844         int ret = 0;
845         struct object_request *obj_req = object_queue_head;
846
847         while (obj_req != NULL && hashcmp(obj_req->sha1, sha1))
848                 obj_req = obj_req->next;
849         if (obj_req == NULL)
850                 return error("Couldn't find request for %s in the queue", hex);
851
852         if (has_sha1_file(obj_req->sha1)) {
853                 abort_object_request(obj_req);
854                 return 0;
855         }
856
857 #ifdef USE_CURL_MULTI
858         while (obj_req->state == WAITING) {
859                 step_active_slots();
860         }
861 #else
862         start_object_request(walker, obj_req);
863 #endif
864
865         while (obj_req->state == ACTIVE) {
866                 run_active_slot(obj_req->slot);
867         }
868         if (obj_req->local != -1) {
869                 close(obj_req->local); obj_req->local = -1;
870         }
871
872         if (obj_req->state == ABORTED) {
873                 ret = error("Request for %s aborted", hex);
874         } else if (obj_req->curl_result != CURLE_OK &&
875                    obj_req->http_code != 416) {
876                 if (missing_target(obj_req))
877                         ret = -1; /* Be silent, it is probably in a pack. */
878                 else
879                         ret = error("%s (curl_result = %d, http_code = %ld, sha1 = %s)",
880                                     obj_req->errorstr, obj_req->curl_result,
881                                     obj_req->http_code, hex);
882         } else if (obj_req->zret != Z_STREAM_END) {
883                 walker->corrupt_object_found++;
884                 ret = error("File %s (%s) corrupt", hex, obj_req->url);
885         } else if (hashcmp(obj_req->sha1, obj_req->real_sha1)) {
886                 ret = error("File %s has bad hash", hex);
887         } else if (obj_req->rename < 0) {
888                 ret = error("unable to write sha1 filename %s",
889                             obj_req->filename);
890         }
891
892         release_object_request(obj_req);
893         return ret;
894 }
895
896 static int fetch(struct walker *walker, unsigned char *sha1)
897 {
898         struct walker_data *data = walker->data;
899         struct alt_base *altbase = data->alt;
900
901         if (!fetch_object(walker, altbase, sha1))
902                 return 0;
903         while (altbase) {
904                 if (!fetch_pack(walker, altbase, sha1))
905                         return 0;
906                 fetch_alternates(walker, data->alt->base);
907                 altbase = altbase->next;
908         }
909         return error("Unable to find %s under %s", sha1_to_hex(sha1),
910                      data->alt->base);
911 }
912
913 static inline int needs_quote(int ch)
914 {
915         if (((ch >= 'A') && (ch <= 'Z'))
916                         || ((ch >= 'a') && (ch <= 'z'))
917                         || ((ch >= '0') && (ch <= '9'))
918                         || (ch == '/')
919                         || (ch == '-')
920                         || (ch == '.'))
921                 return 0;
922         return 1;
923 }
924
925 static inline int hex(int v)
926 {
927         if (v < 10) return '0' + v;
928         else return 'A' + v - 10;
929 }
930
931 static char *quote_ref_url(const char *base, const char *ref)
932 {
933         const char *cp;
934         char *dp, *qref;
935         int len, baselen, ch;
936
937         baselen = strlen(base);
938         len = baselen + 7; /* "/refs/" + NUL */
939         for (cp = ref; (ch = *cp) != 0; cp++, len++)
940                 if (needs_quote(ch))
941                         len += 2; /* extra two hex plus replacement % */
942         qref = xmalloc(len);
943         memcpy(qref, base, baselen);
944         memcpy(qref + baselen, "/refs/", 6);
945         for (cp = ref, dp = qref + baselen + 6; (ch = *cp) != 0; cp++) {
946                 if (needs_quote(ch)) {
947                         *dp++ = '%';
948                         *dp++ = hex((ch >> 4) & 0xF);
949                         *dp++ = hex(ch & 0xF);
950                 }
951                 else
952                         *dp++ = ch;
953         }
954         *dp = 0;
955
956         return qref;
957 }
958
959 static int fetch_ref(struct walker *walker, char *ref, unsigned char *sha1)
960 {
961         char *url;
962         char hex[42];
963         struct buffer buffer;
964         struct walker_data *data = walker->data;
965         const char *base = data->alt->base;
966         struct active_request_slot *slot;
967         struct slot_results results;
968         buffer.size = 41;
969         buffer.posn = 0;
970         buffer.buffer = hex;
971         hex[41] = '\0';
972
973         url = quote_ref_url(base, ref);
974         slot = get_active_slot();
975         slot->results = &results;
976         curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer);
977         curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
978         curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL);
979         curl_easy_setopt(slot->curl, CURLOPT_URL, url);
980         if (start_active_slot(slot)) {
981                 run_active_slot(slot);
982                 if (results.curl_result != CURLE_OK)
983                         return error("Couldn't get %s for %s\n%s",
984                                      url, ref, curl_errorstr);
985         } else {
986                 return error("Unable to start request");
987         }
988
989         hex[40] = '\0';
990         get_sha1_hex(hex, sha1);
991         return 0;
992 }
993
994 static void cleanup(struct walker *walker)
995 {
996         struct walker_data *data = walker->data;
997         http_cleanup();
998
999         curl_slist_free_all(data->no_pragma_header);
1000 }
1001
1002 struct walker *get_http_walker(const char *url)
1003 {
1004         char *s;
1005         struct walker_data *data = xmalloc(sizeof(struct walker_data));
1006         struct walker *walker = xmalloc(sizeof(struct walker));
1007
1008         http_init();
1009
1010         data->no_pragma_header = curl_slist_append(NULL, "Pragma:");
1011
1012         data->alt = xmalloc(sizeof(*data->alt));
1013         data->alt->base = xmalloc(strlen(url) + 1);
1014         strcpy(data->alt->base, url);
1015         for (s = data->alt->base + strlen(data->alt->base) - 1; *s == '/'; --s)
1016                 *s = 0;
1017
1018         data->alt->got_indices = 0;
1019         data->alt->packs = NULL;
1020         data->alt->next = NULL;
1021         data->got_alternates = -1;
1022
1023         walker->corrupt_object_found = 0;
1024         walker->fetch = fetch;
1025         walker->fetch_ref = fetch_ref;
1026         walker->prefetch = prefetch;
1027         walker->cleanup = cleanup;
1028         walker->data = data;
1029
1030 #ifdef USE_CURL_MULTI
1031         add_fill_function(walker, (int (*)(void *)) fill_active_slot);
1032 #endif
1033
1034         return walker;
1035 }