url.c 7.84 KB
Newer Older
1
/*
2
 * Copyright (C) 2000-2002,2004 Thomas Roessler <roessler@does-not-exist.org>
3 4 5 6 7 8 9 10 11 12 13 14 15
 * 
 *     This program is free software; you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation; either version 2 of the License, or
 *     (at your option) any later version.
 * 
 *     This program 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 General Public License for more details.
 * 
 *     You should have received a copy of the GNU General Public License
 *     along with this program; if not, write to the Free Software
16
 *     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 18 19 20 21 22
 */ 

/*
 * A simple URL parser.
 */

23 24 25 26
#if HAVE_CONFIG_H
# include "config.h"
#endif

27 28 29 30 31
#include "mutt.h"
#include "mapping.h"
#include "url.h"

#include "mime.h"
32
#include "rfc2047.h"
33 34 35

#include <ctype.h>

36
static const struct mapping_t UrlMap[] =
37 38 39 40
{
  { "file", 	U_FILE },
  { "imap", 	U_IMAP },
  { "imaps", 	U_IMAPS },
41 42
  { "pop",  	U_POP },
  { "pops", 	U_POPS },
43
  { "mailto",	U_MAILTO },
44 45 46
  { "smtp",     U_SMTP },
  { "smtps",    U_SMTPS },
  { NULL,	U_UNKNOWN }
47 48
};

Rocco Rutte's avatar
Rocco Rutte committed
49
static int url_pct_decode (char *s)
50 51
{
  char *d;
52 53

  if (!s)
Rocco Rutte's avatar
Rocco Rutte committed
54 55
    return -1;

56 57
  for (d = s; *s; s++)
  {
Rocco Rutte's avatar
Rocco Rutte committed
58
    if (*s == '%')
59
    {
Rocco Rutte's avatar
Rocco Rutte committed
60 61 62 63 64 65 66 67 68 69 70
      if (s[1] && s[2] &&
	  isxdigit ((unsigned char) s[1]) &&
	  isxdigit ((unsigned char) s[2]) &&
	  hexval (s[1]) >= 0 && hexval (s[2]) >= 0)
      {
	*d++ = (hexval (s[1]) << 4) | (hexval (s[2]));
	s += 2;
      }
      else
	return -1;
    } else
71 72 73
      *d++ = *s;
  }
  *d ='\0';
Rocco Rutte's avatar
Rocco Rutte committed
74
  return 0;
75 76 77 78 79 80 81
}

url_scheme_t url_check_scheme (const char *s)
{
  char sbuf[STRING];
  char *t;
  int i;
Rocco Rutte's avatar
Rocco Rutte committed
82

83
  if (!s || !(t = strchr (s, ':')))
84
    return U_UNKNOWN;
85
  if ((size_t)(t - s) >= sizeof (sbuf) - 1)
86
    return U_UNKNOWN;
Rocco Rutte's avatar
Rocco Rutte committed
87

88 89
  strfcpy (sbuf, s, t - s + 1);
  for (t = sbuf; *t; t++)
90
    *t = ascii_tolower (*t);
91 92 93 94 95 96 97 98 99

  if ((i = mutt_getvaluebyname (sbuf, UrlMap)) == -1)
    return U_UNKNOWN;
  else
    return (url_scheme_t) i;
}

int url_parse_file (char *d, const char *src, size_t dl)
{
100
  if (ascii_strncasecmp (src, "file:", 5))
101
    return -1;
102
  else if (!ascii_strncasecmp (src, "file://", 7))	/* we don't support remote files */
103 104 105
    return -1;
  else
    strfcpy (d, src + 5, dl);
Rocco Rutte's avatar
Rocco Rutte committed
106 107

  return url_pct_decode (d);
108 109
}

Thomas Roessler's avatar
Thomas Roessler committed
110 111 112
/* ciss_parse_userhost: fill in components of ciss with info from src. Note
 *   these are pointers into src, which is altered with '\0's. Port of 0
 *   means no port given. */
