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