]>
Commit | Line | Data |
---|---|---|
f7fc68df | 1 | /* |
bb742ede | 2 | * Copyright (C) 2009-2011 the libgit2 contributors |
f7fc68df | 3 | * |
bb742ede VM |
4 | * This file is part of libgit2, distributed under the GNU GPL v2 with |
5 | * a Linking Exception. For full terms see the included COPYING file. | |
f7fc68df CMN |
6 | */ |
7 | ||
bdd18829 VM |
8 | #include "common.h" |
9 | ||
f7fc68df CMN |
10 | #include "git2/types.h" |
11 | #include "git2/errors.h" | |
b4c90630 CMN |
12 | #include "git2/refs.h" |
13 | #include "git2/revwalk.h" | |
f7fc68df | 14 | |
bdd18829 | 15 | #include "pkt.h" |
f7fc68df | 16 | #include "util.h" |
c4d0fa85 | 17 | #include "netops.h" |
84dd3820 | 18 | #include "posix.h" |
34bfb4b0 | 19 | #include "buffer.h" |
f7fc68df | 20 | |
7632e249 CMN |
21 | #include <ctype.h> |
22 | ||
23 | #define PKT_LEN_SIZE 4 | |
24 | ||
1d27446c CMN |
25 | static int flush_pkt(git_pkt **out) |
26 | { | |
27 | git_pkt *pkt; | |
28 | ||
29 | pkt = git__malloc(sizeof(git_pkt)); | |
30 | if (pkt == NULL) | |
31 | return GIT_ENOMEM; | |
32 | ||
33 | pkt->type = GIT_PKT_FLUSH; | |
34 | *out = pkt; | |
35 | ||
36 | return GIT_SUCCESS; | |
37 | } | |
38 | ||
da290220 CMN |
39 | /* the rest of the line will be useful for multi_ack */ |
40 | static int ack_pkt(git_pkt **out, const char *GIT_UNUSED(line), size_t GIT_UNUSED(len)) | |
7e1a94db CMN |
41 | { |
42 | git_pkt *pkt; | |
da290220 CMN |
43 | GIT_UNUSED_ARG(line); |
44 | GIT_UNUSED_ARG(len); | |
7e1a94db CMN |
45 | |
46 | pkt = git__malloc(sizeof(git_pkt)); | |
47 | if (pkt == NULL) | |
48 | return GIT_ENOMEM; | |
49 | ||
50 | pkt->type = GIT_PKT_ACK; | |
51 | *out = pkt; | |
52 | ||
53 | return GIT_SUCCESS; | |
54 | } | |
55 | ||
da290220 | 56 | static int nak_pkt(git_pkt **out) |
7e1a94db CMN |
57 | { |
58 | git_pkt *pkt; | |
59 | ||
60 | pkt = git__malloc(sizeof(git_pkt)); | |
61 | if (pkt == NULL) | |
62 | return GIT_ENOMEM; | |
63 | ||
da290220 CMN |
64 | pkt->type = GIT_PKT_NAK; |
65 | *out = pkt; | |
66 | ||
67 | return GIT_SUCCESS; | |
68 | } | |
69 | ||
70 | static int pack_pkt(git_pkt **out) | |
71 | { | |
72 | git_pkt *pkt; | |
73 | ||
74 | pkt = git__malloc(sizeof(git_pkt)); | |
75 | if (pkt == NULL) | |
76 | return GIT_ENOMEM; | |
77 | ||
78 | pkt->type = GIT_PKT_PACK; | |
7e1a94db CMN |
79 | *out = pkt; |
80 | ||
81 | return GIT_SUCCESS; | |
82 | } | |
83 | ||
b76f7522 CMN |
84 | static int comment_pkt(git_pkt **out, const char *line, size_t len) |
85 | { | |
86 | git_pkt_comment *pkt; | |
87 | ||
88 | pkt = git__malloc(sizeof(git_pkt_comment) + len + 1); | |
89 | if (pkt == NULL) | |
90 | return GIT_ENOMEM; | |
91 | ||
92 | pkt->type = GIT_PKT_COMMENT; | |
93 | memcpy(pkt->comment, line, len); | |
94 | pkt->comment[len] = '\0'; | |
95 | ||
96 | *out = (git_pkt *) pkt; | |
97 | ||
98 | return GIT_SUCCESS; | |
99 | } | |
100 | ||
b31803f3 CMN |
101 | /* |
102 | * Parse an other-ref line. | |
103 | */ | |
7e1a94db | 104 | static int ref_pkt(git_pkt **out, const char *line, size_t len) |
b31803f3 CMN |
105 | { |
106 | git_pkt_ref *pkt; | |
76a9081d | 107 | int error; |
b31803f3 CMN |
108 | |
109 | pkt = git__malloc(sizeof(git_pkt_ref)); | |
110 | if (pkt == NULL) | |
111 | return GIT_ENOMEM; | |
112 | ||
8b9e8de5 | 113 | memset(pkt, 0x0, sizeof(git_pkt_ref)); |
b31803f3 CMN |
114 | pkt->type = GIT_PKT_REF; |
115 | error = git_oid_fromstr(&pkt->head.oid, line); | |
116 | if (error < GIT_SUCCESS) { | |
117 | error = git__throw(error, "Failed to parse reference ID"); | |
118 | goto out; | |
119 | } | |
120 | ||
121 | /* Check for a bit of consistency */ | |
122 | if (line[GIT_OID_HEXSZ] != ' ') { | |
123 | error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse ref. No SP"); | |
124 | goto out; | |
125 | } | |
126 | ||
8b9e8de5 | 127 | /* Jump from the name */ |
b31803f3 | 128 | line += GIT_OID_HEXSZ + 1; |
8b9e8de5 | 129 | len -= (GIT_OID_HEXSZ + 1); |
b31803f3 | 130 | |
7632e249 CMN |
131 | if (line[len - 1] == '\n') |
132 | --len; | |
b31803f3 | 133 | |
7632e249 | 134 | pkt->head.name = git__malloc(len + 1); |
b31803f3 CMN |
135 | if (pkt->head.name == NULL) { |
136 | error = GIT_ENOMEM; | |
137 | goto out; | |
138 | } | |
7632e249 CMN |
139 | memcpy(pkt->head.name, line, len); |
140 | pkt->head.name[len] = '\0'; | |
b31803f3 | 141 | |
76a9081d | 142 | if (strlen(pkt->head.name) < len) { |
7632e249 | 143 | pkt->capabilities = strchr(pkt->head.name, '\0') + 1; |
8b9e8de5 CMN |
144 | } |
145 | ||
b31803f3 CMN |
146 | out: |
147 | if (error < GIT_SUCCESS) | |
148 | free(pkt); | |
149 | else | |
150 | *out = (git_pkt *)pkt; | |
151 | ||
152 | return error; | |
153 | } | |
154 | ||
cbf742ac | 155 | static ssize_t parse_len(const char *line) |
7632e249 CMN |
156 | { |
157 | char num[PKT_LEN_SIZE + 1]; | |
158 | int i, error; | |
ad196c6a | 159 | int len; |
7632e249 CMN |
160 | const char *num_end; |
161 | ||
162 | memcpy(num, line, PKT_LEN_SIZE); | |
163 | num[PKT_LEN_SIZE] = '\0'; | |
164 | ||
165 | for (i = 0; i < PKT_LEN_SIZE; ++i) { | |
166 | if (!isxdigit(num[i])) | |
167 | return GIT_ENOTNUM; | |
168 | } | |
169 | ||
170 | error = git__strtol32(&len, num, &num_end, 16); | |
171 | if (error < GIT_SUCCESS) { | |
172 | return error; | |
173 | } | |
174 | ||
175 | return (unsigned int) len; | |
176 | } | |
177 | ||
f7fc68df CMN |
178 | /* |
179 | * As per the documentation, the syntax is: | |
180 | * | |
87d9869f VM |
181 | * pkt-line = data-pkt / flush-pkt |
182 | * data-pkt = pkt-len pkt-payload | |
183 | * pkt-len = 4*(HEXDIG) | |
f7fc68df | 184 | * pkt-payload = (pkt-len -4)*(OCTET) |
87d9869f | 185 | * flush-pkt = "0000" |
f7fc68df CMN |
186 | * |
187 | * Which means that the first four bytes are the length of the line, | |
188 | * in ASCII hexadecimal (including itself) | |
189 | */ | |
190 | ||
cbf742ac | 191 | int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t bufflen) |
f7fc68df CMN |
192 | { |
193 | int error = GIT_SUCCESS; | |
cbf742ac | 194 | size_t len; |
f7fc68df | 195 | |
7632e249 CMN |
196 | /* Not even enough for the length */ |
197 | if (bufflen > 0 && bufflen < PKT_LEN_SIZE) | |
198 | return GIT_ESHORTBUFFER; | |
78fae478 | 199 | |
7632e249 | 200 | error = parse_len(line); |
c7c787ce | 201 | if (error < GIT_SUCCESS) { |
da290220 CMN |
202 | /* |
203 | * If we fail to parse the length, it might be because the | |
204 | * server is trying to send us the packfile already. | |
205 | */ | |
206 | if (bufflen >= 4 && !git__prefixcmp(line, "PACK")) { | |
207 | *out = line; | |
208 | return pack_pkt(head); | |
209 | } | |
210 | ||
b31803f3 | 211 | return git__throw(error, "Failed to parse pkt length"); |
c7c787ce | 212 | } |
f7fc68df | 213 | |
7632e249 CMN |
214 | len = error; |
215 | ||
216 | /* | |
217 | * If we were given a buffer length, then make sure there is | |
218 | * enough in the buffer to satisfy this line | |
219 | */ | |
220 | if (bufflen > 0 && bufflen < len) | |
221 | return GIT_ESHORTBUFFER; | |
222 | ||
223 | line += PKT_LEN_SIZE; | |
f7fc68df CMN |
224 | /* |
225 | * TODO: How do we deal with empty lines? Try again? with the next | |
226 | * line? | |
227 | */ | |
7632e249 | 228 | if (len == PKT_LEN_SIZE) { |
1d27446c | 229 | *out = line; |
f7fc68df CMN |
230 | return GIT_SUCCESS; |
231 | } | |
232 | ||
1d27446c CMN |
233 | if (len == 0) { /* Flush pkt */ |
234 | *out = line; | |
235 | return flush_pkt(head); | |
f7fc68df CMN |
236 | } |
237 | ||
7632e249 | 238 | len -= PKT_LEN_SIZE; /* the encoded length includes its own size */ |
f7fc68df | 239 | |
7e1a94db CMN |
240 | /* Assming the minimal size is actually 4 */ |
241 | if (!git__prefixcmp(line, "ACK")) | |
242 | error = ack_pkt(head, line, len); | |
da290220 CMN |
243 | else if (!git__prefixcmp(line, "NAK")) |
244 | error = nak_pkt(head); | |
b76f7522 CMN |
245 | else if (*line == '#') |
246 | error = comment_pkt(head, line, len); | |
7e1a94db CMN |
247 | else |
248 | error = ref_pkt(head, line, len); | |
b31803f3 | 249 | |
b31803f3 CMN |
250 | *out = line + len; |
251 | ||
252 | return error; | |
f7fc68df | 253 | } |
6a9597c5 | 254 | |
be9fe679 CMN |
255 | void git_pkt_free(git_pkt *pkt) |
256 | { | |
257 | if(pkt->type == GIT_PKT_REF) { | |
258 | git_pkt_ref *p = (git_pkt_ref *) pkt; | |
be9fe679 CMN |
259 | free(p->head.name); |
260 | } | |
261 | ||
262 | free(pkt); | |
263 | } | |
264 | ||
34bfb4b0 | 265 | int git_pkt_send_flush(int s, int chunked) |
c4d0fa85 CMN |
266 | { |
267 | char flush[] = "0000"; | |
34bfb4b0 | 268 | int error; |
c4d0fa85 | 269 | |
34bfb4b0 CMN |
270 | if (chunked) { |
271 | error = gitno_send_chunk_size(s, strlen(flush)); | |
272 | if (error < GIT_SUCCESS) | |
273 | return git__rethrow(error, "Failed to send chunk size"); | |
274 | } | |
932669b8 | 275 | return gitno_send(s, flush, strlen(flush), 0); |
c4d0fa85 | 276 | } |
0132cf64 | 277 | |
34bfb4b0 | 278 | static int send_want_with_caps(git_remote_head *head, git_transport_caps *caps, int fd, int chunked) |
0437d991 CMN |
279 | { |
280 | char capstr[20]; /* Longer than we need */ | |
281 | char oid[GIT_OID_HEXSZ +1] = {0}, *cmd; | |
282 | int error, len; | |
34bfb4b0 | 283 | git_buf buf = GIT_BUF_INIT; |
0437d991 CMN |
284 | |
285 | if (caps->ofs_delta) | |
286 | strcpy(capstr, GIT_CAP_OFS_DELTA); | |
287 | ||
932669b8 | 288 | len = strlen("XXXXwant ") + GIT_OID_HEXSZ + 1 /* NUL */ + strlen(capstr) + 1 /* LF */; |
0437d991 CMN |
289 | cmd = git__malloc(len + 1); |
290 | if (cmd == NULL) | |
291 | return GIT_ENOMEM; | |
292 | ||
293 | git_oid_fmt(oid, &head->oid); | |
34bfb4b0 CMN |
294 | git_buf_printf(&buf, "%04xwant %s%c%s\n", len, oid, 0, capstr); |
295 | if (chunked) { | |
296 | error = gitno_send_chunk_size(fd, len); | |
297 | if (error < GIT_SUCCESS) | |
298 | return git__rethrow(error, "Failed to send first want chunk size"); | |
299 | } | |
300 | error = gitno_send(fd, git_buf_cstr(&buf), len, 0); | |
0437d991 CMN |
301 | free(cmd); |
302 | return error; | |
303 | } | |
304 | ||
0132cf64 CMN |
305 | /* |
306 | * All "want" packets have the same length and format, so what we do | |
307 | * is overwrite the OID each time. | |
308 | */ | |
309 | #define WANT_PREFIX "0032want " | |
310 | ||
34bfb4b0 | 311 | int git_pkt_send_wants(git_headarray *refs, git_transport_caps *caps, int fd, int chunked) |
0132cf64 | 312 | { |
427ca3d3 CMN |
313 | unsigned int i = 0; |
314 | int error = GIT_SUCCESS; | |
85b91652 | 315 | char buf[sizeof(WANT_PREFIX) + GIT_OID_HEXSZ + 1]; |
0132cf64 CMN |
316 | git_remote_head *head; |
317 | ||
932669b8 | 318 | memcpy(buf, WANT_PREFIX, strlen(WANT_PREFIX)); |
0132cf64 CMN |
319 | buf[sizeof(buf) - 2] = '\n'; |
320 | buf[sizeof(buf) - 1] = '\0'; | |
321 | ||
427ca3d3 CMN |
322 | /* If there are common caps, find the first one */ |
323 | if (caps->common) { | |
324 | for (; i < refs->len; ++i) { | |
325 | head = refs->heads[i]; | |
326 | if (head->local) | |
327 | continue; | |
328 | else | |
329 | break; | |
330 | } | |
331 | ||
34bfb4b0 | 332 | error = send_want_with_caps(refs->heads[i], caps, fd, chunked); |
427ca3d3 CMN |
333 | if (error < GIT_SUCCESS) |
334 | return git__rethrow(error, "Failed to send want pkt with caps"); | |
335 | /* Increase it here so it's correct whether we run this or not */ | |
336 | i++; | |
0437d991 CMN |
337 | } |
338 | ||
427ca3d3 | 339 | /* Continue from where we left off */ |
0437d991 | 340 | for (; i < refs->len; ++i) { |
0132cf64 | 341 | head = refs->heads[i]; |
1564db11 CMN |
342 | if (head->local) |
343 | continue; | |
b4c90630 | 344 | |
932669b8 | 345 | git_oid_fmt(buf + strlen(WANT_PREFIX), &head->oid); |
34bfb4b0 CMN |
346 | if (chunked) { |
347 | error = gitno_send_chunk_size(fd, strlen(buf)); | |
348 | if (error < GIT_SUCCESS) | |
349 | return git__rethrow(error, "Failed to send want chunk size"); | |
350 | } | |
932669b8 | 351 | error = gitno_send(fd, buf, strlen(buf), 0); |
34bfb4b0 | 352 | if (error < GIT_SUCCESS) |
922bc225 | 353 | return git__rethrow(error, "Failed to send want pkt"); |
b4c90630 CMN |
354 | } |
355 | ||
34bfb4b0 | 356 | return git_pkt_send_flush(fd, chunked); |
b4c90630 CMN |
357 | } |
358 | ||
7e1a94db CMN |
359 | /* |
360 | * TODO: this should be a more generic function, maybe to be used by | |
361 | * git_pkt_send_wants, as it's not performance-critical | |
362 | */ | |
b4c90630 CMN |
363 | #define HAVE_PREFIX "0032have " |
364 | ||
34bfb4b0 | 365 | int git_pkt_send_have(git_oid *oid, int fd, int chunked) |
b4c90630 | 366 | { |
7e1a94db | 367 | char buf[] = "0032have 0000000000000000000000000000000000000000\n"; |
34bfb4b0 | 368 | int error; |
b4c90630 | 369 | |
34bfb4b0 CMN |
370 | if (chunked) { |
371 | error = gitno_send_chunk_size(fd, strlen(buf)); | |
372 | if (error < GIT_SUCCESS) | |
373 | return git__rethrow(error, "Failed to send chunk size"); | |
374 | } | |
932669b8 KS |
375 | git_oid_fmt(buf + strlen(HAVE_PREFIX), oid); |
376 | return gitno_send(fd, buf, strlen(buf), 0); | |
7e1a94db | 377 | } |
b4c90630 | 378 | |
34bfb4b0 | 379 | int git_pkt_send_done(int fd, int chunked) |
7e1a94db CMN |
380 | { |
381 | char buf[] = "0009done\n"; | |
34bfb4b0 | 382 | int error; |
b4c90630 | 383 | |
34bfb4b0 CMN |
384 | if (chunked) { |
385 | error = gitno_send_chunk_size(fd, strlen(buf)); | |
386 | if (error < GIT_SUCCESS) | |
387 | return git__rethrow(error, "Failed to send chunk size"); | |
388 | } | |
932669b8 | 389 | return gitno_send(fd, buf, strlen(buf), 0); |
0132cf64 | 390 | } |