File Coverage

deps/libgit2/src/libgit2/transports/smart.c
Criterion Covered Total %
statement 0 241 0.0
branch 0 142 0.0
condition n/a
subroutine n/a
pod n/a
total 0 383 0.0


line stmt bran cond sub pod time code
1             /*
2             * Copyright (C) the libgit2 contributors. All rights reserved.
3             *
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.
6             */
7              
8             #include "smart.h"
9              
10             #include "git2.h"
11             #include "git2/sys/remote.h"
12             #include "refs.h"
13             #include "refspec.h"
14             #include "proxy.h"
15              
16 0           static int git_smart__recv_cb(gitno_buffer *buf)
17             {
18 0           transport_smart *t = (transport_smart *) buf->cb_data;
19             size_t old_len, bytes_read;
20             int error;
21              
22 0 0         GIT_ASSERT(t->current_stream);
23              
24 0           old_len = buf->offset;
25              
26 0 0         if ((error = t->current_stream->read(t->current_stream, buf->data + buf->offset, buf->len - buf->offset, &bytes_read)) < 0)
27 0           return error;
28              
29 0           buf->offset += bytes_read;
30              
31 0 0         if (t->packetsize_cb && !t->cancelled.val) {
    0          
32 0           error = t->packetsize_cb(bytes_read, t->packetsize_payload);
33 0 0         if (error) {
34 0           git_atomic32_set(&t->cancelled, 1);
35 0           return GIT_EUSER;
36             }
37             }
38              
39 0           return (int)(buf->offset - old_len);
40             }
41              
42 0           GIT_INLINE(int) git_smart__reset_stream(transport_smart *t, bool close_subtransport)
43             {
44 0 0         if (t->current_stream) {
45 0           t->current_stream->free(t->current_stream);
46 0           t->current_stream = NULL;
47             }
48              
49 0 0         if (close_subtransport) {
50 0           git__free(t->url);
51 0           t->url = NULL;
52              
53 0 0         if (t->wrapped->close(t->wrapped) < 0)
54 0           return -1;
55             }
56              
57 0           return 0;
58             }
59              
60 0           int git_smart__update_heads(transport_smart *t, git_vector *symrefs)
61             {
62             size_t i;
63             git_pkt *pkt;
64              
65 0           git_vector_clear(&t->heads);
66 0 0         git_vector_foreach(&t->refs, i, pkt) {
67 0           git_pkt_ref *ref = (git_pkt_ref *) pkt;
68 0 0         if (pkt->type != GIT_PKT_REF)
69 0           continue;
70              
71 0 0         if (symrefs) {
72             git_refspec *spec;
73 0           git_str buf = GIT_STR_INIT;
74             size_t j;
75 0           int error = 0;
76              
77 0 0         git_vector_foreach(symrefs, j, spec) {
78 0           git_str_clear(&buf);
79 0 0         if (git_refspec_src_matches(spec, ref->head.name) &&
    0          
80 0           !(error = git_refspec__transform(&buf, spec, ref->head.name))) {
81 0           git__free(ref->head.symref_target);
82 0           ref->head.symref_target = git_str_detach(&buf);
83             }
84             }
85              
86 0           git_str_dispose(&buf);
87              
88 0 0         if (error < 0)
89 0           return error;
90             }
91              
92 0 0         if (git_vector_insert(&t->heads, &ref->head) < 0)
93 0           return -1;
94             }
95              
96 0           return 0;
97             }
98              
99 0           static void free_symrefs(git_vector *symrefs)
100             {
101             git_refspec *spec;
102             size_t i;
103              
104 0 0         git_vector_foreach(symrefs, i, spec) {
105 0           git_refspec__dispose(spec);
106 0           git__free(spec);
107             }
108              
109 0           git_vector_free(symrefs);
110 0           }
111              
112 0           static int git_smart__connect(
113             git_transport *transport,
114             const char *url,
115             int direction,
116             const git_remote_connect_options *connect_opts)
117             {
118 0           transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
119             git_smart_subtransport_stream *stream;
120             int error;
121             git_pkt *pkt;
122             git_pkt_ref *first;
123             git_vector symrefs;
124             git_smart_service_t service;
125              
126 0 0         if (git_smart__reset_stream(t, true) < 0)
127 0           return -1;
128              
129 0 0         if (git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, connect_opts) < 0)
130 0           return -1;
131              
132 0           t->url = git__strdup(url);
133 0 0         GIT_ERROR_CHECK_ALLOC(t->url);
134              
135 0           t->direction = direction;
136              
137 0 0         if (GIT_DIRECTION_FETCH == t->direction) {
138 0           service = GIT_SERVICE_UPLOADPACK_LS;
139 0 0         } else if (GIT_DIRECTION_PUSH == t->direction) {
140 0           service = GIT_SERVICE_RECEIVEPACK_LS;
141             } else {
142 0           git_error_set(GIT_ERROR_NET, "invalid direction");
143 0           return -1;
144             }
145              
146 0 0         if ((error = t->wrapped->action(&stream, t->wrapped, t->url, service)) < 0)
147 0           return error;
148              
149             /* Save off the current stream (i.e. socket) that we are working with */
150 0           t->current_stream = stream;
151              
152 0           gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
153              
154             /* 2 flushes for RPC; 1 for stateful */
155 0 0         if ((error = git_smart__store_refs(t, t->rpc ? 2 : 1)) < 0)
    0          
