blob: 76672679865fed37d2c8d1687318a298938aab54 [file] [log] [blame]
/*
* 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;
}