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