Rocco Rutte's avatar
Rocco Rutte committed
113
static int ciss_parse_userhost (ciss_url_t *ciss, char *src)
114
{
Rocco Rutte's avatar
Rocco Rutte committed
115
  char *t, *p;
116 117 118 119 120 121

  ciss->user = NULL;
  ciss->pass = NULL;
  ciss->host = NULL;
  ciss->port = 0;

Rocco Rutte's avatar
Rocco Rutte committed
122 123 124 125 126 127
  if (strncmp (src, "//", 2) != 0)
  {
    ciss->path = src;
    return url_pct_decode (ciss->path);
  }

128
  src += 2;
129

Rocco Rutte's avatar
Rocco Rutte committed
130 131 132
  if ((ciss->path = strchr (src, '/')))
    *ciss->path++ = '\0';

133
  if ((t = strrchr (src, '@')))
134 135 136 137 138
  {
    *t = '\0';
    if ((p = strchr (src, ':')))
    {
      *p = '\0';
Thomas Roessler's avatar
Thomas Roessler committed
139
      ciss->pass = p + 1;
Rocco Rutte's avatar
Rocco Rutte committed
140 141
      if (url_pct_decode (ciss->pass) < 0)
	return -1;
142
    }
Thomas Roessler's avatar
Thomas Roessler committed
143
    ciss->user = src;
Rocco Rutte's avatar
Rocco Rutte committed
144 145
    if (url_pct_decode (ciss->user) < 0)
      return -1;
146 147 148 149 150 151 152 153 154 155
    src = t + 1;
  }

  /* IPv6 literal address.  It may contain colons, so set t to start
   * the port scan after it.
   */
  if ((*src == '[') && (t = strchr (src, ']')))
  {
    src++;
    *t++ = '\0';
156 157 158
  }
  else
    t = src;
Rocco Rutte's avatar
Rocco Rutte committed
159

160 161
  if ((p = strchr (t, ':')))
  {
162
    int t;
163
    *p++ = '\0';
164
    if (mutt_atoi (p, &t) < 0 || t < 0 || t > 0xffff)
Rocco Rutte's avatar
Rocco Rutte committed
165
      return -1;
166
    ciss->port = (unsigned short)t;
167 168 169
  }
  else
    ciss->port = 0;
Rocco Rutte's avatar
Rocco Rutte committed
170

171
  ciss->host = src;
Rocco Rutte's avatar
Rocco Rutte committed
172 173
  return url_pct_decode (ciss->host) >= 0 &&
    (!ciss->path || url_pct_decode (ciss->path) >= 0) ? 0 : -1;
174 175
}

176 177
/* url_parse_ciss: Fill in ciss_url_t. char* elements are pointers into src,
 *   which is modified by this call (duplicate it first if you need to). */
Thomas Roessler's avatar
Thomas Roessler committed
178
int url_parse_ciss (ciss_url_t *ciss, char *src)
179
{
Thomas Roessler's avatar
Thomas Roessler committed
180 181 182 183 184 185 186
  char *tmp;

  if ((ciss->scheme = url_check_scheme (src)) == U_UNKNOWN)
    return -1;

  tmp = strchr (src, ':') + 1;

Rocco Rutte's avatar
Rocco Rutte committed
187
  return ciss_parse_userhost (ciss, tmp);
188 189
}

190
static void url_pct_encode (char *dst, size_t l, const char *src)
Rocco Rutte's avatar
Rocco Rutte committed
191 192
{
  static const char *alph = "0123456789ABCDEF";
193

Rocco Rutte's avatar
Rocco Rutte committed
194 195 196 197
  *dst = 0;
  l--;
  while (src && *src && l)
  {
198
    if (strchr ("/:%", *src) && l > 3)
Rocco Rutte's avatar
Rocco Rutte committed
199 200 201 202 203 204 205 206 207 208 209 210 211
    {
      *dst++ = '%';
      *dst++ = alph[(*src >> 4) & 0xf];
      *dst++ = alph[*src & 0xf];
      src++;
      continue;
    }
    *dst++ = *src++;
  }
  *dst = 0;
}

/* url_ciss_tostring: output the URL string for a given CISS object. */
Thomas Roessler's avatar
Thomas Roessler committed
212
int url_ciss_tostring (ciss_url_t* ciss, char* dest, size_t len, int flags)
213
{
214 215
  long l;

Thomas Roessler's avatar
Thomas Roessler committed
216
  if (ciss->scheme == U_UNKNOWN)
217 218
    return -1;

Thomas Roessler's avatar
Thomas Roessler committed
219 220 221 222
  snprintf (dest, len, "%s:", mutt_getnamebyvalue (ciss->scheme, UrlMap));

  if (ciss->host)
  {
223 224
    if (!(flags & U_PATH))
      safe_strcat (dest, len, "//");
225
    len -= (l = strlen (dest)); dest += l;
Rocco Rutte's avatar
Rocco Rutte committed
226 227 228 229

    if (ciss->user)
    {
      char u[STRING];
230
      url_pct_encode (u, sizeof (u), ciss->user);
Rocco Rutte's avatar
Rocco Rutte committed
231

Thomas Roessler's avatar
Thomas Roessler committed
232
      if (flags & U_DECODE_PASSWD && ciss->pass)
Rocco Rutte's avatar
Rocco Rutte committed
233 234
      {
	char p[STRING];
235
	url_pct_encode (p, sizeof (p), ciss->pass);
Rocco Rutte's avatar
Rocco Rutte committed
236 237
	snprintf (dest, len, "%s:%s@", u, p);
      }
Thomas Roessler's avatar
Thomas Roessler committed
238
      else
Rocco Rutte's avatar
Rocco Rutte committed
239
	snprintf (dest, len, "%s@", u);
240 241

      len -= (l = strlen (dest)); dest += l;
Thomas Roessler's avatar
Thomas Roessler committed
242
    }
Thomas Roessler's avatar
Thomas Roessler committed
243

244 245 246 247 248 249 250
    if (strchr (ciss->host, ':'))
      snprintf (dest, len, "[%s]", ciss->host);
    else
      snprintf (dest, len, "%s", ciss->host);

    len -= (l = strlen (dest)); dest += l;

Thomas Roessler's avatar
Thomas Roessler committed
251
    if (ciss->port)
252
      snprintf (dest, len, ":%hu/", ciss->port);
Thomas Roessler's avatar
Thomas Roessler committed
253
    else
254
      snprintf (dest, len, "/");
Thomas Roessler's avatar
Thomas Roessler committed
255 256 257
  }

  if (ciss->path)
258
    safe_strcat (dest, len, ciss->path);
259 260 261 262 263 264

  return 0;
}