156 0           return error;
157              
158             /* Strip the comment packet for RPC */
159 0 0         if (t->rpc) {
160 0           pkt = (git_pkt *)git_vector_get(&t->refs, 0);
161              
162 0 0         if (!pkt || GIT_PKT_COMMENT != pkt->type) {
    0          
163 0           git_error_set(GIT_ERROR_NET, "invalid response");
164 0           return -1;
165             } else {
166             /* Remove the comment pkt from the list */
167 0           git_vector_remove(&t->refs, 0);
168 0           git__free(pkt);
169             }
170             }
171              
172             /* We now have loaded the refs. */
173 0           t->have_refs = 1;
174              
175 0           pkt = (git_pkt *)git_vector_get(&t->refs, 0);
176 0 0         if (pkt && GIT_PKT_REF != pkt->type) {
    0          
177 0           git_error_set(GIT_ERROR_NET, "invalid response");
178 0           return -1;
179             }
180 0           first = (git_pkt_ref *)pkt;
181              
182 0 0         if ((error = git_vector_init(&symrefs, 1, NULL)) < 0)
183 0           return error;
184              
185             /* Detect capabilities */
186 0 0         if ((error = git_smart__detect_caps(first, &t->caps, &symrefs)) == 0) {
187             /* If the only ref in the list is capabilities^{} with OID_ZERO, remove it */
188 0 0         if (1 == t->refs.length && !strcmp(first->head.name, "capabilities^{}") &&
189 0           git_oid_is_zero(&first->head.oid)) {
190 0           git_vector_clear(&t->refs);
191 0           git_pkt_free((git_pkt *)first);
192             }
193              
194             /* Keep a list of heads for _ls */
195 0           git_smart__update_heads(t, &symrefs);
196 0 0         } else if (error == GIT_ENOTFOUND) {
197             /* There was no ref packet received, or the cap list was empty */
198 0           error = 0;
199             } else {
200 0           git_error_set(GIT_ERROR_NET, "invalid response");
201 0           goto cleanup;
202             }
203              
204 0 0         if (t->rpc && (error = git_smart__reset_stream(t, false)) < 0)
    0          
