/* GNU Mailutils -- a suite of utilities for electronic mail Copyright (C) 2010 Free Software Foundation, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #ifdef HAVE_STRINGS_H # include #endif #include #include #include #include #include #include #include #include #include #include #include struct mu_url_ctx { int flags; const char *input; const char *cur; mu_url_t url; size_t passoff; char *tokbuf; size_t toksize; size_t toklen; }; static int getkn (struct mu_url_ctx *ctx, char *delim) { size_t n; if (*ctx->cur == 0) return -1; n = strcspn (ctx->cur, delim); if (n > ctx->toksize) { char *p = realloc (ctx->tokbuf, n + 1); if (!p) return ENOENT; ctx->toksize = n + 1; ctx->tokbuf = p; } memcpy (ctx->tokbuf, ctx->cur, n); ctx->tokbuf[n] = 0; ctx->toklen = n; ctx->cur += n; return 0; } #define INIT_ARRAY_SIZE 16 static int expand_array (size_t *pwc, char ***pwv, int incr) { size_t wc = *pwc; char **wv = *pwv; if (!wv) { wv = calloc (INIT_ARRAY_SIZE, sizeof (wv[0])); wc = INIT_ARRAY_SIZE; } else { if (incr) wc += incr; else { size_t newsize = wc * 2; if (newsize < wc) return ENOMEM; wc = newsize; } wv = realloc (wv, sizeof (wv[0]) * wc); } if (!wv) return ENOMEM; *pwv = wv; *pwc = wc; return 0; } static int parse_param (struct mu_url_ctx *ctx, char *delim, int *pargc, char ***pargv) { int rc; size_t wc = 0, wn = 0; char **wv = NULL; while ((rc = getkn (ctx, delim)) == 0) { if (wn == wc) { rc = expand_array (&wc, &wv, 0); if (rc) break; } wv[wn] = strdup (ctx->tokbuf); if (!wv[wn]) { rc = ENOMEM; break; } wn++; if (*ctx->cur != delim[0]) break; ctx->cur++; } if (rc == 0) { if (wn == wc) { rc = expand_array (&wc, &wv, 1); if (rc) { mu_argcv_free (wc, wv); return ENOMEM; } wv[wn] = NULL; } *pargv = realloc (wv, sizeof (wv[0]) * (wn + 1)); *pargc = wn; } else mu_argcv_free (wc, wv); return rc; } static int _mu_url_ctx_parse_query (struct mu_url_ctx *ctx) { int rc; ctx->cur++; rc = parse_param (ctx, "&", &ctx->url->qargc, &ctx->url->qargv); if (rc == 0 && ctx->url->qargc) ctx->url->flags |= MU_URL_QUERY; return rc; } static int _mu_url_ctx_parse_param (struct mu_url_ctx *ctx) { int rc; ctx->cur++; rc = parse_param (ctx, ";?", &ctx->url->fvcount, &ctx->url->fvpairs); if (rc) return rc; if (ctx->url->fvcount) ctx->url->flags |= MU_URL_PARAM; if (*ctx->cur == '?') return _mu_url_ctx_parse_query (ctx); return 0; } static int str_assign (char **ptr, const char *str) { *ptr = strdup (str); if (!*ptr) return ENOMEM; return 0; } static int _mu_url_ctx_parse_path (struct mu_url_ctx *ctx) { int rc; mu_url_t url = ctx->url; rc = getkn (ctx, ";?"); if (rc) return rc; rc = str_assign (&url->path, ctx->tokbuf); if (rc == 0) url->flags |= MU_URL_PATH; if (*ctx->cur == ';') return _mu_url_ctx_parse_param (ctx); if (*ctx->cur == '?') return _mu_url_ctx_parse_query (ctx); return 0; } static int _mu_url_ctx_parse_host (struct mu_url_ctx *ctx, int has_host) { int rc; mu_url_t url = ctx->url; rc = getkn (ctx, ":/;?"); if (rc) return rc; if (ctx->toklen) { rc = str_assign (&url->host, ctx->tokbuf); if (rc) return rc; url->flags |= MU_URL_HOST; has_host = 1; } if (*ctx->cur == ':') { ctx->cur++; has_host = 1; rc = getkn (ctx, "/;?"); if (rc) return rc; rc = str_assign (&url->portstr, ctx->tokbuf); if (rc) return rc; url->flags |= MU_URL_PORT; } if (*ctx->cur == '/') { if (has_host) ctx->cur++; return _mu_url_ctx_parse_path (ctx); } if (*ctx->cur == ';') return _mu_url_ctx_parse_param (ctx); if (*ctx->cur == '?') return _mu_url_ctx_parse_query (ctx); return 0; } static int _mu_url_ctx_parse_cred (struct mu_url_ctx *ctx) { int rc, has_cred; mu_url_t url = ctx->url; const char *save = ctx->cur; rc = getkn (ctx, "@"); if (rc) return rc; has_cred = *ctx->cur == '@'; /* restore the pointer */ ctx->cur = save; if (has_cred) { /* Try to split the user into a: : or :;AUTH= */ rc = getkn (ctx, ":;@"); if (rc) return rc; if (ctx->toklen) { rc = str_assign (&url->user, ctx->tokbuf); if (rc) return rc; url->flags |= MU_URL_USER; } if (*ctx->cur == ':') { ctx->cur++; ctx->passoff = ctx->cur - ctx->input; rc = getkn (ctx, ";@"); if (rc) return rc; if (ctx->toklen) { if (mu_secret_create (&url->secret, ctx->tokbuf, ctx->toklen)) return ENOMEM; else /* Clear password */ memset (ctx->tokbuf, 0, ctx->toklen); url->flags |= MU_URL_SECRET; } } if (*ctx->cur == ';') { ctx->cur++; rc = getkn (ctx, "@"); if (rc) return rc; /* Make sure it's the auth token. */ if (mu_c_strncasecmp (ctx->tokbuf, "auth=", 5) == 0) { rc = str_assign (&url->auth, ctx->tokbuf + 5); if (rc) return rc; url->flags |= MU_URL_AUTH; } } /* Skip @ sign */ ctx->cur++; } return _mu_url_ctx_parse_host (ctx, has_cred); } int _mu_url_ctx_parse (struct mu_url_ctx *ctx) { int rc; mu_url_t url = ctx->url; /* Parse the scheme part */ rc = getkn (ctx, ":/"); if (rc) return rc; if (*ctx->cur == ':') { rc = str_assign (&url->scheme, ctx->tokbuf); if (rc) return rc; url->flags |= MU_URL_SCHEME; ctx->cur++; } if (*ctx->cur == 0) return 0; if (ctx->cur[0] == '/' && ctx->cur[1] == '/') { ctx->cur += 2; return _mu_url_ctx_parse_cred (ctx); } return _mu_url_ctx_parse_path (ctx); } static int _mu_url_create_internal (struct mu_url_ctx *ctx, mu_url_t hint) { int rc; mu_url_t url = ctx->url; if ((ctx->flags & MU_URL_PARSE_PIPE) && ctx->input[0] == '|') { struct mu_wordsplit ws; rc = str_assign (&url->scheme, "prog"); if (rc) return rc; url->flags |= MU_URL_SCHEME; ctx->flags &= ~MU_URL_PARSE_HEXCODE; if (mu_wordsplit (ctx->input + 1, &ws, MU_WRDSF_DEFFLAGS)) return errno; url->qargc = ws.ws_wordc; url->qargv = ws.ws_wordv; ws.ws_wordc = 0; ws.ws_wordv = NULL; mu_wordsplit_free (&ws); url->flags |= MU_URL_QUERY; rc = str_assign (&url->path, url->qargv[0]); if (rc == 0) url->flags |= MU_URL_PATH; } else if ((ctx->flags & MU_URL_PARSE_SLASH) && ctx->input[0] == '/') { rc = str_assign (&url->scheme, "file"); if (rc) return rc; url->flags |= MU_URL_SCHEME; ctx->flags &= ~MU_URL_PARSE_HEXCODE; rc = str_assign (&url->path, ctx->input); if (rc == 0) url->flags |= MU_URL_PATH; } else rc = _mu_url_ctx_parse (ctx); if (rc) return rc; if (hint) { /* Fill in missing values */ rc = mu_url_copy_hints (url, hint); if (rc) return rc; } if (!(url->flags & MU_URL_SCHEME)) return MU_ERR_URL_MISS_PARTS; /* RFC 1738, section 2.1, lower the scheme case */ mu_strlower (url->scheme); if ((url->flags & MU_URL_PORT) && url->port == 0) { /* Convert port string to number */ unsigned long n; char *p; n = strtoul (url->portstr, &p, 10); if (*p) { if (ctx->flags & MU_URL_PARSE_PORTSRV) { /* FIXME: Another proto? */ struct servent *sp = getservbyname (url->portstr, "tcp"); if (!sp) return MU_ERR_TCP_NO_PORT; //FIXME: Error code? url->port = ntohs (sp->s_port); } else return MU_ERR_TCP_NO_PORT; } else if (n > USHRT_MAX) return ERANGE; else url->port = n; } if (ctx->flags & MU_URL_PARSE_HEXCODE) { /* Decode the %XX notations */ rc = mu_url_decode (url); if (rc) return rc; } if ((url->flags & MU_URL_SECRET) && (ctx->flags & MU_URL_PARSE_HIDEPASS)) { /* Obfuscate the password */ #define PASS_REPL "***" #define PASS_REPL_LEN (sizeof (PASS_REPL) - 1) size_t plen = mu_secret_length (url->secret); size_t nlen = strlen (url->name); size_t len = nlen - plen + PASS_REPL_LEN + 1; char *newname; memset (url->name + ctx->passoff, 0, plen); if (len > nlen + 1) { newname = realloc (url->name, len); if (!newname) return rc; url->name = newname; } else newname = url->name; memmove (newname + ctx->passoff + PASS_REPL_LEN, newname + ctx->passoff + plen, nlen - (ctx->passoff + plen) + 1); memcpy (newname + ctx->passoff, PASS_REPL, PASS_REPL_LEN); } return 0; } int mu_url_create_hint (mu_url_t *purl, const char *str, int flags, mu_url_t hint) { int rc; struct mu_url_ctx ctx; mu_url_t url = calloc (1, sizeof (*url)); if (url == NULL) return ENOMEM; url->name = strdup (str); if (!url->name) { free (url); return ENOMEM; } memset (&ctx, 0, sizeof (ctx)); ctx.flags = flags; ctx.input = str; ctx.cur = ctx.input; ctx.url = url; rc = _mu_url_create_internal (&ctx, hint); free (ctx.tokbuf); if (rc) mu_url_destroy (&url); else *purl = url; return rc; } int mu_url_create (mu_url_t *purl, const char *str) { return mu_url_create_hint (purl, str, MU_URL_PARSE_HEXCODE | MU_URL_PARSE_HIDEPASS | MU_URL_PARSE_PORTSRV | MU_URL_PARSE_PIPE | MU_URL_PARSE_SLASH, NULL); }