Merge master.kernel.org:/home/hpa/git/daemon
[git.git] / http-fetch.c
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
16 static CURL *curl;
17 static struct curl_slist *no_pragma_header;
18 static char curl_errorstr[CURL_ERROR_SIZE];
19
20 static char *initial_base;
21
22 struct alt_base
23 {
24         char *base;
25         int got_indices;
26         struct packed_git *packs;
27         struct alt_base *next;
28 };
29
30 struct alt_base *alt = NULL;
31
32 static SHA_CTX c;
33 static z_stream stream;
34
35 static int local;
36 static int zret;
37
38 static int curl_ssl_verify;
39
40 struct buffer
41 {
42         size_t posn;
43         size_t size;
44         void *buffer;
45 };
46
47 static 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
58 static 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
82 void prefetch(unsigned char *sha1)
83 {
84 }
85
86 static int got_alternates = 0;
87
88 static 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
128 static 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
143 static 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
244 static 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
298 static 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
353 int 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
430 int 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
448 int 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
482 int 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 }