205 0           goto cleanup;
206              
207             /* We're now logically connected. */
208 0           t->connected = 1;
209              
210             cleanup:
211 0           free_symrefs(&symrefs);
212              
213 0           return error;
214             }
215              
216 0           static int git_smart__set_connect_opts(
217             git_transport *transport,
218             const git_remote_connect_options *opts)
219             {
220 0           transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
221              
222 0 0         if (!t->connected) {
223 0           git_error_set(GIT_ERROR_NET, "cannot reconfigure a transport that is not connected");
224 0           return -1;
225             }
226              
227 0           return git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, opts);
228             }
229              
230 0           static int git_smart__capabilities(unsigned int *capabilities, git_transport *transport)
231             {
232 0           transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
233              
234 0           *capabilities = 0;
235              
236 0 0         if (t->caps.want_tip_sha1)
237 0           *capabilities |= GIT_REMOTE_CAPABILITY_TIP_OID;
238              
239 0 0         if (t->caps.want_reachable_sha1)
240 0           *capabilities |= GIT_REMOTE_CAPABILITY_REACHABLE_OID;
241              
242 0           return 0;
243             }
244              
245 0           static int git_smart__ls(const git_remote_head ***out, size_t *size, git_transport *transport)
246             {
247 0           transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
248              
249 0 0         if (!t->have_refs) {
250 0           git_error_set(GIT_ERROR_NET, "the transport has not yet loaded the refs");
251 0           return -1;
252             }
253              
254 0           *out = (const git_remote_head **) t->heads.contents;
255 0           *size = t->heads.length;
256              
257 0           return 0;
258             }
259              
260 0           int git_smart__negotiation_step(git_transport *transport, void *data, size_t len)
261             {
262 0           transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
263             git_smart_subtransport_stream *stream;
264             int error;
265              
266 0 0         if (t->rpc && git_smart__reset_stream(t, false) < 0)
    0          
267 0           return -1;
268              
269 0 0         if (GIT_DIRECTION_FETCH != t->direction) {
270 0           git_error_set(GIT_ERROR_NET, "this operation is only valid for fetch");
271 0           return -1;
272             }
273              
274 0 0         if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0)
275 0           return error;
276              
277             /* If this is a stateful implementation, the stream we get back should be the same */
278 0 0         GIT_ASSERT(t->rpc || t->current_stream == stream);
    0          
