RepositoryBrowser: Add drag handler
[TortoiseGit.git] / src / libgit2 / ssh-wintunnel.c
blob7c89ccdb9623038019020a9ec91157aec166a1bf
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2014, 2016 TortoiseGit
4 // Copyright (C) the libgit2 contributors. All rights reserved.
5 // - based on libgit2/src/transports/ssh.c
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License
9 // as published by the Free Software Foundation; either version 2
10 // of the License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software Foundation,
19 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 #include "buffer.h"
23 #include "netops.h"
24 #include "http_parser.h"
25 #include "../../ext/libgit2/src/transports/smart.h"
26 #include "system-call.h"
27 #include "ssh-wintunnel.h"
29 #define OWNING_SUBTRANSPORT(s) ((ssh_subtransport *)(s)->parent.subtransport)
31 static const char *ssh_prefixes[] = { "ssh://", "ssh+git://", "git+ssh://" };
33 static const char cmd_uploadpack[] = "git-upload-pack";
34 static const char cmd_receivepack[] = "git-receive-pack";
36 typedef struct {
37 git_smart_subtransport_stream parent;
38 COMMAND_HANDLE commandHandle;
39 const char *cmd;
40 char *url;
41 } ssh_stream;
43 typedef struct {
44 git_smart_subtransport parent;
45 transport_smart *owner;
46 ssh_stream *current_stream;
47 char *cmd_uploadpack;
48 char *cmd_receivepack;
49 LPWSTR* pEnv;
50 LPWSTR sshtoolpath;
51 } ssh_subtransport;
54 * Create a git protocol request.
56 * For example: git-upload-pack '/libgit2/libgit2'
58 static int gen_proto(git_buf *request, const char *cmd, const char *url)
60 const char *repo;
62 size_t i;
64 for (i = 0; i < ARRAY_SIZE(ssh_prefixes); ++i) {
65 const char *p = ssh_prefixes[i];
67 if (!git__prefixcmp(url, p)) {
68 url = url + strlen(p);
69 repo = strchr(url, '/');
70 if (repo && repo[1] == '~')
71 ++repo;
73 goto done;
76 repo = strchr(url, ':');
77 if (repo) repo++;
79 done:
80 if (!repo) {
81 giterr_set(GITERR_NET, "Malformed git protocol URL");
82 return -1;
85 git_buf_printf(request, "\"%s '%s'\"", cmd, repo);
87 if (git_buf_oom(request)) {
88 giterr_set_oom();
89 return -1;
92 return 0;
95 static int ssh_stream_read(
96 git_smart_subtransport_stream *stream,
97 char *buffer,
98 size_t buf_size,
99 size_t *bytes_read)
101 ssh_stream *s = (ssh_stream *)stream;
103 if (command_read_stdout(&s->commandHandle, buffer, buf_size, bytes_read))
104 return -1;
106 return 0;
109 static int ssh_stream_write(
110 git_smart_subtransport_stream *stream,
111 const char *buffer,
112 size_t len)
114 ssh_stream *s = (ssh_stream *)stream;
116 if (command_write(&s->commandHandle, buffer, len))
117 return -1;
119 return 0;
122 static void ssh_stream_free(git_smart_subtransport_stream *stream)
124 ssh_stream *s = (ssh_stream *)stream;
125 ssh_subtransport *t = OWNING_SUBTRANSPORT(s);
127 DWORD exitcode = command_close(&s->commandHandle);
128 if (s->commandHandle.errBuf) {
129 if (exitcode && exitcode != MAXDWORD) {
130 if (!git_buf_oom(s->commandHandle.errBuf) && git_buf_len(s->commandHandle.errBuf))
131 giterr_set(GITERR_SSH, "Command exited non-zero (%ld) and returned:\n%s", exitcode, s->commandHandle.errBuf->ptr);
132 else
133 giterr_set(GITERR_SSH, "Command exited non-zero: %ld", exitcode);
135 git_buf_free(s->commandHandle.errBuf);
136 git__free(s->commandHandle.errBuf);
139 t->current_stream = NULL;
141 git__free(s->url);
142 git__free(s);
145 static int ssh_stream_alloc(
146 ssh_subtransport *t,
147 const char *url,
148 const char *cmd,
149 git_smart_subtransport_stream **stream)
151 ssh_stream *s;
152 git_buf *errBuf;
154 assert(stream);
156 s = git__calloc(sizeof(ssh_stream), 1);
157 if (!s) {
158 giterr_set_oom();
159 return -1;
162 errBuf = git__calloc(sizeof(git_buf), 1);
163 if (!errBuf) {
164 git__free(s);
165 giterr_set_oom();
166 return -1;
169 s->parent.subtransport = &t->parent;
170 s->parent.read = ssh_stream_read;
171 s->parent.write = ssh_stream_write;
172 s->parent.free = ssh_stream_free;
174 s->cmd = cmd;
176 s->url = git__strdup(url);
177 if (!s->url) {
178 git__free(errBuf);
179 git__free(s);
180 giterr_set_oom();
181 return -1;
184 command_init(&s->commandHandle);
185 s->commandHandle.errBuf = errBuf;
187 *stream = &s->parent;
188 return 0;
191 static int git_ssh_extract_url_parts(
192 char **host,
193 char **username,
194 const char *url)
196 const char *colon, *at;
197 const char *start;
199 colon = strchr(url, ':');
201 at = strchr(url, '@');
202 if (at) {
203 start = at + 1;
204 *username = git__substrdup(url, at - url);
205 if (!*username) {
206 giterr_set_oom();
207 return -1;
209 } else {
210 start = url;
211 *username = NULL;
214 if (colon == NULL || (colon < start)) {
215 giterr_set(GITERR_NET, "Malformed URL");
216 return -1;
219 *host = git__substrdup(start, colon - start);
220 if (!*host) {
221 giterr_set_oom();
222 return -1;
225 return 0;
228 static int wcstristr(const wchar_t *heystack, const wchar_t *needle)
230 const wchar_t *end;
231 size_t lenNeedle = wcslen(needle);
232 size_t lenHeystack = wcslen(heystack);
233 if (lenNeedle > lenHeystack)
234 return 0;
236 end = heystack + lenHeystack - lenNeedle;
237 while (heystack <= end) {
238 if (!wcsnicmp(heystack, needle, lenNeedle))
239 return 1;
240 ++heystack;
243 return 0;
246 /* based on netops.c code of libgit2, needed by extract_url_parts, see comment there */
247 #define hex2c(c) ((c | 32) % 39 - 9)
248 static char* unescape(char *str)
250 int x, y;
251 int len = (int)strlen(str);
253 for (x = y = 0; str[y]; ++x, ++y) {
254 if ((str[x] = str[y]) == '%') {
255 if (y < len - 2 && isxdigit(str[y + 1]) && isxdigit(str[y + 2])) {
256 str[x] = (hex2c(str[y + 1]) << 4) + hex2c(str[y + 2]);
257 y += 2;
261 str[x] = '\0';
262 return str;
265 /* based on gitno_extract_url_parts, keep c&p copy here until https://github.com/libgit2/libgit2/pull/2492 gets merged or forever ;)*/
266 static int extract_url_parts(
267 char **host,
268 char **port,
269 char **path,
270 char **username,
271 char **password,
272 const char *url,
273 const char *default_port)
275 struct http_parser_url u = { 0 };
276 const char *_host, *_port, *_path, *_userinfo;
278 if (http_parser_parse_url(url, strlen(url), false, &u)) {
279 giterr_set(GITERR_NET, "Malformed URL '%s'", url);
280 return GIT_EINVALIDSPEC;
283 _host = url + u.field_data[UF_HOST].off;
284 _port = url + u.field_data[UF_PORT].off;
285 _path = url + u.field_data[UF_PATH].off;
286 _userinfo = url + u.field_data[UF_USERINFO].off;
288 if (u.field_set & (1 << UF_HOST)) {
289 *host = git__substrdup(_host, u.field_data[UF_HOST].len);
290 GITERR_CHECK_ALLOC(*host);
293 if (u.field_set & (1 << UF_PORT)) {
294 *port = git__substrdup(_port, u.field_data[UF_PORT].len);
295 } else if (default_port) {
296 *port = git__strdup(default_port);
297 GITERR_CHECK_ALLOC(*port);
298 } else {
299 *port = NULL;
302 if (u.field_set & (1 << UF_PATH)) {
303 *path = git__substrdup(_path, u.field_data[UF_PATH].len);
304 GITERR_CHECK_ALLOC(*path);
305 } else {
306 giterr_set(GITERR_NET, "invalid url, missing path");
307 return GIT_EINVALIDSPEC;
310 if (u.field_set & (1 << UF_USERINFO)) {
311 const char *colon = memchr(_userinfo, ':', u.field_data[UF_USERINFO].len);
312 if (colon) {
313 *username = unescape(git__substrdup(_userinfo, colon - _userinfo));
314 *password = unescape(git__substrdup(colon + 1, u.field_data[UF_USERINFO].len - (colon + 1 - _userinfo)));
315 GITERR_CHECK_ALLOC(*password);
316 } else {
317 *username = git__substrdup(_userinfo, u.field_data[UF_USERINFO].len);
319 GITERR_CHECK_ALLOC(*username);
323 return 0;
326 static int _git_ssh_setup_tunnel(
327 ssh_subtransport *t,
328 const char *url,
329 const char *gitCmd,
330 git_smart_subtransport_stream **stream)
332 char *host = NULL, *port = NULL, *path = NULL, *user = NULL, *pass = NULL;
333 size_t i;
334 ssh_stream *s;
335 wchar_t *ssh = t->sshtoolpath;
336 wchar_t *wideParams = NULL;
337 wchar_t *cmd = NULL;
338 git_buf params = GIT_BUF_INIT;
339 int isPutty;
340 size_t length;
342 *stream = NULL;
343 if (ssh_stream_alloc(t, url, gitCmd, stream) < 0) {
344 giterr_set_oom();
345 return -1;
348 s = (ssh_stream *)*stream;
350 for (i = 0; i < ARRAY_SIZE(ssh_prefixes); ++i) {
351 const char *p = ssh_prefixes[i];
353 if (!git__prefixcmp(url, p)) {
354 if (extract_url_parts(&host, &port, &path, &user, &pass, url, NULL) < 0)
355 goto on_error;
357 goto post_extract;
360 if (git_ssh_extract_url_parts(&host, &user, url) < 0)
361 goto on_error;
363 post_extract:
364 if (!ssh)
366 giterr_set(GITERR_SSH, "No GIT_SSH tool configured");
367 goto on_error;
370 isPutty = wcstristr(ssh, L"plink");
371 if (port) {
372 if (isPutty)
373 git_buf_printf(&params, " -P %s", port);
374 else
375 git_buf_printf(&params, " -p %s", port);
377 if (isPutty && !wcstristr(ssh, L"tortoiseplink")) {
378 git_buf_puts(&params, " -batch");
380 if (user)
381 git_buf_printf(&params, " %s@%s ", user, host);
382 else
383 git_buf_printf(&params, " %s ", host);
384 if (gen_proto(&params, s->cmd, s->url))
385 goto on_error;
386 if (git_buf_oom(&params)) {
387 giterr_set_oom();
388 goto on_error;
391 if (git__utf8_to_16_alloc(&wideParams, params.ptr) < 0) {
392 giterr_set_oom();
393 goto on_error;
395 git_buf_free(&params);
397 length = wcslen(ssh) + wcslen(wideParams) + 3;
398 cmd = git__calloc(length, sizeof(wchar_t));
399 if (!cmd) {
400 giterr_set_oom();
401 goto on_error;
404 wcscat_s(cmd, length, L"\"");
405 wcscat_s(cmd, length, ssh);
406 wcscat_s(cmd, length, L"\"");
407 wcscat_s(cmd, length, wideParams);
409 if (command_start(cmd, &s->commandHandle, t->pEnv, isPutty ? CREATE_NEW_CONSOLE : DETACHED_PROCESS))
410 goto on_error;
412 git__free(wideParams);
413 git__free(cmd);
414 t->current_stream = s;
415 git__free(host);
416 git__free(port);
417 git__free(path);
418 git__free(user);
419 git__free(pass);
421 return 0;
423 on_error:
424 t->current_stream = NULL;
426 if (*stream)
427 ssh_stream_free(*stream);
429 git_buf_free(&params);
431 if (wideParams)
432 git__free(wideParams);
434 if (cmd)
435 git__free(cmd);
437 git__free(host);
438 git__free(port);
439 git__free(user);
440 git__free(pass);
442 return -1;
445 static int ssh_uploadpack_ls(
446 ssh_subtransport *t,
447 const char *url,
448 git_smart_subtransport_stream **stream)
450 const char *cmd = t->cmd_uploadpack ? t->cmd_uploadpack : cmd_uploadpack;
452 if (_git_ssh_setup_tunnel(t, url, cmd, stream) < 0)
453 return -1;
455 return 0;
458 static int ssh_uploadpack(
459 ssh_subtransport *t,
460 const char *url,
461 git_smart_subtransport_stream **stream)
463 GIT_UNUSED(url);
465 if (t->current_stream) {
466 *stream = &t->current_stream->parent;
467 return 0;
470 giterr_set(GITERR_NET, "Must call UPLOADPACK_LS before UPLOADPACK");
471 return -1;
474 static int ssh_receivepack_ls(
475 ssh_subtransport *t,
476 const char *url,
477 git_smart_subtransport_stream **stream)
479 const char *cmd = t->cmd_receivepack ? t->cmd_receivepack : cmd_receivepack;
481 if (_git_ssh_setup_tunnel(t, url, cmd, stream) < 0)
482 return -1;
484 return 0;
487 static int ssh_receivepack(
488 ssh_subtransport *t,
489 const char *url,
490 git_smart_subtransport_stream **stream)
492 GIT_UNUSED(url);
494 if (t->current_stream) {
495 *stream = &t->current_stream->parent;
496 return 0;
499 giterr_set(GITERR_NET, "Must call RECEIVEPACK_LS before RECEIVEPACK");
500 return -1;
503 static int _ssh_action(
504 git_smart_subtransport_stream **stream,
505 git_smart_subtransport *subtransport,
506 const char *url,
507 git_smart_service_t action)
509 ssh_subtransport *t = (ssh_subtransport *) subtransport;
511 switch (action) {
512 case GIT_SERVICE_UPLOADPACK_LS:
513 return ssh_uploadpack_ls(t, url, stream);
515 case GIT_SERVICE_UPLOADPACK:
516 return ssh_uploadpack(t, url, stream);
518 case GIT_SERVICE_RECEIVEPACK_LS:
519 return ssh_receivepack_ls(t, url, stream);
521 case GIT_SERVICE_RECEIVEPACK:
522 return ssh_receivepack(t, url, stream);
525 *stream = NULL;
526 return -1;
529 static int _ssh_close(git_smart_subtransport *subtransport)
531 ssh_subtransport *t = (ssh_subtransport *) subtransport;
533 assert(!t->current_stream);
535 return 0;
538 static void _ssh_free(git_smart_subtransport *subtransport)
540 ssh_subtransport *t = (ssh_subtransport *) subtransport;
542 assert(!t->current_stream);
544 git__free(t->cmd_uploadpack);
545 git__free(t->cmd_receivepack);
546 git__free(t->sshtoolpath);
547 git__free(t);
550 int git_smart_subtransport_ssh_wintunnel(
551 git_smart_subtransport **out, git_transport *owner, LPCWSTR sshtoolpath, LPWSTR* pEnv)
553 ssh_subtransport *t;
555 assert(out);
557 t = git__calloc(sizeof(ssh_subtransport), 1);
558 if (!t) {
559 giterr_set_oom();
560 return -1;
563 t->sshtoolpath = wcsdup(sshtoolpath);
564 t->pEnv = pEnv;
566 t->owner = (transport_smart *)owner;
567 t->parent.action = _ssh_action;
568 t->parent.close = _ssh_close;
569 t->parent.free = _ssh_free;
571 *out = (git_smart_subtransport *) t;
572 return 0;