credential.con commit credential: add function for parsing url components (d3e847c)
   1#include "cache.h"
   2#include "credential.h"
   3#include "string-list.h"
   4#include "run-command.h"
   5#include "url.h"
   6
   7void credential_init(struct credential *c)
   8{
   9        memset(c, 0, sizeof(*c));
  10        c->helpers.strdup_strings = 1;
  11}
  12
  13void 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);
  21
  22        credential_init(c);
  23}
  24
  25static 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}
  37
  38static 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;
  43
  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);
  49
  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);
  55
  56        strbuf_release(&desc);
  57        strbuf_release(&prompt);
  58        return xstrdup(r);
  59}
  60
  61static 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}
  68
  69int credential_read(struct credential *c, FILE *fp)
  70{
  71        struct strbuf line = STRBUF_INIT;
  72
  73        while (strbuf_getline(&line, fp, '\n') != EOF) {
  74                char *key = line.buf;
  75                char *value = strchr(key, '=');
  76
  77                if (!line.len)
  78                        break;
  79
  80                if (!value) {
  81                        warning("invalid credential line: %s", key);
  82                        strbuf_release(&line);
  83                        return -1;
  84                }
  85                *value++ = '\0';
  86
  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        }
 109
 110        strbuf_release(&line);
 111        return 0;
 112}
 113
 114static void credential_write_item(FILE *fp, const char *key, const char *value)
 115{
 116        if (!value)
 117                return;
 118        fprintf(fp, "%s=%s\n", key, value);
 119}
 120
 121static void credential_write(const struct credential *c, FILE *fp)
 122{
 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);
 128}
 129
 130static int run_credential_helper(struct credential *c,
 131                                 const char *cmd,
 132                                 int want_output)
 133{
 134        struct child_process helper;
 135        const char *argv[] = { NULL, NULL };
 136        FILE *fp;
 137
 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;
 147
 148        if (start_command(&helper) < 0)
 149                return -1;
 150
 151        fp = xfdopen(helper.in, "w");
 152        credential_write(c, fp);
 153        fclose(fp);
 154
 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        }
 165
 166        if (finish_command(&helper))
 167                return -1;
 168        return 0;
 169}
 170
 171static int credential_do(struct credential *c, const char *helper,
 172                         const char *operation)
 173{
 174        struct strbuf cmd = STRBUF_INIT;
 175        int r;
 176
 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);
 183
 184        strbuf_addf(&cmd, " %s", operation);
 185        r = run_credential_helper(c, cmd.buf, !strcmp(operation, "get"));
 186
 187        strbuf_release(&cmd);
 188        return r;
 189}
 190
 191void credential_fill(struct credential *c)
 192{
 193        int i;
 194
 195        if (c->username && c->password)
 196                return;
 197
 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        }
 203
 204        credential_getpass(c);
 205        if (!c->username && !c->password)
 206                die("unable to get password from user");
 207}
 208
 209void credential_approve(struct credential *c)
 210{
 211        int i;
 212
 213        if (c->approved)
 214                return;
 215        if (!c->username || !c->password)
 216                return;
 217
 218        for (i = 0; i < c->helpers.nr; i++)
 219                credential_do(c, c->helpers.items[i].string, "store");
 220        c->approved = 1;
 221}
 222
 223void credential_reject(struct credential *c)
 224{
 225        int i;
 226
 227        for (i = 0; i < c->helpers.nr; i++)
 228                credential_do(c, c->helpers.items[i].string, "erase");
 229
 230        free(c->username);
 231        c->username = NULL;
 232        free(c->password);
 233        c->password = NULL;
 234        c->approved = 0;
 235}
 236
 237void credential_from_url(struct credential *c, const char *url)
 238{
 239        const char *at, *colon, *cp, *slash, *host, *proto_end;
 240
 241        credential_clear(c);
 242
 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, '/');
 256
 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        }
 271
 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        }
 286}