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