diff --git a/include/git2/errors.h b/include/git2/errors.h index 334b9edab..03c74e822 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -128,6 +128,9 @@ typedef enum { /** The path pattern and string did not match */ GIT_ENOMATCH = -31, + + /** The buffer is too short to satisfy the request */ + GIT_ESHORTBUFFER = -32, } git_error; /** diff --git a/include/git2/pkt.h b/include/git2/pkt.h index d0ffa5569..79c582828 100644 --- a/include/git2/pkt.h +++ b/include/git2/pkt.h @@ -55,5 +55,5 @@ struct git_pkt_ref { * Create a git protocol request. */ int git_pkt_gen_proto(char **out, int *outlen, const char *url); -int git_pkt_parse_line(git_pkt **head, const char *line, const char **out); +int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, unsigned int len); void git_pkt_free(git_pkt *pkt); diff --git a/src/pkt.c b/src/pkt.c index f77bc3046..17edf320a 100644 --- a/src/pkt.c +++ b/src/pkt.c @@ -30,6 +30,10 @@ #include "common.h" #include "util.h" +#include + +#define PKT_LEN_SIZE 4 + static int flush_pkt(git_pkt **out) { git_pkt *pkt; @@ -50,8 +54,7 @@ static int flush_pkt(git_pkt **out) int ref_pkt(git_pkt **out, const char *line, size_t len) { git_pkt_ref *pkt; - int error; - size_t name_len; + int error, has_caps = 0; pkt = git__malloc(sizeof(git_pkt_ref)); if (pkt == NULL) @@ -75,30 +78,22 @@ int ref_pkt(git_pkt **out, const char *line, size_t len) line += GIT_OID_HEXSZ + 1; len -= (GIT_OID_HEXSZ + 1); - name_len = min(strlen(line), len); - if (line[name_len - 1] == '\n') - --name_len; + if (strlen(line) < len) + has_caps = 1; - pkt->head.name = git__strndup(line, name_len); + if (line[len - 1] == '\n') + --len; + + pkt->head.name = git__malloc(len + 1); if (pkt->head.name == NULL) { error = GIT_ENOMEM; goto out; } + memcpy(pkt->head.name, line, len); + pkt->head.name[len] = '\0'; - /* Try to get the capabilities */ - line += name_len + 1; /* + \0 */ - len -= (name_len + 1); - if (line[len - 1] == '\n') - --len; - - if (len > 0) { /* capatilities */ - pkt->capabilities = git__malloc(len); - if (pkt->capabilities == NULL) { - error = GIT_ENOMEM; - goto out; - } - - memcpy(pkt->capabilities, line, len); + if (has_caps) { + pkt->capabilities = strchr(pkt->head.name, '\0') + 1; } out: @@ -110,6 +105,29 @@ out: return error; } +static unsigned int parse_len(const char *line) +{ + char num[PKT_LEN_SIZE + 1]; + int i, error; + long len; + const char *num_end; + + memcpy(num, line, PKT_LEN_SIZE); + num[PKT_LEN_SIZE] = '\0'; + + for (i = 0; i < PKT_LEN_SIZE; ++i) { + if (!isxdigit(num[i])) + return GIT_ENOTNUM; + } + + error = git__strtol32(&len, num, &num_end, 16); + if (error < GIT_SUCCESS) { + return error; + } + + return (unsigned int) len; +} + /* * As per the documentation, the syntax is: * @@ -123,35 +141,34 @@ out: * in ASCII hexadecimal (including itself) */ -int git_pkt_parse_line(git_pkt **head, const char *line, const char **out) +int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, unsigned int bufflen) { int error = GIT_SUCCESS; - long int len; - const int num_len = 4; - char *num; - const char *num_end; + unsigned int len; - num = git__strndup(line, num_len); - if (num == NULL) - return GIT_ENOMEM; + /* Not even enough for the length */ + if (bufflen > 0 && bufflen < PKT_LEN_SIZE) + return GIT_ESHORTBUFFER; - error = git__strtol32(&len, num, &num_end, 16); - if (error < GIT_SUCCESS) { - free(num); + error = parse_len(line); + if (error < GIT_SUCCESS) return git__throw(error, "Failed to parse pkt length"); - } - if (num_end - num != num_len) { - free(num); - return git__throw(GIT_EOBJCORRUPTED, "Wrong pkt length"); - } - free(num); - line += num_len; + len = error; + + /* + * If we were given a buffer length, then make sure there is + * enough in the buffer to satisfy this line + */ + if (bufflen > 0 && bufflen < len) + return GIT_ESHORTBUFFER; + + line += PKT_LEN_SIZE; /* * TODO: How do we deal with empty lines? Try again? with the next * line? */ - if (len == 4) { + if (len == PKT_LEN_SIZE) { *out = line; return GIT_SUCCESS; } @@ -161,7 +178,7 @@ int git_pkt_parse_line(git_pkt **head, const char *line, const char **out) return flush_pkt(head); } - len -= num_len; /* the length includes the space for the length */ + len -= PKT_LEN_SIZE; /* the encoded length includes its own size */ /* * For now, we're just going to assume we're parsing references @@ -177,7 +194,6 @@ void git_pkt_free(git_pkt *pkt) { if(pkt->type == GIT_PKT_REF) { git_pkt_ref *p = (git_pkt_ref *) pkt; - free(p->capabilities); free(p->head.name); } diff --git a/src/transport_git.c b/src/transport_git.c index ed7203137..e5c7b1dc4 100644 --- a/src/transport_git.c +++ b/src/transport_git.c @@ -157,46 +157,58 @@ static int store_refs(git_priv *priv) int s = priv->socket; git_vector *refs = &priv->refs; int error = GIT_SUCCESS; - char buffer[1024] = {0}; + char buffer[1024]; const char *line_end, *ptr; int off = 0, ret; + unsigned int bufflen = 0; git_pkt *pkt; + memset(buffer, 0x0, sizeof(buffer)); + while (1) { - ret = recv(s, buffer, sizeof(buffer) - off, 0); + ret = recv(s, buffer + off, sizeof(buffer) - off, 0); if (ret < 0) return git__throw(GIT_EOSERR, "Failed to receive data"); - if (ret == 0) - puts("got zero!"); + if (ret == 0) /* Orderly shutdown, so exit */ + return GIT_SUCCESS; + bufflen += ret; ptr = buffer; while (1) { - error = git_pkt_parse_line(&pkt, ptr, &line_end); - /* - * An error here can mean that the input in corrupt or - * (more likely) that the input hasn't arrived yet. - * - * FIXME: Add actual error checking. Right now we just ask - * for more input. - */ - if (error < GIT_SUCCESS) + if (bufflen == 0) break; + error = git_pkt_parse_line(&pkt, ptr, &line_end, bufflen); + /* + * If the error is GIT_ESHORTBUFFER, it means the buffer + * isn't long enough to satisfy the request. Break out and + * wait for more input. + * On any other error, fail. + */ + if (error == GIT_ESHORTBUFFER) { + line_end = ptr; + break; + } + if (error < GIT_SUCCESS) { + return error; + } error = git_vector_insert(refs, pkt); if (error < GIT_SUCCESS) return error; - if (pkt->type != GIT_PKT_REF) + if (pkt->type == GIT_PKT_FLUSH) return GIT_SUCCESS; + bufflen -= line_end - ptr; ptr = line_end; } /* * Move the rest to the start of the buffer */ - memmove(buffer, line_end, sizeof(buffer) - (line_end - buffer)); - off = ret - (line_end - buffer); + memmove(buffer, line_end, bufflen); + off = bufflen; + memset(buffer + off, 0x0, sizeof(buffer) - off); } return error; @@ -271,6 +283,8 @@ static int git_close(git_transport *transport) int s = priv->socket; int error; + /* FIXME: We probably want to send a flush pkt back */ + error = close(s); if (error < 0) error = git__throw(GIT_EOSERR, "Failed to close socket");