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 "common.h"
9
10 #include "git2.h"
11 #include "buffer.h"
12 #include "netops.h"
13 #include "git2/sys/transport.h"
14 #include "stream.h"
15 #include "streams/socket.h"
16
17 #define OWNING_SUBTRANSPORT(s) ((git_subtransport *)(s)->parent.subtransport)
18
19 static const char prefix_git[] = "git://";
20 static const char cmd_uploadpack[] = "git-upload-pack";
21 static const char cmd_receivepack[] = "git-receive-pack";
22
23 typedef struct {
24 git_smart_subtransport_stream parent;
25 git_stream *io;
26 const char *cmd;
27 char *url;
28 unsigned sent_command : 1;
29 } git_proto_stream;
30
31 typedef struct {
32 git_smart_subtransport parent;
33 git_transport *owner;
34 git_proto_stream *current_stream;
35 } git_subtransport;
36
37 /*
38 * Create a git protocol request.
39 *
40 * For example: 0035git-upload-pack /libgit2/libgit2\0host=github.com\0
41 */
gen_proto(git_buf * request,const char * cmd,const char * url)42 static int gen_proto(git_buf *request, const char *cmd, const char *url)
43 {
44 char *delim, *repo;
45 char host[] = "host=";
46 size_t len;
47
48 delim = strchr(url, '/');
49 if (delim == NULL) {
50 git_error_set(GIT_ERROR_NET, "malformed URL");
51 return -1;
52 }
53
54 repo = delim;
55 if (repo[1] == '~')
56 ++repo;
57
58 delim = strchr(url, ':');
59 if (delim == NULL)
60 delim = strchr(url, '/');
61
62 len = 4 + strlen(cmd) + 1 + strlen(repo) + 1 + strlen(host) + (delim - url) + 1;
63
64 git_buf_grow(request, len);
65 git_buf_printf(request, "%04x%s %s%c%s",
66 (unsigned int)(len & 0x0FFFF), cmd, repo, 0, host);
67 git_buf_put(request, url, delim - url);
68 git_buf_putc(request, '\0');
69
70 if (git_buf_oom(request))
71 return -1;
72
73 return 0;
74 }
75
send_command(git_proto_stream * s)76 static int send_command(git_proto_stream *s)
77 {
78 git_buf request = GIT_BUF_INIT;
79 int error;
80
81 if ((error = gen_proto(&request, s->cmd, s->url)) < 0)
82 goto cleanup;
83
84 if ((error = git_stream__write_full(s->io, request.ptr, request.size, 0)) < 0)
85 goto cleanup;
86
87 s->sent_command = 1;
88
89 cleanup:
90 git_buf_dispose(&request);
91 return error;
92 }
93
git_proto_stream_read(git_smart_subtransport_stream * stream,char * buffer,size_t buf_size,size_t * bytes_read)94 static int git_proto_stream_read(
95 git_smart_subtransport_stream *stream,
96 char *buffer,
97 size_t buf_size,
98 size_t *bytes_read)
99 {
100 int error;
101 git_proto_stream *s = (git_proto_stream *)stream;
102 gitno_buffer buf;
103
104 *bytes_read = 0;
105
106 if (!s->sent_command && (error = send_command(s)) < 0)
107 return error;
108
109 gitno_buffer_setup_fromstream(s->io, &buf, buffer, buf_size);
110
111 if ((error = gitno_recv(&buf)) < 0)
112 return error;
113
114 *bytes_read = buf.offset;
115
116 return 0;
117 }
118
git_proto_stream_write(git_smart_subtransport_stream * stream,const char * buffer,size_t len)119 static int git_proto_stream_write(
120 git_smart_subtransport_stream *stream,
121 const char *buffer,
122 size_t len)
123 {
124 git_proto_stream *s = (git_proto_stream *)stream;
125 int error;
126
127 if (!s->sent_command && (error = send_command(s)) < 0)
128 return error;
129
130 return git_stream__write_full(s->io, buffer, len, 0);
131 }
132
git_proto_stream_free(git_smart_subtransport_stream * stream)133 static void git_proto_stream_free(git_smart_subtransport_stream *stream)
134 {
135 git_proto_stream *s;
136 git_subtransport *t;
137
138 if (!stream)
139 return;
140
141 s = (git_proto_stream *)stream;
142 t = OWNING_SUBTRANSPORT(s);
143
144 t->current_stream = NULL;
145
146 git_stream_close(s->io);
147 git_stream_free(s->io);
148 git__free(s->url);
149 git__free(s);
150 }
151
git_proto_stream_alloc(git_subtransport * t,const char * url,const char * cmd,const char * host,const char * port,git_smart_subtransport_stream ** stream)152 static int git_proto_stream_alloc(
153 git_subtransport *t,
154 const char *url,
155 const char *cmd,
156 const char *host,
157 const char *port,
158 git_smart_subtransport_stream **stream)
159 {
160 git_proto_stream *s;
161
162 if (!stream)
163 return -1;
164
165 s = git__calloc(1, sizeof(git_proto_stream));
166 GIT_ERROR_CHECK_ALLOC(s);
167
168 s->parent.subtransport = &t->parent;
169 s->parent.read = git_proto_stream_read;
170 s->parent.write = git_proto_stream_write;
171 s->parent.free = git_proto_stream_free;
172
173 s->cmd = cmd;
174 s->url = git__strdup(url);
175
176 if (!s->url) {
177 git__free(s);
178 return -1;
179 }
180
181 if ((git_socket_stream_new(&s->io, host, port)) < 0)
182 return -1;
183
184 GIT_ERROR_CHECK_VERSION(s->io, GIT_STREAM_VERSION, "git_stream");
185
186 *stream = &s->parent;
187 return 0;
188 }
189
_git_uploadpack_ls(git_subtransport * t,const char * url,git_smart_subtransport_stream ** stream)190 static int _git_uploadpack_ls(
191 git_subtransport *t,
192 const char *url,
193 git_smart_subtransport_stream **stream)
194 {
195 git_net_url urldata = GIT_NET_URL_INIT;
196 const char *stream_url = url;
197 const char *host, *port;
198 git_proto_stream *s;
199 int error;
200
201 *stream = NULL;
202
203 if (!git__prefixcmp(url, prefix_git))
204 stream_url += strlen(prefix_git);
205
206 if ((error = git_net_url_parse(&urldata, url)) < 0)
207 return error;
208
209 host = urldata.host;
210 port = urldata.port ? urldata.port : GIT_DEFAULT_PORT;
211
212 error = git_proto_stream_alloc(t, stream_url, cmd_uploadpack, host, port, stream);
213
214 git_net_url_dispose(&urldata);
215
216 if (error < 0) {
217 git_proto_stream_free(*stream);
218 return error;
219 }
220
221 s = (git_proto_stream *) *stream;
222 if ((error = git_stream_connect(s->io)) < 0) {
223 git_proto_stream_free(*stream);
224 return error;
225 }
226
227 t->current_stream = s;
228
229 return 0;
230 }
231
_git_uploadpack(git_subtransport * t,const char * url,git_smart_subtransport_stream ** stream)232 static int _git_uploadpack(
233 git_subtransport *t,
234 const char *url,
235 git_smart_subtransport_stream **stream)
236 {
237 GIT_UNUSED(url);
238
239 if (t->current_stream) {
240 *stream = &t->current_stream->parent;
241 return 0;
242 }
243
244 git_error_set(GIT_ERROR_NET, "must call UPLOADPACK_LS before UPLOADPACK");
245 return -1;
246 }
247
_git_receivepack_ls(git_subtransport * t,const char * url,git_smart_subtransport_stream ** stream)248 static int _git_receivepack_ls(
249 git_subtransport *t,
250 const char *url,
251 git_smart_subtransport_stream **stream)
252 {
253 git_net_url urldata = GIT_NET_URL_INIT;
254 const char *stream_url = url;
255 git_proto_stream *s;
256 int error;
257
258 *stream = NULL;
259 if (!git__prefixcmp(url, prefix_git))
260 stream_url += strlen(prefix_git);
261
262 if ((error = git_net_url_parse(&urldata, url)) < 0)
263 return error;
264
265 error = git_proto_stream_alloc(t, stream_url, cmd_receivepack, urldata.host, urldata.port, stream);
266
267 git_net_url_dispose(&urldata);
268
269 if (error < 0) {
270 git_proto_stream_free(*stream);
271 return error;
272 }
273
274 s = (git_proto_stream *) *stream;
275
276 if ((error = git_stream_connect(s->io)) < 0)
277 return error;
278
279 t->current_stream = s;
280
281 return 0;
282 }
283
_git_receivepack(git_subtransport * t,const char * url,git_smart_subtransport_stream ** stream)284 static int _git_receivepack(
285 git_subtransport *t,
286 const char *url,
287 git_smart_subtransport_stream **stream)
288 {
289 GIT_UNUSED(url);
290
291 if (t->current_stream) {
292 *stream = &t->current_stream->parent;
293 return 0;
294 }
295
296 git_error_set(GIT_ERROR_NET, "must call RECEIVEPACK_LS before RECEIVEPACK");
297 return -1;
298 }
299
_git_action(git_smart_subtransport_stream ** stream,git_smart_subtransport * subtransport,const char * url,git_smart_service_t action)300 static int _git_action(
301 git_smart_subtransport_stream **stream,
302 git_smart_subtransport *subtransport,
303 const char *url,
304 git_smart_service_t action)
305 {
306 git_subtransport *t = (git_subtransport *) subtransport;
307
308 switch (action) {
309 case GIT_SERVICE_UPLOADPACK_LS:
310 return _git_uploadpack_ls(t, url, stream);
311
312 case GIT_SERVICE_UPLOADPACK:
313 return _git_uploadpack(t, url, stream);
314
315 case GIT_SERVICE_RECEIVEPACK_LS:
316 return _git_receivepack_ls(t, url, stream);
317
318 case GIT_SERVICE_RECEIVEPACK:
319 return _git_receivepack(t, url, stream);
320 }
321
322 *stream = NULL;
323 return -1;
324 }
325
_git_close(git_smart_subtransport * subtransport)326 static int _git_close(git_smart_subtransport *subtransport)
327 {
328 git_subtransport *t = (git_subtransport *) subtransport;
329
330 GIT_ASSERT(!t->current_stream);
331
332 GIT_UNUSED(t);
333
334 return 0;
335 }
336
_git_free(git_smart_subtransport * subtransport)337 static void _git_free(git_smart_subtransport *subtransport)
338 {
339 git_subtransport *t = (git_subtransport *) subtransport;
340
341 git__free(t);
342 }
343
git_smart_subtransport_git(git_smart_subtransport ** out,git_transport * owner,void * param)344 int git_smart_subtransport_git(git_smart_subtransport **out, git_transport *owner, void *param)
345 {
346 git_subtransport *t;
347
348 GIT_UNUSED(param);
349
350 if (!out)
351 return -1;
352
353 t = git__calloc(1, sizeof(git_subtransport));
354 GIT_ERROR_CHECK_ALLOC(t);
355
356 t->owner = owner;
357 t->parent.action = _git_action;
358 t->parent.close = _git_close;
359 t->parent.free = _git_free;
360
361 *out = (git_smart_subtransport *) t;
362 return 0;
363 }
364