File Coverage

deps/libgit2/src/transports/smart.c
Criterion Covered Total %
statement 0 272 0.0
branch 0 154 0.0
condition n/a
subroutine n/a
pod n/a
total 0 426 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 "refs.h"
12             #include "refspec.h"
13             #include "proxy.h"
14              
15 0           static int git_smart__recv_cb(gitno_buffer *buf)
16             {
17 0           transport_smart *t = (transport_smart *) buf->cb_data;
18             size_t old_len, bytes_read;
19             int error;
20              
21 0 0         assert(t->current_stream);
22              
23 0           old_len = buf->offset;
24              
25 0 0         if ((error = t->current_stream->read(t->current_stream, buf->data + buf->offset, buf->len - buf->offset, &bytes_read)) < 0)
26 0           return error;
27              
28 0           buf->offset += bytes_read;
29              
30 0 0         if (t->packetsize_cb && !t->cancelled.val) {
    0          
31 0           error = t->packetsize_cb(bytes_read, t->packetsize_payload);
32 0 0         if (error) {
33 0           git_atomic_set(&t->cancelled, 1);
34 0           return GIT_EUSER;
35             }
36             }
37              
38 0           return (int)(buf->offset - old_len);
39             }
40              
41 0           GIT_INLINE(int) git_smart__reset_stream(transport_smart *t, bool close_subtransport)
42             {
43 0 0         if (t->current_stream) {
44 0           t->current_stream->free(t->current_stream);
45 0           t->current_stream = NULL;
46             }
47              
48 0 0         if (close_subtransport) {
49 0           git__free(t->url);
50 0           t->url = NULL;
51              
52 0 0         if (t->wrapped->close(t->wrapped) < 0)
53 0           return -1;
54             }
55              
56 0           return 0;
57             }
58              
59 0           static int git_smart__set_callbacks(
60             git_transport *transport,
61             git_transport_message_cb progress_cb,
62             git_transport_message_cb error_cb,
63             git_transport_certificate_check_cb certificate_check_cb,
64             void *message_cb_payload)
65             {
66 0           transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
67              
68 0           t->progress_cb = progress_cb;
69 0           t->error_cb = error_cb;
70 0           t->certificate_check_cb = certificate_check_cb;
71 0           t->message_cb_payload = message_cb_payload;
72              
73 0           return 0;
74             }
75              
76 0           static size_t http_header_name_length(const char *http_header)
77             {
78 0           const char *colon = strchr(http_header, ':');
79 0 0         if (!colon)
80 0           return 0;
81 0           return colon - http_header;
82             }
83              
84 0           static bool is_malformed_http_header(const char *http_header)
85             {
86             const char *c;
87             size_t name_len;
88              
89             /* Disallow \r and \n */
90 0           c = strchr(http_header, '\r');
91 0 0         if (c)
92 0           return true;
93 0           c = strchr(http_header, '\n');
94 0 0         if (c)
95 0           return true;
96              
97             /* Require a header name followed by : */
98 0           name_len = http_header_name_length(http_header);
99 0 0         if (name_len < 1)
100 0           return true;
101              
102 0           return false;
103             }
104              
105             static char *forbidden_custom_headers[] = {
106             "User-Agent",
107             "Host",
108             "Accept",
109             "Content-Type",
110             "Transfer-Encoding",
111             "Content-Length",
112             };
113              
114 0           static bool is_forbidden_custom_header(const char *custom_header)
115             {
116             unsigned long i;
117 0           size_t name_len = http_header_name_length(custom_header);
118              
119             /* Disallow headers that we set */
120 0 0         for (i = 0; i < ARRAY_SIZE(forbidden_custom_headers); i++)
121 0 0         if (strncmp(forbidden_custom_headers[i], custom_header, name_len) == 0)
122 0           return true;
123              
124 0           return false;
125             }
126              
127 0           static int git_smart__set_custom_headers(
128             git_transport *transport,
129             const git_strarray *custom_headers)
130             {
131 0           transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
132             size_t i;
133              
134 0 0         if (t->custom_headers.count)
135 0           git_strarray_free(&t->custom_headers);
136              
137 0 0         if (!custom_headers)
138 0           return 0;
139              
140 0 0         for (i = 0; i < custom_headers->count; i++) {
141 0 0         if (is_malformed_http_header(custom_headers->strings[i])) {
142 0           git_error_set(GIT_ERROR_INVALID, "custom HTTP header '%s' is malformed", custom_headers->strings[i]);
143 0           return -1;
144             }
145 0 0         if (is_forbidden_custom_header(custom_headers->strings[i])) {
146 0           git_error_set(GIT_ERROR_INVALID, "custom HTTP header '%s' is already set by libgit2", custom_headers->strings[i]);
147 0           return -1;
148             }
149             }
150              
151 0           return git_strarray_copy(&t->custom_headers, custom_headers);
152             }
153              
154 0           int git_smart__update_heads(transport_smart *t, git_vector *symrefs)
155             {
156             size_t i;
157             git_pkt *pkt;
158              
159 0           git_vector_clear(&t->heads);
160 0 0         git_vector_foreach(&t->refs, i, pkt) {
161 0           git_pkt_ref *ref = (git_pkt_ref *) pkt;
162 0 0         if (pkt->type != GIT_PKT_REF)
163 0           continue;
164              
165 0 0         if (symrefs) {
166             git_refspec *spec;
167 0           git_buf buf = GIT_BUF_INIT;
168             size_t j;
169 0           int error = 0;
170              
171 0 0         git_vector_foreach(symrefs, j, spec) {
172 0           git_buf_clear(&buf);
173 0 0         if (git_refspec_src_matches(spec, ref->head.name) &&
    0          
174 0           !(error = git_refspec_transform(&buf, spec, ref->head.name))) {
175 0           git__free(ref->head.symref_target);
176 0           ref->head.symref_target = git_buf_detach(&buf);
177             }
178             }
179              
180 0           git_buf_dispose(&buf);
181              
182 0 0         if (error < 0)
183 0           return error;
184             }
185              
186 0 0         if (git_vector_insert(&t->heads, &ref->head) < 0)
187 0           return -1;
188             }
189              
190 0           return 0;
191             }
192              
193 0           static void free_symrefs(git_vector *symrefs)
194             {
195             git_refspec *spec;
196             size_t i;
197              
198 0 0         git_vector_foreach(symrefs, i, spec) {
199 0           git_refspec__dispose(spec);
200 0           git__free(spec);
201             }
202              
203 0           git_vector_free(symrefs);
204 0           }
205              
206 0           static int git_smart__connect(
207             git_transport *transport,
208             const char *url,
209             git_credential_acquire_cb cred_acquire_cb,
210             void *cred_acquire_payload,
211             const git_proxy_options *proxy,
212             int direction,
213             int flags)
214             {
215 0           transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
216             git_smart_subtransport_stream *stream;
217             int error;
218             git_pkt *pkt;
219             git_pkt_ref *first;
220             git_vector symrefs;
221             git_smart_service_t service;
222              
223 0 0         if (git_smart__reset_stream(t, true) < 0)
224 0           return -1;
225              
226 0           t->url = git__strdup(url);
227 0 0         GIT_ERROR_CHECK_ALLOC(t->url);
228              
229 0 0         if (git_proxy_options_dup(&t->proxy, proxy) < 0)
230 0           return -1;
231              
232 0           t->direction = direction;
233 0           t->flags = flags;
234 0           t->cred_acquire_cb = cred_acquire_cb;
235 0           t->cred_acquire_payload = cred_acquire_payload;
236              
237 0 0         if (GIT_DIRECTION_FETCH == t->direction)
238 0           service = GIT_SERVICE_UPLOADPACK_LS;
239 0 0         else if (GIT_DIRECTION_PUSH == t->direction)
240 0           service = GIT_SERVICE_RECEIVEPACK_LS;
241             else {
242 0           git_error_set(GIT_ERROR_NET, "invalid direction");
243 0           return -1;
244             }
245              
246 0 0         if ((error = t->wrapped->action(&stream, t->wrapped, t->url, service)) < 0)
247 0           return error;
248              
249             /* Save off the current stream (i.e. socket) that we are working with */
250 0           t->current_stream = stream;
251              
252 0           gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
253              
254             /* 2 flushes for RPC; 1 for stateful */
255 0 0         if ((error = git_smart__store_refs(t, t->rpc ? 2 : 1)) < 0)
    0          
256 0           return error;
257              
258             /* Strip the comment packet for RPC */
259 0 0         if (t->rpc) {
260 0           pkt = (git_pkt *)git_vector_get(&t->refs, 0);
261              
262 0 0         if (!pkt || GIT_PKT_COMMENT != pkt->type) {
    0          
263 0           git_error_set(GIT_ERROR_NET, "invalid response");
264 0           return -1;
265             } else {
266             /* Remove the comment pkt from the list */
267 0           git_vector_remove(&t->refs, 0);
268 0           git__free(pkt);
269             }
270             }
271              
272             /* We now have loaded the refs. */
273 0           t->have_refs = 1;
274              
275 0           pkt = (git_pkt *)git_vector_get(&t->refs, 0);
276 0 0         if (pkt && GIT_PKT_REF != pkt->type) {
    0          
277 0           git_error_set(GIT_ERROR_NET, "invalid response");
278 0           return -1;
279             }
280 0           first = (git_pkt_ref *)pkt;
281              
282 0 0         if ((error = git_vector_init(&symrefs, 1, NULL)) < 0)
283 0           return error;
284              
285             /* Detect capabilities */
286 0 0         if ((error = git_smart__detect_caps(first, &t->caps, &symrefs)) == 0) {
287             /* If the only ref in the list is capabilities^{} with OID_ZERO, remove it */
288 0 0         if (1 == t->refs.length && !strcmp(first->head.name, "capabilities^{}") &&
289 0           git_oid_is_zero(&first->head.oid)) {
290 0           git_vector_clear(&t->refs);
291 0           git_pkt_free((git_pkt *)first);
292             }
293              
294             /* Keep a list of heads for _ls */
295 0           git_smart__update_heads(t, &symrefs);
296 0 0         } else if (error == GIT_ENOTFOUND) {
297             /* There was no ref packet received, or the cap list was empty */
298 0           error = 0;
299             } else {
300 0           git_error_set(GIT_ERROR_NET, "invalid response");
301 0           goto cleanup;
302             }
303              
304 0 0         if (t->rpc && (error = git_smart__reset_stream(t, false)) < 0)
    0          
305 0           goto cleanup;
306              
307             /* We're now logically connected. */
308 0           t->connected = 1;
309              
310             cleanup:
311 0           free_symrefs(&symrefs);
312              
313 0           return error;
314             }
315              
316 0           static int git_smart__ls(const git_remote_head ***out, size_t *size, git_transport *transport)
317             {
318 0           transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
319              
320 0 0         if (!t->have_refs) {
321 0           git_error_set(GIT_ERROR_NET, "the transport has not yet loaded the refs");
322 0           return -1;
323             }
324              
325 0           *out = (const git_remote_head **) t->heads.contents;
326 0           *size = t->heads.length;
327              
328 0           return 0;
329             }
330              
331 0           int git_smart__negotiation_step(git_transport *transport, void *data, size_t len)
332             {
333 0           transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
334             git_smart_subtransport_stream *stream;
335             int error;
336              
337 0 0         if (t->rpc && git_smart__reset_stream(t, false) < 0)
    0          
338 0           return -1;
339              
340 0 0         if (GIT_DIRECTION_FETCH != t->direction) {
341 0           git_error_set(GIT_ERROR_NET, "this operation is only valid for fetch");
342 0           return -1;
343             }
344              
345 0 0         if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0)
346 0           return error;
347              
348             /* If this is a stateful implementation, the stream we get back should be the same */
349 0 0         assert(t->rpc || t->current_stream == stream);
    0          
