| /* |
| * Copyright (C) the libgit2 contributors. All rights reserved. |
| * |
| * This file is part of libgit2, distributed under the GNU GPL v2 with |
| * a Linking Exception. For full terms see the included COPYING file. |
| */ |
| |
| #include "server.h" |
| #include "netops.h" |
| |
| int git_server_new(git_server **out, git_repository *repo, int fd) |
| { |
| git_server *server; |
| |
| assert(out && repo); |
| |
| server = git__calloc(1, sizeof(git_server)); |
| GITERR_CHECK_ALLOC(server); |
| |
| server->repo = repo; |
| server->s.socket = fd; |
| |
| *out = server; |
| return 0; |
| } |
| |
| void git_server_free(git_server *server) |
| { |
| if (server == NULL) |
| return; |
| |
| git_array_clear(server->wants); |
| git_array_clear(server->common); |
| git__free(server->path); |
| git__free(server); |
| } |
| |
| int git_server__handle_request(git_server *server, git_pkt *pkt) |
| { |
| git_pkt_request *req; |
| |
| if (pkt->type != GIT_PKT_REQUEST) { |
| giterr_set(GITERR_NET, "first line was not a request"); |
| return -1; |
| } |
| |
| req = (git_pkt_request *) pkt; |
| server->type = req->request; |
| server->path = git__strdup(req->path); |
| GITERR_CHECK_ALLOC(server->path); |
| |
| return 0; |
| } |
| |
| int git_server__ls(git_buf *out, git_server *server) |
| { |
| git_repository *repo = server->repo; |
| git_strarray ref_names = {0}; |
| git_reference *ref = NULL; |
| int error; |
| size_t i; |
| |
| assert(out && server); |
| |
| if (server->type != GIT_REQUEST_UPLOAD_PACK) { |
| giterr_set(GITERR_NET, "unsupported type"); |
| } |
| |
| if ((error = git_reference_list(&ref_names, repo)) < 0) |
| return error; |
| |
| /* the references need to be alphasorted */ |
| git__tsort((void **)ref_names.strings, ref_names.count, git__strcmp_cb); |
| |
| if ((error = git_reference_lookup(&ref, repo, "HEAD")) < 0) |
| goto cleanup; |
| |
| error = git_pkt_buffer_reference(out, ref); |
| git_reference_free(ref); |
| if (error < 0) |
| return error; |
| |
| for (i = 0; i < ref_names.count; i++) { |
| if ((error = git_reference_lookup(&ref, repo, ref_names.strings[i])) < 0) |
| goto cleanup; |
| |
| error = git_pkt_buffer_reference(out, ref); |
| git_reference_free(ref); |
| if (error < 0) |
| break; |
| } |
| |
| if (error < 0) |
| return -1; |
| |
| error = git_pkt_buffer_flush(out); |
| |
| cleanup: |
| git_strarray_free(&ref_names); |
| return error; |
| } |
| |
| int git_server__negotiation(git_server *server, git_pkt *_pkt) |
| { |
| git_oid *id, *have_id; |
| git_pkt_have_want *pkt; |
| git_odb *odb = NULL; |
| int error; |
| |
| if (_pkt->type != GIT_PKT_HAVE && _pkt->type != GIT_PKT_WANT) { |
| giterr_set(GITERR_NET, "invalid pkt for negotiation"); |
| return -1; |
| } |
| |
| pkt = (git_pkt_have_want *) _pkt; |
| |
| if (pkt->type == GIT_PKT_WANT) { |
| id = git_array_alloc(server->wants); |
| GITERR_CHECK_ALLOC(id); |
| |
| git_oid_cpy(id, &pkt->id); |
| return 0; |
| } |
| |
| /* we know it's a 'have', so we check to see if it's common */ |
| have_id = &pkt->id; |
| if ((error = git_repository_odb(&odb, server->repo)) < 0) |
| return error; |
| |
| if ((error = git_odb_exists(odb, have_id)) < 0) |
| goto cleanup; |
| |
| if (error == 1) { |
| error = 0; |
| id = git_array_alloc(server->common); |
| GITERR_CHECK_ALLOC(id); |
| |
| git_oid_cpy(id, &pkt->id); |
| } |
| |
| cleanup: |
| git_odb_free(odb); |
| |
| return error; |
| } |
| |
| int git_server_run(git_server *server) |
| { |
| /* 65535 is the max size of a pkt frame */ |
| char buffer[65536] = {0}; |
| gitno_buffer buf; |
| gitno_socket *s = &server->s; |
| int error; |
| |
| gitno_buffer_setup(s, &buf, buffer, sizeof(buffer)); |
| |
| /* first we figure out which service the user wants */ |
| while (1) { |
| git_pkt *pkt; |
| const char *rest; |
| |
| |
| if ((error = gitno_recv(&buf)) < 0) |
| return error; |
| |
| error = git_pkt_parse_line(&pkt, buffer, &rest, buf.len); |
| if (error == GIT_EBUFS) |
| continue; |
| |
| if (error < 0) |
| return error; |
| |
| gitno_consume(&buf, rest); |
| |
| error = git_server__handle_request(server, pkt); |
| git_pkt_free(pkt); |
| |
| if (error < 0) |
| return error; |
| |
| break; |
| } |
| |
| /* and now we let the server respond with the listing */ |
| |
| return 0; |
| } |