279              
280             /* Save off the current stream (i.e. socket) that we are working with */
281 0           t->current_stream = stream;
282              
283 0 0         if ((error = stream->write(stream, (const char *)data, len)) < 0)
284 0           return error;
285              
286 0           gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
287              
288 0           return 0;
289             }
290              
291 0           int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **stream)
292             {
293             int error;
294              
295 0 0         if (t->rpc && git_smart__reset_stream(t, false) < 0)
    0          
296 0           return -1;
297              
298 0 0         if (GIT_DIRECTION_PUSH != t->direction) {
299 0           git_error_set(GIT_ERROR_NET, "this operation is only valid for push");
300 0           return -1;
301             }
302              
303 0 0         if ((error = t->wrapped->action(stream, t->wrapped, t->url, GIT_SERVICE_RECEIVEPACK)) < 0)
304 0           return error;
305              
306             /* If this is a stateful implementation, the stream we get back should be the same */
307 0 0         GIT_ASSERT(t->rpc || t->current_stream == *stream);
    0          
308              
309             /* Save off the current stream (i.e. socket) that we are working with */
310 0           t->current_stream = *stream;
311              
312 0           gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
313              
314 0           return 0;
315             }
316              
317 0           static void git_smart__cancel(git_transport *transport)
318             {
319 0           transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
320              
321 0           git_atomic32_set(&t->cancelled, 1);
322 0           }
323              
324 0           static int git_smart__is_connected(git_transport *transport)
325             {
326 0           transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
327              
328 0           return t->connected;
329             }
330              
331 0           static int git_smart__close(git_transport *transport)
332             {
333 0           transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
334 0           git_vector *common = &t->common;
335             unsigned int i;
336             git_pkt *p;
337             int ret;
338             git_smart_subtransport_stream *stream;
339 0           const char flush[] = "0000";
340              
341             /*
342             * If we're still connected at this point and not using RPC,
343             * we should say goodbye by sending a flush, or git-daemon
344             * will complain that we disconnected unexpectedly.
345             */
346 0 0         if (t->connected && !t->rpc &&
347 0           !t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) {
348 0           t->current_stream->write(t->current_stream, flush, 4);
349             }
350              
351 0           ret = git_smart__reset_stream(t, true);
352              
353 0 0         git_vector_foreach(common, i, p)
354 0           git_pkt_free(p);
355              
356 0           git_vector_free(common);
357              
358 0 0         if (t->url) {
359 0           git__free(t->url);
360 0           t->url = NULL;
361             }
362              
363 0           t->connected = 0;
364              
365 0           return ret;
366             }
367              
368 0           static void git_smart__free(git_transport *transport)
369             {
370 0           transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
371 0           git_vector *refs = &t->refs;
372             unsigned int i;
373             git_pkt *p;
374              
375             /* Make sure that the current stream is closed, if we have one. */
376 0           git_smart__close(transport);
377              
378             /* Free the subtransport */
379 0           t->wrapped->free(t->wrapped);
380              
381 0           git_vector_free(&t->heads);
382 0 0         git_vector_foreach(refs, i, p)
383 0           git_pkt_free(p);
384              
385 0           git_vector_free(refs);
386              
387 0           git_remote_connect_options_dispose(&t->connect_opts);
388              
389 0           git__free(t);
390 0           }
391              
392 0           static int ref_name_cmp(const void *a, const void *b)
393             {
394 0           const git_pkt_ref *ref_a = a, *ref_b = b;
395              
396 0           return strcmp(ref_a->head.name, ref_b->head.name);
397             }
398              
399 0           int git_transport_smart_certificate_check(git_transport *transport, git_cert *cert, int valid, const char *hostname)
400             {
401 0           transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
402 0           git_remote_connect_options *connect_opts = &t->connect_opts;
403              
404 0 0         GIT_ASSERT_ARG(transport);
405 0 0         GIT_ASSERT_ARG(cert);
406 0 0         GIT_ASSERT_ARG(hostname);
407              
408 0 0         if (!connect_opts->callbacks.certificate_check)
409 0           return GIT_PASSTHROUGH;
410              
411 0           return connect_opts->callbacks.certificate_check(cert, valid, hostname, connect_opts->callbacks.payload);
412             }
413              
414 0           int git_transport_smart_credentials(git_credential **out, git_transport *transport, const char *user, int methods)
415             {
416 0           transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
417 0           git_remote_connect_options *connect_opts = &t->connect_opts;
418              
419 0 0         GIT_ASSERT_ARG(out);
420 0 0         GIT_ASSERT_ARG(transport);
421              
422 0 0         if (!connect_opts->callbacks.credentials)
423 0           return GIT_PASSTHROUGH;
424              
425 0           return connect_opts->callbacks.credentials(out, t->url, user, methods, connect_opts->callbacks.payload);
426             }
427              
428 0           int git_transport_remote_connect_options(
429             git_remote_connect_options *out,
430             git_transport *transport)
431             {
432 0           transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
433              
434 0 0         GIT_ASSERT_ARG(out);
435 0 0         GIT_ASSERT_ARG(transport);
436              
437 0           return git_remote_connect_options_dup(out, &t->connect_opts);
438             }
439              
440 0           int git_transport_smart(git_transport **out, git_remote *owner, void *param)
441             {
442             transport_smart *t;
443 0           git_smart_subtransport_definition *definition = (git_smart_subtransport_definition *)param;
444              
445 0 0         if (!param)
446 0           return -1;
447              
448 0           t = git__calloc(1, sizeof(transport_smart));
449 0 0         GIT_ERROR_CHECK_ALLOC(t);
450              
451 0           t->parent.version = GIT_TRANSPORT_VERSION;
452 0           t->parent.connect = git_smart__connect;
453 0           t->parent.set_connect_opts = git_smart__set_connect_opts;
454 0           t->parent.capabilities = git_smart__capabilities;
455 0           t->parent.close = git_smart__close;
456 0           t->parent.free = git_smart__free;
457 0           t->parent.negotiate_fetch = git_smart__negotiate_fetch;
458 0           t->parent.download_pack = git_smart__download_pack;
459 0           t->parent.push = git_smart__push;
460 0           t->parent.ls = git_smart__ls;
461 0           t->parent.is_connected = git_smart__is_connected;
462 0           t->parent.cancel = git_smart__cancel;
463              
464 0           t->owner = owner;
465 0           t->rpc = definition->rpc;
466              
467 0 0         if (git_vector_init(&t->refs, 16, ref_name_cmp) < 0) {
468 0           git__free(t);
469 0           return -1;
470             }
471              
472 0 0         if (git_vector_init(&t->heads, 16, ref_name_cmp) < 0) {
473 0           git__free(t);
474 0           return -1;
475             }
476              
477 0 0         if (definition->callback(&t->wrapped, &t->parent, definition->param) < 0) {
478 0           git__free(t);
479 0           return -1;
480             }
481              
482 0           *out = (git_transport *) t;
483 0           return 0;
484             }