350              
351             /* Save off the current stream (i.e. socket) that we are working with */
352 0           t->current_stream = stream;
353              
354 0 0         if ((error = stream->write(stream, (const char *)data, len)) < 0)
355 0           return error;
356              
357 0           gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
358              
359 0           return 0;
360             }
361              
362 0           int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **stream)
363             {
364             int error;
365              
366 0 0         if (t->rpc && git_smart__reset_stream(t, false) < 0)
    0          
367 0           return -1;
368              
369 0 0         if (GIT_DIRECTION_PUSH != t->direction) {
370 0           git_error_set(GIT_ERROR_NET, "this operation is only valid for push");
371 0           return -1;
372             }
373              
374 0 0         if ((error = t->wrapped->action(stream, t->wrapped, t->url, GIT_SERVICE_RECEIVEPACK)) < 0)
375 0           return error;
376              
377             /* If this is a stateful implementation, the stream we get back should be the same */
378 0 0         assert(t->rpc || t->current_stream == *stream);
    0          
379              
380             /* Save off the current stream (i.e. socket) that we are working with */
381 0           t->current_stream = *stream;
382              
383 0           gitno_buffer_setup_callback(&t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
384              
385 0           return 0;
386             }
387              
388 0           static void git_smart__cancel(git_transport *transport)
389             {
390 0           transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
391              
392 0           git_atomic_set(&t->cancelled, 1);
393 0           }
394              
395 0           static int git_smart__is_connected(git_transport *transport)
396             {
397 0           transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
398              
399 0           return t->connected;
400             }
401              
402 0           static int git_smart__read_flags(git_transport *transport, int *flags)
403             {
404 0           transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
405              
406 0           *flags = t->flags;
407              
408 0           return 0;
409             }
410              
411 0           static int git_smart__close(git_transport *transport)
412             {
413 0           transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
414 0           git_vector *common = &t->common;
415             unsigned int i;
416             git_pkt *p;
417             int ret;
418             git_smart_subtransport_stream *stream;
419 0           const char flush[] = "0000";
420              
421             /*
422             * If we're still connected at this point and not using RPC,
423             * we should say goodbye by sending a flush, or git-daemon
424             * will complain that we disconnected unexpectedly.
425             */
426 0 0         if (t->connected && !t->rpc &&
427 0           !t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) {
428 0           t->current_stream->write(t->current_stream, flush, 4);
429             }
430              
431 0           ret = git_smart__reset_stream(t, true);
432              
433 0 0         git_vector_foreach(common, i, p)
434 0           git_pkt_free(p);
435              
436 0           git_vector_free(common);
437              
438 0 0         if (t->url) {
439 0           git__free(t->url);
440 0           t->url = NULL;
441             }
442              
443 0           t->connected = 0;
444              
445 0           return ret;
446             }
447              
448 0           static void git_smart__free(git_transport *transport)
449             {
450 0           transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
451 0           git_vector *refs = &t->refs;
452             unsigned int i;
453             git_pkt *p;
454              
455             /* Make sure that the current stream is closed, if we have one. */
456 0           git_smart__close(transport);
457              
458             /* Free the subtransport */
459 0           t->wrapped->free(t->wrapped);
460              
461 0           git_vector_free(&t->heads);
462 0 0         git_vector_foreach(refs, i, p)
463 0           git_pkt_free(p);
464              
465 0           git_vector_free(refs);
466 0           git__free((char *)t->proxy.url);
467              
468 0           git_strarray_free(&t->custom_headers);
469              
470 0           git__free(t);
471 0           }
472              
473 0           static int ref_name_cmp(const void *a, const void *b)
474             {
475 0           const git_pkt_ref *ref_a = a, *ref_b = b;
476              
477 0           return strcmp(ref_a->head.name, ref_b->head.name);
478             }
479              
480 0           int git_transport_smart_certificate_check(git_transport *transport, git_cert *cert, int valid, const char *hostname)
481             {
482 0           transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
483              
484 0 0         assert(transport && cert && hostname);
    0          
    0          
485              
486 0 0         if (!t->certificate_check_cb)
487 0           return GIT_PASSTHROUGH;
488              
489 0           return t->certificate_check_cb(cert, valid, hostname, t->message_cb_payload);
490             }
491              
492 0           int git_transport_smart_credentials(git_credential **out, git_transport *transport, const char *user, int methods)
493             {
494 0           transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
495              
496 0 0         assert(out && transport);
    0          
497              
498 0 0         if (!t->cred_acquire_cb)
499 0           return GIT_PASSTHROUGH;
500              
501 0           return t->cred_acquire_cb(out, t->url, user, methods, t->cred_acquire_payload);
502             }
503              
504 0           int git_transport_smart_proxy_options(git_proxy_options *out, git_transport *transport)
505             {
506 0           transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
507 0           return git_proxy_options_dup(out, &t->proxy);
508             }
509              
510 0           int git_transport_smart(git_transport **out, git_remote *owner, void *param)
511             {
512             transport_smart *t;
513 0           git_smart_subtransport_definition *definition = (git_smart_subtransport_definition *)param;
514              
515 0 0         if (!param)
516 0           return -1;
517              
518 0           t = git__calloc(1, sizeof(transport_smart));
519 0 0         GIT_ERROR_CHECK_ALLOC(t);
520              
521 0           t->parent.version = GIT_TRANSPORT_VERSION;
522 0           t->parent.set_callbacks = git_smart__set_callbacks;
523 0           t->parent.set_custom_headers = git_smart__set_custom_headers;
524 0           t->parent.connect = git_smart__connect;
525 0           t->parent.close = git_smart__close;
526 0           t->parent.free = git_smart__free;
527 0           t->parent.negotiate_fetch = git_smart__negotiate_fetch;
528 0           t->parent.download_pack = git_smart__download_pack;
529 0           t->parent.push = git_smart__push;
530 0           t->parent.ls = git_smart__ls;
531 0           t->parent.is_connected = git_smart__is_connected;
532 0           t->parent.read_flags = git_smart__read_flags;
533 0           t->parent.cancel = git_smart__cancel;
534              
535 0           t->owner = owner;
536 0           t->rpc = definition->rpc;
537              
538 0 0         if (git_vector_init(&t->refs, 16, ref_name_cmp) < 0) {
539 0           git__free(t);
540 0           return -1;
541             }
542              
543 0 0         if (git_vector_init(&t->heads, 16, ref_name_cmp) < 0) {
544 0           git__free(t);
545 0           return -1;
546             }
547              
548 0 0         if (definition->callback(&t->wrapped, &t->parent, definition->param) < 0) {
549 0           git__free(t);
550 0           return -1;
551             }
552              
553 0           *out = (git_transport *) t;
554 0           return 0;
555             }