/* * The dumb HTTP transport, which lets a client clone by fetching plain files * rather than by talking to a server side helper. It answers the requests * such a client makes, the ref listing under info, the loose objects and pack * files under objects, and HEAD, each written as raw bytes rather than as a * page. The two listings a clone starts from are built per request, so a * repository serves without anyone having run git update-server-info over it. */ #define USE_THE_REPOSITORY_VARIABLE #include "cgit.h" #include "html.h" #include "ui-clone.h" #include "ui-shared.h" /* * A client reading this listing expects an annotated tag to be followed by a * second line, marked with ^{}, naming the object the tag peels to, the * format git update-server-info writes into info/refs. */ static int print_ref(const struct reference *ref, void *cb_data) { struct object *obj; obj = parse_object(the_repository, ref->oid); if (!obj) return 0; htmlf("%s\t%s\n", oid_to_hex(ref->oid), ref->name); if (obj->type == OBJ_TAG) { obj = deref_tag(the_repository, obj, ref->name, 0); if (!obj) return 0; htmlf("%s\t%s^{}\n", oid_to_hex(&obj->oid), ref->name); } return 0; } /* * A path ending in a slash is handed back whole, where git's own * pack_basename would return the empty string. */ static const char *last_path_component(const char *path) { const char *slash = strrchr(path, '/'); if (slash && slash[1] != '\0') return slash + 1; return path; } /* * Only the packs this repository holds itself are listed, because one * borrowed from an alternate is not reachable below this URL and a client * told about it would come back for a file that is not there. */ static void print_pack_info(void) { struct odb_source *source; ctx.page.mimetype = "text/plain"; ctx.page.filename = "objects/info/packs"; cgit_print_http_headers(); odb_reprepare(the_repository->objects); for (source = the_repository->objects->sources; source; source = source->next) { struct odb_source_files *files = odb_source_files_downcast(source); struct packfile_list_entry *entry; // Asked for through the accessor rather than read off the list, // because a pack the multi-pack-index already covers joins that // list only when the accessor loads it, and reading the field // directly leaves those packs unfindable. for (entry = packfile_store_get_packs(files->packed); entry; entry = entry->next) { struct packed_git *pack = entry->pack; if (pack->pack_local) htmlf("P %s\n", last_path_component(pack->pack_name)); } } } /* * Only the characters an object path can hold pass, and no dotdot component. */ static int path_is_safe(const char *path) { const char *p; for (p = path; *p; ++p) { if (*p == '.' && *(p + 1) == '.') return 0; if (!isalnum((unsigned char)*p) && *p != '/' && *p != '.' && *p != '-') return 0; } return 1; } static void send_file(const char *path) { struct stat st; if (stat(path, &st)) { switch (errno) { case ENOENT: cgit_print_error_page(404, "Not Found", "Not found"); break; case EACCES: cgit_print_error_page(403, "Forbidden", "Forbidden"); break; default: cgit_print_error_page(400, "Bad Request", "Bad request"); } return; } // fopen opens a directory on most systems and reads nothing from it. if (!S_ISREG(st.st_mode)) { cgit_print_error_page(404, "Not Found", "Not found"); return; } ctx.page.mimetype = "application/octet-stream"; // Offer the file under its path inside the repository, so the layout // of the server's disk stays out of the download name. ctx.page.filename = path; skip_prefix(path, ctx.repo->path, &ctx.page.filename); skip_prefix(ctx.page.filename, "/", &ctx.page.filename); cgit_print_http_headers(); html_include(path); } void cgit_clone_info(void) { if (!ctx.qry.path || strcmp(ctx.qry.path, "refs")) { cgit_print_error_page(400, "Bad Request", "Bad request"); return; } ctx.page.mimetype = "text/plain"; ctx.page.filename = "info/refs"; cgit_print_http_headers(); refs_for_each_ref(get_main_ref_store(the_repository), print_ref, NULL); } void cgit_clone_objects(void) { char *path; if (!ctx.qry.path) goto err; if (!strcmp(ctx.qry.path, "info/packs")) { print_pack_info(); return; } if (!path_is_safe(ctx.qry.path)) goto err; path = repo_git_path(the_repository, "objects/%s", ctx.qry.path); send_file(path); free(path); return; err: cgit_print_error_page(400, "Bad Request", "Bad request"); } void cgit_clone_head(void) { char *path; path = repo_git_path(the_repository, "HEAD"); send_file(path); free(path); }