Code

credential: add function for parsing url components
[git.git] / credential.c
1 #include "cache.h"
2 #include "credential.h"
3 #include "string-list.h"
4 #include "run-command.h"
5 #include "url.h"
7 void credential_init(struct credential *c)
8 {
9         memset(c, 0, sizeof(*c));
10         c->helpers.strdup_strings = 1;
11 }
13 void credential_clear(struct credential *c)
14 {
15         free(c->protocol);
16         free(c->host);
17         free(c->path);
18         free(c->username);
19         free(c->password);
20         string_list_clear(&c->helpers, 0);
22         credential_init(c);
23 }
25 static void credential_describe(struct credential *c, struct strbuf *out)
26 {
27         if (!c->protocol)
28                 return;
29         strbuf_addf(out, "%s://", c->protocol);
30         if (c->username && *c->username)
31                 strbuf_addf(out, "%s@", c->username);
32         if (c->host)
33                 strbuf_addstr(out, c->host);
34         if (c->path)
35                 strbuf_addf(out, "/%s", c->path);
36 }
38 static char *credential_ask_one(const char *what, struct credential *c)
39 {
40         struct strbuf desc = STRBUF_INIT;
41         struct strbuf prompt = STRBUF_INIT;
42         char *r;
44         credential_describe(c, &desc);
45         if (desc.len)
46                 strbuf_addf(&prompt, "%s for '%s': ", what, desc.buf);
47         else
48                 strbuf_addf(&prompt, "%s: ", what);
50         /* FIXME: for usernames, we should do something less magical that
51          * actually echoes the characters. However, we need to read from
52          * /dev/tty and not stdio, which is not portable (but getpass will do
53          * it for us). http.c uses the same workaround. */
54         r = git_getpass(prompt.buf);
56         strbuf_release(&desc);
57         strbuf_release(&prompt);
58         return xstrdup(r);
59 }
61 static void credential_getpass(struct credential *c)
62 {
63         if (!c->username)
64                 c->username = credential_ask_one("Username", c);
65         if (!c->password)
66                 c->password = credential_ask_one("Password", c);
67 }
69 int credential_read(struct credential *c, FILE *fp)
70 {
71         struct strbuf line = STRBUF_INIT;
73         while (strbuf_getline(&line, fp, '\n') != EOF) {
74                 char *key = line.buf;
75                 char *value = strchr(key, '=');
77                 if (!line.len)
78                         break;
80                 if (!value) {
81                         warning("invalid credential line: %s", key);
82                         strbuf_release(&line);
83                         return -1;
84                 }
85                 *value++ = '\0';
87                 if (!strcmp(key, "username")) {
88                         free(c->username);
89                         c->username = xstrdup(value);
90                 } else if (!strcmp(key, "password")) {
91                         free(c->password);
92                         c->password = xstrdup(value);
93                 } else if (!strcmp(key, "protocol")) {
94                         free(c->protocol);
95                         c->protocol = xstrdup(value);
96                 } else if (!strcmp(key, "host")) {
97                         free(c->host);
98                         c->host = xstrdup(value);
99                 } else if (!strcmp(key, "path")) {
100                         free(c->path);
101                         c->path = xstrdup(value);
102                 }
103                 /*
104                  * Ignore other lines; we don't know what they mean, but
105                  * this future-proofs us when later versions of git do
106                  * learn new lines, and the helpers are updated to match.
107                  */
108         }
110         strbuf_release(&line);
111         return 0;
114 static void credential_write_item(FILE *fp, const char *key, const char *value)
116         if (!value)
117                 return;
118         fprintf(fp, "%s=%s\n", key, value);
121 static void credential_write(const struct credential *c, FILE *fp)
123         credential_write_item(fp, "protocol", c->protocol);
124         credential_write_item(fp, "host", c->host);
125         credential_write_item(fp, "path", c->path);
126         credential_write_item(fp, "username", c->username);
127         credential_write_item(fp, "password", c->password);
130 static int run_credential_helper(struct credential *c,
131                                  const char *cmd,
132                                  int want_output)
134         struct child_process helper;
135         const char *argv[] = { NULL, NULL };
136         FILE *fp;
138         memset(&helper, 0, sizeof(helper));
139         argv[0] = cmd;
140         helper.argv = argv;
141         helper.use_shell = 1;
142         helper.in = -1;
143         if (want_output)
144                 helper.out = -1;
145         else
146                 helper.no_stdout = 1;
148         if (start_command(&helper) < 0)
149                 return -1;
151         fp = xfdopen(helper.in, "w");
152         credential_write(c, fp);
153         fclose(fp);
155         if (want_output) {
156                 int r;
157                 fp = xfdopen(helper.out, "r");
158                 r = credential_read(c, fp);
159                 fclose(fp);
160                 if (r < 0) {
161                         finish_command(&helper);
162                         return -1;
163                 }
164         }
166         if (finish_command(&helper))
167                 return -1;
168         return 0;
171 static int credential_do(struct credential *c, const char *helper,
172                          const char *operation)
174         struct strbuf cmd = STRBUF_INIT;
175         int r;
177         if (helper[0] == '!')
178                 strbuf_addstr(&cmd, helper + 1);
179         else if (is_absolute_path(helper))
180                 strbuf_addstr(&cmd, helper);
181         else
182                 strbuf_addf(&cmd, "git credential-%s", helper);
184         strbuf_addf(&cmd, " %s", operation);
185         r = run_credential_helper(c, cmd.buf, !strcmp(operation, "get"));
187         strbuf_release(&cmd);
188         return r;
191 void credential_fill(struct credential *c)
193         int i;
195         if (c->username && c->password)
196                 return;
198         for (i = 0; i < c->helpers.nr; i++) {
199                 credential_do(c, c->helpers.items[i].string, "get");
200                 if (c->username && c->password)
201                         return;
202         }
204         credential_getpass(c);
205         if (!c->username && !c->password)
206                 die("unable to get password from user");
209 void credential_approve(struct credential *c)
211         int i;
213         if (c->approved)
214                 return;
215         if (!c->username || !c->password)
216                 return;
218         for (i = 0; i < c->helpers.nr; i++)
219                 credential_do(c, c->helpers.items[i].string, "store");
220         c->approved = 1;
223 void credential_reject(struct credential *c)
225         int i;
227         for (i = 0; i < c->helpers.nr; i++)
228                 credential_do(c, c->helpers.items[i].string, "erase");
230         free(c->username);
231         c->username = NULL;
232         free(c->password);
233         c->password = NULL;
234         c->approved = 0;
237 void credential_from_url(struct credential *c, const char *url)
239         const char *at, *colon, *cp, *slash, *host, *proto_end;
241         credential_clear(c);
243         /*
244          * Match one of:
245          *   (1) proto://<host>/...
246          *   (2) proto://<user>@<host>/...
247          *   (3) proto://<user>:<pass>@<host>/...
248          */
249         proto_end = strstr(url, "://");
250         if (!proto_end)
251                 return;
252         cp = proto_end + 3;
253         at = strchr(cp, '@');
254         colon = strchr(cp, ':');
255         slash = strchrnul(cp, '/');
257         if (!at || slash <= at) {
258                 /* Case (1) */
259                 host = cp;
260         }
261         else if (!colon || at <= colon) {
262                 /* Case (2) */
263                 c->username = url_decode_mem(cp, at - cp);
264                 host = at + 1;
265         } else {
266                 /* Case (3) */
267                 c->username = url_decode_mem(cp, colon - cp);
268                 c->password = url_decode_mem(colon + 1, at - (colon + 1));
269                 host = at + 1;
270         }
272         if (proto_end - url > 0)
273                 c->protocol = xmemdupz(url, proto_end - url);
274         if (slash - host > 0)
275                 c->host = url_decode_mem(host, slash - host);
276         /* Trim leading and trailing slashes from path */
277         while (*slash == '/')
278                 slash++;
279         if (*slash) {
280                 char *p;
281                 c->path = url_decode(slash);
282                 p = c->path + strlen(c->path) - 1;
283                 while (p > c->path && *p == '/')
284                         *p-- = '\0';
285         }