int url_parse_mailto (ENVELOPE *e, char **body, const char *src)
{
265
  char *t, *p;
266 267 268 269
  char *tmp;
  char *headers;
  char *tag, *value;

270
  int rc = -1;
271 272

  LIST *last = NULL;
Rocco Rutte's avatar
Rocco Rutte committed
273

274 275
  if (!(t = strchr (src, ':')))
    return -1;
Rocco Rutte's avatar
Rocco Rutte committed
276

277
  /* copy string for safe use of strtok() */
Thomas Roessler's avatar
Thomas Roessler committed
278 279 280
  if ((tmp = safe_strdup (t + 1)) == NULL)
    return -1;

281 282 283
  if ((headers = strchr (tmp, '?')))
    *headers++ = '\0';

Rocco Rutte's avatar
Rocco Rutte committed
284
  if (url_pct_decode (tmp) < 0)
285 286
    goto out;

287 288
  e->to = rfc822_parse_adrlist (e->to, tmp);

289
  tag = headers ? strtok_r (headers, "&", &p) : NULL;
Rocco Rutte's avatar
Rocco Rutte committed
290

291
  for (; tag; tag = strtok_r (NULL, "&", &p))
292 293 294 295 296 297
  {
    if ((value = strchr (tag, '=')))
      *value++ = '\0';
    if (!value || !*value)
      continue;

Rocco Rutte's avatar
Rocco Rutte committed
298
    if (url_pct_decode (tag) < 0)
299
      goto out;
Rocco Rutte's avatar
Rocco Rutte committed
300
    if (url_pct_decode (value) < 0)
301
      goto out;
302

303 304 305 306 307 308 309 310 311 312 313 314
    /* Determine if this header field is on the allowed list.  Since Mutt
     * interprets some header fields specially (such as
     * "Attach: ~/.gnupg/secring.gpg"), care must be taken to ensure that
     * only safe fields are allowed.
     *
     * RFC2368, "4. Unsafe headers"
     * The user agent interpreting a mailto URL SHOULD choose not to create
     * a message if any of the headers are considered dangerous; it may also
     * choose to create a message with only a subset of the headers given in
     * the URL.
     */
    if (mutt_matches_ignore(tag, MailtoAllow))
315
    {
316 317 318 319 320 321 322 323 324 325
      if (!ascii_strcasecmp (tag, "body"))
      {
	if (body)
	  mutt_str_replace (body, value);
      }
      else
      {
	char *scratch;
	size_t taglen = mutt_strlen (tag);

326
	safe_asprintf (&scratch, "%s: %s", tag, value);
327 328
	scratch[taglen] = 0; /* overwrite the colon as mutt_parse_rfc822_line expects */
	value = skip_email_wsp(&scratch[taglen + 1]);
329
	mutt_parse_rfc822_line (e, NULL, scratch, value, 1, 0, 1, &last);
330 331
	FREE (&scratch);
      }
332
    }
333
  }
334

335 336 337 338 339 340 341 342 343 344 345 346
  /* RFC2047 decode after the RFC822 parsing */
  rfc2047_decode_adrlist (e->from);
  rfc2047_decode_adrlist (e->to);
  rfc2047_decode_adrlist (e->cc);
  rfc2047_decode_adrlist (e->bcc);
  rfc2047_decode_adrlist (e->reply_to);
  rfc2047_decode_adrlist (e->mail_followup_to);
  rfc2047_decode_adrlist (e->return_path);
  rfc2047_decode_adrlist (e->sender);
  rfc2047_decode (&e->x_label);
  rfc2047_decode (&e->subject);

347 348
  rc = 0;

349
out:
350
  FREE (&tmp);
351
  return rc;
352 353
}