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