CommitDlg: Update empty file list message
[TortoiseGit.git] / src / libgit2 / ssh-wintunnel.c
blobb64ed9f2fc26a902689298185472ea30ddf8e284
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2014 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 prefix_ssh[] = "ssh://";
32 static const char cmd_uploadpack[] = "git-upload-pack";
33 static const char cmd_receivepack[] = "git-receive-pack";
35 typedef struct {
36 git_smart_subtransport_stream parent;
37 COMMAND_HANDLE commandHandle;
38 const char *cmd;
39 char *url;
40 } ssh_stream;
42 typedef struct {
43 git_smart_subtransport parent;
44 transport_smart *owner;
45 ssh_stream *current_stream;
46 char *cmd_uploadpack;
47 char *cmd_receivepack;
48 LPWSTR pEnv;
49 LPWSTR sshtoolpath;
50 } ssh_subtransport;
53 * Create a git protocol request.
55 * For example: git-upload-pack '/libgit2/libgit2'
57 static int gen_proto(git_buf *request, const char *cmd, const char *url)
59 const char *repo;
61 if (!git__prefixcmp(url, prefix_ssh)) {
62 url = url + strlen(prefix_ssh);
63 repo = strchr(url, '/');
64 } else {
65 repo = strchr(url, ':');
66 if (repo) repo++;
69 if (!repo) {
70 giterr_set(GITERR_NET, "Malformed git protocol URL");
71 return -1;
74 git_buf_printf(request, "\"%s '%s'\"", cmd, repo);
76 if (git_buf_oom(request)) {
77 giterr_set_oom();
78 return -1;
81 return 0;
84 static int ssh_stream_read(
85 git_smart_subtransport_stream *stream,
86 char *buffer,
87 size_t buf_size,
88 size_t *bytes_read)
90 ssh_stream *s = (ssh_stream *)stream;
92 if (command_read_stdout(&s->commandHandle, buffer, buf_size, bytes_read))
93 return -1;
95 return 0;
98 static int ssh_stream_write(
99 git_smart_subtransport_stream *stream,
100 const char *buffer,
101 size_t len)
103 ssh_stream *s = (ssh_stream *)stream;
105 if (command_write(&s->commandHandle, buffer, len))
106 return -1;
108 return 0;
111 static void ssh_stream_free(git_smart_subtransport_stream *stream)
113 ssh_stream *s = (ssh_stream *)stream;
114 ssh_subtransport *t = OWNING_SUBTRANSPORT(s);
116 DWORD exitcode = command_close(&s->commandHandle);
117 if (s->commandHandle.errBuf) {
118 if (exitcode && exitcode != MAXDWORD) {
119 if (!git_buf_oom(s->commandHandle.errBuf) && git_buf_len(s->commandHandle.errBuf))
120 giterr_set(GITERR_SSH, "Command exited non-zero (%ld) and returned:\n%s", exitcode, s->commandHandle.errBuf->ptr);
121 else
122 giterr_set(GITERR_SSH, "Command exited non-zero: %ld", exitcode);
124 git_buf_free(s->commandHandle.errBuf);
125 git__free(s->commandHandle.errBuf);
128 t->current_stream = NULL;
130 git__free(s->url);
131 git__free(s);
134 static int ssh_stream_alloc(
135 ssh_subtransport *t,
136 const char *url,
137 const char *cmd,
138 git_smart_subtransport_stream **stream)
140 ssh_stream *s;
141 git_buf *errBuf;
143 assert(stream);
145 s = git__calloc(sizeof(ssh_stream), 1);
146 if (!s) {
147 giterr_set_oom();
148 return -1;
151 errBuf = git__calloc(sizeof(git_buf), 1);
152 if (!errBuf) {
153 git__free(s);
154 giterr_set_oom();
155 return -1;
158 s->parent.subtransport = &t->parent;
159 s->parent.read = ssh_stream_read;
160 s->parent.write = ssh_stream_write;
161 s->parent.free = ssh_stream_free;
163 s->cmd = cmd;
165 s->url = git__strdup(url);
166 if (!s->url) {
167 git__free(errBuf);
168 git__free(s);
169 giterr_set_oom();
170 return -1;
173 command_init(&s->commandHandle);
174 s->commandHandle.errBuf = errBuf;
176 *stream = &s->parent;
177 return 0;
180 static int git_ssh_extract_url_parts(
181 char **host,
182 char **username,
183 const char *url)
185 const char *colon, *at;
186 const char *start;
188 colon = strchr(url, ':');
190 at = strchr(url, '@');
191 if (at) {
192 start = at + 1;
193 *username = git__substrdup(url, at - url);
194 if (!*username) {
195 giterr_set_oom();
196 return -1;
198 } else {
199 start = url;
200 *username = NULL;
203 if (colon == NULL || (colon < start)) {
204 giterr_set(GITERR_NET, "Malformed URL");
205 return -1;
208 *host = git__substrdup(start, colon - start);
209 if (!*host) {
210 giterr_set_oom();
211 return -1;
214 return 0;
217 static int wcstristr(const wchar_t *heystack, const wchar_t *needle)
219 const wchar_t *end;
220 size_t lenNeedle = wcslen(needle);
221 size_t lenHeystack = wcslen(heystack);
222 if (lenNeedle > lenHeystack)
223 return 0;
225 end = heystack + lenHeystack - lenNeedle;
226 while (heystack <= end) {
227 if (!wcsnicmp(heystack, needle, lenNeedle))
228 return 1;
229 ++heystack;
232 return 0;
235 /* based on netops.c code of libgit2, needed by extract_url_parts, see comment there */
236 #define hex2c(c) ((c | 32) % 39 - 9)
237 static char* unescape(char *str)
239 int x, y;
240 int len = (int)strlen(str);
242 for (x = y = 0; str[y]; ++x, ++y) {
243 if ((str[x] = str[y]) == '%') {
244 if (y < len - 2 && isxdigit(str[y + 1]) && isxdigit(str[y + 2])) {
245 str[x] = (hex2c(str[y + 1]) << 4) + hex2c(str[y + 2]);
246 y += 2;
250 str[x] = '\0';
251 return str;
254 /* based on gitno_extract_url_parts, keep c&p copy here until https://github.com/libgit2/libgit2/pull/2492 gets merged or forever ;)*/
255 static int extract_url_parts(
256 char **host,
257 char **port,
258 char **path,
259 char **username,
260 char **password,
261 const char *url,
262 const char *default_port)
264 struct http_parser_url u = { 0 };
265 const char *_host, *_port, *_path, *_userinfo;
267 if (http_parser_parse_url(url, strlen(url), false, &u)) {
268 giterr_set(GITERR_NET, "Malformed URL '%s'", url);
269 return GIT_EINVALIDSPEC;
272 _host = url + u.field_data[UF_HOST].off;
273 _port = url + u.field_data[UF_PORT].off;
274 _path = url + u.field_data[UF_PATH].off;
275 _userinfo = url + u.field_data[UF_USERINFO].off;
277 if (u.field_set & (1 << UF_HOST)) {
278 *host = git__substrdup(_host, u.field_data[UF_HOST].len);
279 GITERR_CHECK_ALLOC(*host);
282 if (u.field_set & (1 << UF_PORT)) {
283 *port = git__substrdup(_port, u.field_data[UF_PORT].len);
284 } else if (default_port) {
285 *port = git__strdup(default_port);
286 GITERR_CHECK_ALLOC(*port);
287 } else {
288 *port = NULL;
291 if (u.field_set & (1 << UF_PATH)) {
292 *path = git__substrdup(_path, u.field_data[UF_PATH].len);
293 GITERR_CHECK_ALLOC(*path);
294 } else {
295 giterr_set(GITERR_NET, "invalid url, missing path");
296 return GIT_EINVALIDSPEC;
299 if (u.field_set & (1 << UF_USERINFO)) {
300 const char *colon = memchr(_userinfo, ':', u.field_data[UF_USERINFO].len);
301 if (colon) {
302 *username = unescape(git__substrdup(_userinfo, colon - _userinfo));
303 *password = unescape(git__substrdup(colon + 1, u.field_data[UF_USERINFO].len - (colon + 1 - _userinfo)));
304 GITERR_CHECK_ALLOC(*password);
305 } else {
306 *username = git__substrdup(_userinfo, u.field_data[UF_USERINFO].len);
308 GITERR_CHECK_ALLOC(*username);
312 return 0;
315 static int _git_ssh_setup_tunnel(
316 ssh_subtransport *t,
317 const char *url,
318 const char *gitCmd,
319 git_smart_subtransport_stream **stream)
321 char *host = NULL, *port = NULL, *path = NULL, *user = NULL, *pass = NULL;
322 ssh_stream *s;
323 wchar_t *ssh = t->sshtoolpath;
324 wchar_t *wideParams = NULL;
325 wchar_t *cmd = NULL;
326 git_buf params = GIT_BUF_INIT;
327 int isPutty;
328 size_t length;
330 *stream = NULL;
331 if (ssh_stream_alloc(t, url, gitCmd, stream) < 0) {
332 giterr_set_oom();
333 return -1;
336 s = (ssh_stream *)*stream;
338 if (!git__prefixcmp(url, prefix_ssh)) {
339 if (extract_url_parts(&host, &port, &path, &user, &pass, url, NULL) < 0)
340 goto on_error;
341 } else {
342 if (git_ssh_extract_url_parts(&host, &user, url) < 0)
343 goto on_error;
346 if (!ssh)
348 giterr_set(GITERR_SSH, "No GIT_SSH tool configured");
349 goto on_error;
352 isPutty = wcstristr(ssh, L"plink");
353 if (port) {
354 if (isPutty)
355 git_buf_printf(&params, " -P %s", port);
356 else
357 git_buf_printf(&params, " -p %s", port);
359 if (isPutty && !wcstristr(ssh, L"tortoiseplink")) {
360 git_buf_puts(&params, " -batch");
362 if (user)
363 git_buf_printf(&params, " %s@%s ", user, host);
364 else
365 git_buf_printf(&params, " %s ", host);
366 if (gen_proto(&params, s->cmd, s->url))
367 goto on_error;
368 if (git_buf_oom(&params)) {
369 giterr_set_oom();
370 goto on_error;
373 if (git__utf8_to_16_alloc(&wideParams, params.ptr) < 0) {
374 giterr_set_oom();
375 goto on_error;
377 git_buf_free(&params);
379 length = wcslen(ssh) + wcslen(wideParams) + 3;
380 cmd = git__calloc(length, sizeof(wchar_t));
381 if (!cmd) {
382 giterr_set_oom();
383 goto on_error;
386 wcscat_s(cmd, length, L"\"");
387 wcscat_s(cmd, length, ssh);
388 wcscat_s(cmd, length, L"\"");
389 wcscat_s(cmd, length, wideParams);
391 if (command_start(cmd, &s->commandHandle, t->pEnv, isPutty ? CREATE_NEW_CONSOLE : DETACHED_PROCESS))
392 goto on_error;
394 git__free(wideParams);
395 git__free(cmd);
396 t->current_stream = s;
397 git__free(host);
398 git__free(port);
399 git__free(path);
400 git__free(user);
401 git__free(pass);
403 return 0;
405 on_error:
406 t->current_stream = NULL;
408 if (*stream)
409 ssh_stream_free(*stream);
411 git_buf_free(&params);
413 if (wideParams)
414 git__free(wideParams);
416 if (cmd)
417 git__free(cmd);
419 git__free(host);
420 git__free(port);
421 git__free(user);
422 git__free(pass);
424 return -1;
427 static int ssh_uploadpack_ls(
428 ssh_subtransport *t,
429 const char *url,
430 git_smart_subtransport_stream **stream)
432 const char *cmd = t->cmd_uploadpack ? t->cmd_uploadpack : cmd_uploadpack;
434 if (_git_ssh_setup_tunnel(t, url, cmd, stream) < 0)
435 return -1;
437 return 0;
440 static int ssh_uploadpack(
441 ssh_subtransport *t,
442 const char *url,
443 git_smart_subtransport_stream **stream)
445 GIT_UNUSED(url);
447 if (t->current_stream) {
448 *stream = &t->current_stream->parent;
449 return 0;
452 giterr_set(GITERR_NET, "Must call UPLOADPACK_LS before UPLOADPACK");
453 return -1;
456 static int ssh_receivepack_ls(
457 ssh_subtransport *t,
458 const char *url,
459 git_smart_subtransport_stream **stream)
461 const char *cmd = t->cmd_receivepack ? t->cmd_receivepack : cmd_receivepack;
463 if (_git_ssh_setup_tunnel(t, url, cmd, stream) < 0)
464 return -1;
466 return 0;
469 static int ssh_receivepack(
470 ssh_subtransport *t,
471 const char *url,
472 git_smart_subtransport_stream **stream)
474 GIT_UNUSED(url);
476 if (t->current_stream) {
477 *stream = &t->current_stream->parent;
478 return 0;
481 giterr_set(GITERR_NET, "Must call RECEIVEPACK_LS before RECEIVEPACK");
482 return -1;
485 static int _ssh_action(
486 git_smart_subtransport_stream **stream,
487 git_smart_subtransport *subtransport,
488 const char *url,
489 git_smart_service_t action)
491 ssh_subtransport *t = (ssh_subtransport *) subtransport;
493 switch (action) {
494 case GIT_SERVICE_UPLOADPACK_LS:
495 return ssh_uploadpack_ls(t, url, stream);
497 case GIT_SERVICE_UPLOADPACK:
498 return ssh_uploadpack(t, url, stream);
500 case GIT_SERVICE_RECEIVEPACK_LS:
501 return ssh_receivepack_ls(t, url, stream);
503 case GIT_SERVICE_RECEIVEPACK:
504 return ssh_receivepack(t, url, stream);
507 *stream = NULL;
508 return -1;
511 static int _ssh_close(git_smart_subtransport *subtransport)
513 ssh_subtransport *t = (ssh_subtransport *) subtransport;
515 assert(!t->current_stream);
517 return 0;
520 static void _ssh_free(git_smart_subtransport *subtransport)
522 ssh_subtransport *t = (ssh_subtransport *) subtransport;
524 assert(!t->current_stream);
526 git__free(t->cmd_uploadpack);
527 git__free(t->cmd_receivepack);
528 git__free(t->sshtoolpath);
529 git__free(t);
532 int git_smart_subtransport_ssh_wintunnel(
533 git_smart_subtransport **out, git_transport *owner, LPCWSTR sshtoolpath, LPWSTR pEnv)
535 ssh_subtransport *t;
537 assert(out);
539 t = git__calloc(sizeof(ssh_subtransport), 1);
540 if (!t) {
541 giterr_set_oom();
542 return -1;
545 t->sshtoolpath = wcsdup(sshtoolpath);
546 t->pEnv = pEnv;
548 t->owner = (transport_smart *)owner;
549 t->parent.action = _ssh_action;
550 t->parent.close = _ssh_close;
551 t->parent.free = _ssh_free;
553 *out = (git_smart_subtransport *) t;
554 return 0;