Coverage Report

Created: 2024-09-08 06:23

/src/git/builtin/remote-ext.c
Line
Count
Source (jump to first uncovered line)
1
#include "builtin.h"
2
#include "transport.h"
3
#include "run-command.h"
4
#include "pkt-line.h"
5
6
static const char usage_msg[] =
7
  "git remote-ext <remote> <url>";
8
9
/*
10
 * URL syntax:
11
 *  'command [arg1 [arg2 [...]]]' Invoke command with given arguments.
12
 *  Special characters:
13
 *  '% ': Literal space in argument.
14
 *  '%%': Literal percent sign.
15
 *  '%S': Name of service (git-upload-pack/git-upload-archive/
16
 *    git-receive-pack.
17
 *  '%s': Same as \s, but with possible git- prefix stripped.
18
 *  '%G': Only allowed as first 'character' of argument. Do not pass this
19
 *    Argument to command, instead send this as name of repository
20
 *    in in-line git://-style request (also activates sending this
21
 *    style of request).
22
 *  '%V': Only allowed as first 'character' of argument. Used in
23
 *    conjunction with '%G': Do not pass this argument to command,
24
 *    instead send this as vhost in git://-style request (note: does
25
 *    not activate sending git:// style request).
26
 */
27
28
static char *git_req;
29
static char *git_req_vhost;
30
31
static char *strip_escapes(const char *str, const char *service,
32
  const char **next)
33
0
{
34
0
  size_t rpos = 0;
35
0
  int escape = 0;
36
0
  char special = 0;
37
0
  const char *service_noprefix = service;
38
0
  struct strbuf ret = STRBUF_INIT;
39
40
0
  skip_prefix(service_noprefix, "git-", &service_noprefix);
41
42
  /* Pass the service to command. */
43
0
  setenv("GIT_EXT_SERVICE", service, 1);
44
0
  setenv("GIT_EXT_SERVICE_NOPREFIX", service_noprefix, 1);
45
46
  /* Scan the length of argument. */
47
0
  while (str[rpos] && (escape || str[rpos] != ' ')) {
48
0
    if (escape) {
49
0
      switch (str[rpos]) {
50
0
      case ' ':
51
0
      case '%':
52
0
      case 's':
53
0
      case 'S':
54
0
        break;
55
0
      case 'G':
56
0
      case 'V':
57
0
        special = str[rpos];
58
0
        if (rpos == 1)
59
0
          break;
60
        /* fallthrough */
61
0
      default:
62
0
        die("Bad remote-ext placeholder '%%%c'.",
63
0
          str[rpos]);
64
0
      }
65
0
      escape = 0;
66
0
    } else
67
0
      escape = (str[rpos] == '%');
68
0
    rpos++;
69
0
  }
70
0
  if (escape && !str[rpos])
71
0
    die("remote-ext command has incomplete placeholder");
72
0
  *next = str + rpos;
73
0
  if (**next == ' ')
74
0
    ++*next; /* Skip over space */
75
76
  /*
77
   * Do the actual placeholder substitution. The string will be short
78
   * enough not to overflow integers.
79
   */
80
0
  rpos = special ? 2 : 0;   /* Skip first 2 bytes in specials. */
81
0
  escape = 0;
82
0
  while (str[rpos] && (escape || str[rpos] != ' ')) {
83
0
    if (escape) {
84
0
      switch (str[rpos]) {
85
0
      case ' ':
86
0
      case '%':
87
0
        strbuf_addch(&ret, str[rpos]);
88
0
        break;
89
0
      case 's':
90
0
        strbuf_addstr(&ret, service_noprefix);
91
0
        break;
92
0
      case 'S':
93
0
        strbuf_addstr(&ret, service);
94
0
        break;
95
0
      }
96
0
      escape = 0;
97
0
    } else
98
0
      switch (str[rpos]) {
99
0
      case '%':
100
0
        escape = 1;
101
0
        break;
102
0
      default:
103
0
        strbuf_addch(&ret, str[rpos]);
104
0
        break;
105
0
      }
106
0
    rpos++;
107
0
  }
108
0
  switch (special) {
109
0
  case 'G':
110
0
    git_req = strbuf_detach(&ret, NULL);
111
0
    return NULL;
112
0
  case 'V':
113
0
    git_req_vhost = strbuf_detach(&ret, NULL);
114
0
    return NULL;
115
0
  default:
116
0
    return strbuf_detach(&ret, NULL);
117
0
  }
118
0
}
119
120
static void parse_argv(struct strvec *out, const char *arg, const char *service)
121
0
{
122
0
  while (*arg) {
123
0
    char *expanded = strip_escapes(arg, service, &arg);
124
0
    if (expanded)
125
0
      strvec_push(out, expanded);
126
0
    free(expanded);
127
0
  }
128
0
}
129
130
static void send_git_request(int stdin_fd, const char *serv, const char *repo,
131
  const char *vhost)
132
0
{
133
0
  if (!vhost)
134
0
    packet_write_fmt(stdin_fd, "%s %s%c", serv, repo, 0);
135
0
  else
136
0
    packet_write_fmt(stdin_fd, "%s %s%chost=%s%c", serv, repo, 0,
137
0
           vhost, 0);
138
0
}
139
140
static int run_child(const char *arg, const char *service)
141
0
{
142
0
  int r;
143
0
  struct child_process child = CHILD_PROCESS_INIT;
144
145
0
  child.in = -1;
146
0
  child.out = -1;
147
0
  child.err = 0;
148
0
  parse_argv(&child.args, arg, service);
149
150
0
  if (start_command(&child) < 0)
151
0
    die("Can't run specified command");
152
153
0
  if (git_req)
154
0
    send_git_request(child.in, service, git_req, git_req_vhost);
155
156
0
  r = bidirectional_transfer_loop(child.out, child.in);
157
0
  if (!r)
158
0
    r = finish_command(&child);
159
0
  else
160
0
    finish_command(&child);
161
0
  return r;
162
0
}
163
164
0
#define MAXCOMMAND 4096
165
166
static int command_loop(const char *child)
167
0
{
168
0
  char buffer[MAXCOMMAND];
169
170
0
  while (1) {
171
0
    size_t i;
172
0
    const char *arg;
173
174
0
    if (!fgets(buffer, MAXCOMMAND - 1, stdin)) {
175
0
      if (ferror(stdin))
176
0
        die("Command input error");
177
0
      exit(0);
178
0
    }
179
    /* Strip end of line characters. */
180
0
    i = strlen(buffer);
181
0
    while (i > 0 && isspace(buffer[i - 1]))
182
0
      buffer[--i] = 0;
183
184
0
    if (!strcmp(buffer, "capabilities")) {
185
0
      printf("*connect\n\n");
186
0
      fflush(stdout);
187
0
    } else if (skip_prefix(buffer, "connect ", &arg)) {
188
0
      printf("\n");
189
0
      fflush(stdout);
190
0
      return run_child(child, arg);
191
0
    } else {
192
0
      fprintf(stderr, "Bad command");
193
0
      return 1;
194
0
    }
195
0
  }
196
0
}
197
198
int cmd_remote_ext(int argc, const char **argv, const char *prefix)
199
0
{
200
0
  BUG_ON_NON_EMPTY_PREFIX(prefix);
201
202
0
  if (argc != 3)
203
0
    usage(usage_msg);
204
205
0
  return command_loop(argv[2]);
206
0
}