]> sjero.net Git - wget/blob - src/url.c
[svn] Just clarified a comment in the fix I just committed.
[wget] / src / url.c
1 /* URL handling.
2    Copyright (C) 1995, 1996, 1997, 2000 Free Software Foundation, Inc.
3
4 This file is part of Wget.
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or (at
9 your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
19
20 #include <config.h>
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #ifdef HAVE_STRING_H
25 # include <string.h>
26 #else
27 # include <strings.h>
28 #endif
29 #include <ctype.h>
30 #include <sys/types.h>
31 #ifdef HAVE_UNISTD_H
32 # include <unistd.h>
33 #endif
34 #include <errno.h>
35 #include <assert.h>
36
37 #include "wget.h"
38 #include "utils.h"
39 #include "url.h"
40 #include "host.h"
41
42 #ifndef errno
43 extern int errno;
44 #endif
45
46 /* Default port definitions */
47 #define DEFAULT_HTTP_PORT 80
48 #define DEFAULT_FTP_PORT 21
49 #define DEFAULT_HTTPS_PORT 443
50
51 /* Table of Unsafe chars.  This is intialized in
52    init_unsafe_char_table.  */
53
54 static char unsafe_char_table[256];
55
56 #define UNSAFE_CHAR(c) (unsafe_char_table[(unsigned char)(c)])
57
58 /* If S contains unsafe characters, free it and replace it with a
59    version that doesn't.  */
60 #define URL_CLEANSE(s) do                       \
61 {                                               \
62   if (contains_unsafe (s))                      \
63     {                                           \
64       char *uc_tmp = encode_string (s);         \
65       xfree (s);                                \
66       (s) = uc_tmp;                             \
67     }                                           \
68 } while (0)
69
70 /* Is a directory "."?  */
71 #define DOTP(x) ((*(x) == '.') && (!*(x + 1)))
72 /* Is a directory ".."?  */
73 #define DDOTP(x) ((*(x) == '.') && (*(x + 1) == '.') && (!*(x + 2)))
74
75 #if 0
76 static void path_simplify_with_kludge PARAMS ((char *));
77 #endif
78 static int urlpath_length PARAMS ((const char *));
79
80 /* NULL-terminated list of strings to be recognized as prototypes (URL
81    schemes).  Note that recognized doesn't mean supported -- only HTTP,
82    HTTPS and FTP are currently supported .
83
84    However, a string that does not match anything in the list will be
85    considered a relative URL.  Thus it's important that this list has
86    anything anyone could think of being legal.
87
88    There are wild things here.  :-) Take a look at
89    <URL:http://www.w3.org/pub/WWW/Addressing/schemes.html> for more
90    fun.  */
91 static char *protostrings[] =
92 {
93   "cid:",
94   "clsid:",
95   "file:",
96   "finger:",
97   "ftp:",
98   "gopher:",
99   "hdl:",
100   "http:",
101   "https:",
102   "ilu:",
103   "ior:",
104   "irc:",
105   "java:",
106   "javascript:",
107   "lifn:",
108   "mailto:",
109   "mid:",
110   "news:",
111   "nntp:",
112   "path:",
113   "prospero:",
114   "rlogin:",
115   "service:",
116   "shttp:",
117   "snews:",
118   "stanf:",
119   "telnet:",
120   "tn3270:",
121   "wais:",
122   "whois++:",
123   NULL
124 };
125
126 struct proto
127 {
128   char *name;
129   uerr_t ind;
130   unsigned short port;
131 };
132
133 /* Similar to former, but for supported protocols: */
134 static struct proto sup_protos[] =
135 {
136   { "http://", URLHTTP, DEFAULT_HTTP_PORT },
137 #ifdef HAVE_SSL
138   { "https://",URLHTTPS, DEFAULT_HTTPS_PORT},
139 #endif
140   { "ftp://", URLFTP, DEFAULT_FTP_PORT },
141   /*{ "file://", URLFILE, DEFAULT_FTP_PORT },*/
142 };
143
144 static void parse_dir PARAMS ((const char *, char **, char **));
145 static uerr_t parse_uname PARAMS ((const char *, char **, char **));
146 static char *construct PARAMS ((const char *, const char *, int , int));
147 static char *construct_relative PARAMS ((const char *, const char *));
148 static char process_ftp_type PARAMS ((char *));
149
150 \f
151 /* Returns the number of characters to be skipped if the first thing
152    in a URL is URL: (which is 0 or 4+).  The optional spaces after
153    URL: are also skipped.  */
154 int
155 skip_url (const char *url)
156 {
157   int i;
158
159   if (TOUPPER (url[0]) == 'U'
160       && TOUPPER (url[1]) == 'R'
161       && TOUPPER (url[2]) == 'L'
162       && url[3] == ':')
163     {
164       /* Skip blanks.  */
165       for (i = 4; url[i] && ISSPACE (url[i]); i++);
166       return i;
167     }
168   else
169     return 0;
170 }
171
172 /* Unsafe chars:
173    - anything <= 32;
174    - stuff from rfc1738 ("<>\"#%{}|\\^~[]`");
175    - @ and :, for user/password encoding.
176    - everything over 127 (but we don't bother with recording those.  */
177 void
178 init_unsafe_char_table (void)
179 {
180   int i;
181   for (i = 0; i < 256; i++)
182     if (i < 32 || i >= 127
183         || i == ' '
184         || i == '<'
185         || i == '>'
186         || i == '\"'
187         || i == '#'
188         || i == '%'
189         || i == '{'
190         || i == '}'
191         || i == '|'
192         || i == '\\'
193         || i == '^'
194         || i == '~'
195         || i == '['
196         || i == ']'
197         || i == '`')
198       unsafe_char_table[i] = 1;
199 }
200
201 /* Returns 1 if the string contains unsafe characters, 0 otherwise.  */
202 int
203 contains_unsafe (const char *s)
204 {
205   for (; *s; s++)
206     if (UNSAFE_CHAR (*s))
207       return 1;
208   return 0;
209 }
210
211 /* Decodes the forms %xy in a URL to the character the hexadecimal
212    code of which is xy.  xy are hexadecimal digits from
213    [0123456789ABCDEF] (case-insensitive).  If x or y are not
214    hex-digits or `%' precedes `\0', the sequence is inserted
215    literally.  */
216
217 static void
218 decode_string (char *s)
219 {
220   char *p = s;
221
222   for (; *s; s++, p++)
223     {
224       if (*s != '%')
225         *p = *s;
226       else
227         {
228           /* Do nothing if at the end of the string, or if the chars
229              are not hex-digits.  */
230           if (!*(s + 1) || !*(s + 2)
231               || !(ISXDIGIT (*(s + 1)) && ISXDIGIT (*(s + 2))))
232             {
233               *p = *s;
234               continue;
235             }
236           *p = (ASC2HEXD (*(s + 1)) << 4) + ASC2HEXD (*(s + 2));
237           s += 2;
238         }
239     }
240   *p = '\0';
241 }
242
243 /* Encode the unsafe characters (as determined by URL_UNSAFE) in a
244    given string, returning a malloc-ed %XX encoded string.  */
245 char *
246 encode_string (const char *s)
247 {
248   const char *b;
249   char *p, *res;
250   int i;
251
252   b = s;
253   for (i = 0; *s; s++, i++)
254     if (UNSAFE_CHAR (*s))
255       i += 2; /* Two more characters (hex digits) */
256   res = (char *)xmalloc (i + 1);
257   s = b;
258   for (p = res; *s; s++)
259     if (UNSAFE_CHAR (*s))
260       {
261         const unsigned char c = *s;
262         *p++ = '%';
263         *p++ = HEXD2ASC (c >> 4);
264         *p++ = HEXD2ASC (c & 0xf);
265       }
266     else
267       *p++ = *s;
268   *p = '\0';
269   return res;
270 }
271 \f
272 /* Returns the proto-type if URL's protocol is supported, or
273    URLUNKNOWN if not.  */
274 uerr_t
275 urlproto (const char *url)
276 {
277   int i;
278
279   url += skip_url (url);
280   for (i = 0; i < ARRAY_SIZE (sup_protos); i++)
281     if (!strncasecmp (url, sup_protos[i].name, strlen (sup_protos[i].name)))
282       return sup_protos[i].ind;
283   for (i = 0; url[i] && url[i] != ':' && url[i] != '/'; i++);
284   if (url[i] == ':')
285     {
286       for (++i; url[i] && url[i] != '/'; i++)
287         if (!ISDIGIT (url[i]))
288           return URLBADPORT;
289       if (url[i - 1] == ':')
290         return URLFTP;
291       else
292         return URLHTTP;
293     }
294   else
295     return URLHTTP;
296 }
297
298 /* Skip the protocol part of the URL, e.g. `http://'.  If no protocol
299    part is found, returns 0.  */
300 int
301 skip_proto (const char *url)
302 {
303   char **s;
304   int l;
305
306   for (s = protostrings; *s; s++)
307     if (!strncasecmp (*s, url, strlen (*s)))
308       break;
309   if (!*s)
310     return 0;
311   l = strlen (*s);
312   /* HTTP and FTP protocols are expected to yield exact host names
313      (i.e. the `//' part must be skipped, too).  */
314   if (!strcmp (*s, "http:") || !strcmp (*s, "ftp:"))
315     l += 2;
316   return l;
317 }
318
319 /* Returns 1 if the URL begins with a protocol (supported or
320    unsupported), 0 otherwise.  */
321 int
322 has_proto (const char *url)
323 {
324   char **s;
325
326   url += skip_url (url);
327   for (s = protostrings; *s; s++)
328     if (strncasecmp (url, *s, strlen (*s)) == 0)
329       return 1;
330   return 0;
331 }
332
333 /* Skip the username and password, if present here.  The function
334    should be called *not* with the complete URL, but with the part
335    right after the protocol.
336
337    If no username and password are found, return 0.  */
338 int
339 skip_uname (const char *url)
340 {
341   const char *p;
342   for (p = url; *p && *p != '/'; p++)
343     if (*p == '@')
344       break;
345   /* If a `@' was found before the first occurrence of `/', skip
346      it.  */
347   if (*p == '@')
348     return p - url + 1;
349   else
350     return 0;
351 }
352 \f
353 /* Allocate a new urlinfo structure, fill it with default values and
354    return a pointer to it.  */
355 struct urlinfo *
356 newurl (void)
357 {
358   struct urlinfo *u;
359
360   u = (struct urlinfo *)xmalloc (sizeof (struct urlinfo));
361   memset (u, 0, sizeof (*u));
362   u->proto = URLUNKNOWN;
363   return u;
364 }
365
366 /* Perform a "deep" free of the urlinfo structure.  The structure
367    should have been created with newurl, but need not have been used.
368    If free_pointer is non-0, free the pointer itself.  */
369 void
370 freeurl (struct urlinfo *u, int complete)
371 {
372   assert (u != NULL);
373   FREE_MAYBE (u->url);
374   FREE_MAYBE (u->host);
375   FREE_MAYBE (u->path);
376   FREE_MAYBE (u->file);
377   FREE_MAYBE (u->dir);
378   FREE_MAYBE (u->user);
379   FREE_MAYBE (u->passwd);
380   FREE_MAYBE (u->local);
381   FREE_MAYBE (u->referer);
382   if (u->proxy)
383     freeurl (u->proxy, 1);
384   if (complete)
385     xfree (u);
386   return;
387 }
388 \f
389 /* Extract the given URL of the form
390    (http:|ftp:)// (user (:password)?@)?hostname (:port)? (/path)?
391    1. hostname (terminated with `/' or `:')
392    2. port number (terminated with `/'), or chosen for the protocol
393    3. dirname (everything after hostname)
394    Most errors are handled.  No allocation is done, you must supply
395    pointers to allocated memory.
396    ...and a host of other stuff :-)
397
398    - Recognizes hostname:dir/file for FTP and
399      hostname (:portnum)?/dir/file for HTTP.
400    - Parses the path to yield directory and file
401    - Parses the URL to yield the username and passwd (if present)
402    - Decodes the strings, in case they contain "forbidden" characters
403    - Writes the result to struct urlinfo
404
405    If the argument STRICT is set, it recognizes only the canonical
406    form.  */
407 uerr_t
408 parseurl (const char *url, struct urlinfo *u, int strict)
409 {
410   int i, l, abs_ftp;
411   int recognizable;            /* Recognizable URL is the one where
412                                   the protocol name was explicitly
413                                   named, i.e. it wasn't deduced from
414                                   the URL format.  */
415   uerr_t type;
416
417   DEBUGP (("parseurl (\"%s\") -> ", url));
418   url += skip_url (url);
419   recognizable = has_proto (url);
420   if (strict && !recognizable)
421     return URLUNKNOWN;
422   for (i = 0, l = 0; i < ARRAY_SIZE (sup_protos); i++)
423     {
424       l = strlen (sup_protos[i].name);
425       if (!strncasecmp (sup_protos[i].name, url, l))
426         break;
427     }
428   /* If protocol is recognizable, but unsupported, bail out, else
429      suppose unknown.  */
430   if (recognizable && i == ARRAY_SIZE (sup_protos))
431     return URLUNKNOWN;
432   else if (i == ARRAY_SIZE (sup_protos))
433     type = URLUNKNOWN;
434   else
435     u->proto = type = sup_protos[i].ind;
436
437   if (type == URLUNKNOWN)
438     l = 0;
439   /* Allow a username and password to be specified (i.e. just skip
440      them for now).  */
441   if (recognizable)
442     l += skip_uname (url + l);
443   for (i = l; url[i] && url[i] != ':' && url[i] != '/'; i++);
444   if (i == l)
445     return URLBADHOST;
446   /* Get the hostname.  */
447   u->host = strdupdelim (url + l, url + i);
448   DEBUGP (("host %s -> ", u->host));
449
450   /* Assume no port has been given.  */
451   u->port = 0;
452   if (url[i] == ':')
453     {
454       /* We have a colon delimiting the hostname.  It could mean that
455          a port number is following it, or a directory.  */
456       if (ISDIGIT (url[++i]))    /* A port number */
457         {
458           if (type == URLUNKNOWN)
459             u->proto = type = URLHTTP;
460           for (; url[i] && url[i] != '/'; i++)
461             if (ISDIGIT (url[i]))
462               u->port = 10 * u->port + (url[i] - '0');
463             else
464               return URLBADPORT;
465           if (!u->port)
466             return URLBADPORT;
467           DEBUGP (("port %hu -> ", u->port));
468         }
469       else if (type == URLUNKNOWN) /* or a directory */
470         u->proto = type = URLFTP;
471       else                      /* or just a misformed port number */
472         return URLBADPORT;
473     }
474   else if (type == URLUNKNOWN)
475     u->proto = type = URLHTTP;
476   if (!u->port)
477     {
478       int ind;
479       for (ind = 0; ind < ARRAY_SIZE (sup_protos); ind++)
480         if (sup_protos[ind].ind == type)
481           break;
482       if (ind == ARRAY_SIZE (sup_protos))
483         return URLUNKNOWN;
484       u->port = sup_protos[ind].port;
485     }
486   /* Some delimiter troubles...  */
487   if (url[i] == '/' && url[i - 1] != ':')
488     ++i;
489   if (type == URLHTTP)
490     while (url[i] && url[i] == '/')
491       ++i;
492   u->path = (char *)xmalloc (strlen (url + i) + 8);
493   strcpy (u->path, url + i);
494   if (type == URLFTP)
495     {
496       u->ftp_type = process_ftp_type (u->path);
497       /* #### We don't handle type `d' correctly yet.  */
498       if (!u->ftp_type || TOUPPER (u->ftp_type) == 'D')
499         u->ftp_type = 'I';
500     }
501   DEBUGP (("opath %s -> ", u->path));
502   /* Parse the username and password (if existing).  */
503   parse_uname (url, &u->user, &u->passwd);
504   /* Decode the strings, as per RFC 1738.  */
505   decode_string (u->host);
506   decode_string (u->path);
507   if (u->user)
508     decode_string (u->user);
509   if (u->passwd)
510     decode_string (u->passwd);
511   /* Parse the directory.  */
512   parse_dir (u->path, &u->dir, &u->file);
513   DEBUGP (("dir %s -> file %s -> ", u->dir, u->file));
514   /* Simplify the directory.  */
515   path_simplify (u->dir);
516   /* Remove the leading `/' in HTTP.  */
517   if (type == URLHTTP && *u->dir == '/')
518     strcpy (u->dir, u->dir + 1);
519   DEBUGP (("ndir %s\n", u->dir));
520   /* Strip trailing `/'.  */
521   l = strlen (u->dir);
522   if (l && u->dir[l - 1] == '/')
523     u->dir[l - 1] = '\0';
524   /* Re-create the path: */
525   abs_ftp = (u->proto == URLFTP && *u->dir == '/');
526   /*  sprintf (u->path, "%s%s%s%s", abs_ftp ? "%2F": "/",
527       abs_ftp ? (u->dir + 1) : u->dir, *u->dir ? "/" : "", u->file); */
528   strcpy (u->path, abs_ftp ? "%2F" : "/");
529   strcat (u->path, abs_ftp ? (u->dir + 1) : u->dir);
530   strcat (u->path, *u->dir ? "/" : "");
531   strcat (u->path, u->file);
532   URL_CLEANSE (u->path);
533   DEBUGP (("newpath: %s\n", u->path));
534   /* Create the clean URL.  */
535   u->url = str_url (u, 0);
536   return URLOK;
537 }
538 \f
539 /* Special versions of DOTP and DDOTP for parse_dir(). */
540
541 #define PD_DOTP(x)  ((*(x) == '.') && (!*((x) + 1) || *((x) + 1) == '?'))
542 #define PD_DDOTP(x) ((*(x) == '.') && (*(x) == '.')             \
543                      && (!*((x) + 2) || *((x) + 2) == '?'))
544
545 /* Build the directory and filename components of the path.  Both
546    components are *separately* malloc-ed strings!  It does not change
547    the contents of path.
548
549    If the path ends with "." or "..", they are (correctly) counted as
550    directories.  */
551 static void
552 parse_dir (const char *path, char **dir, char **file)
553 {
554   int i, l;
555
556   l = urlpath_length (path);
557   for (i = l; i && path[i] != '/'; i--);
558
559   if (!i && *path != '/')   /* Just filename */
560     {
561       if (PD_DOTP (path) || PD_DDOTP (path))
562         {
563           *dir = strdupdelim (path, path + l);
564           *file = xstrdup (path + l); /* normally empty, but could
565                                          contain ?... */
566         }
567       else
568         {
569           *dir = xstrdup ("");     /* This is required because of FTP */
570           *file = xstrdup (path);
571         }
572     }
573   else if (!i)                 /* /filename */
574     {
575       if (PD_DOTP (path + 1) || PD_DDOTP (path + 1))
576         {
577           *dir = strdupdelim (path, path + l);
578           *file = xstrdup (path + l); /* normally empty, but could
579                                          contain ?... */
580         }
581       else
582         {
583           *dir = xstrdup ("/");
584           *file = xstrdup (path + 1);
585         }
586     }
587   else /* Nonempty directory with or without a filename */
588     {
589       if (PD_DOTP (path + i + 1) || PD_DDOTP (path + i + 1))
590         {
591           *dir = strdupdelim (path, path + l);
592           *file = xstrdup (path + l); /* normally empty, but could
593                                          contain ?... */
594         }
595       else
596         {
597           *dir = strdupdelim (path, path + i);
598           *file = xstrdup (path + i + 1);
599         }
600     }
601 }
602
603 /* Find the optional username and password within the URL, as per
604    RFC1738.  The returned user and passwd char pointers are
605    malloc-ed.  */
606 static uerr_t
607 parse_uname (const char *url, char **user, char **passwd)
608 {
609   int l;
610   const char *p, *col;
611   char **where;
612
613   *user = NULL;
614   *passwd = NULL;
615   url += skip_url (url);
616   /* Look for end of protocol string.  */
617   l = skip_proto (url);
618   if (!l)
619     return URLUNKNOWN;
620   /* Add protocol offset.  */
621   url += l;
622   /* Is there an `@' character?  */
623   for (p = url; *p && *p != '/'; p++)
624     if (*p == '@')
625       break;
626   /* If not, return.  */
627   if (*p != '@')
628     return URLOK;
629   /* Else find the username and password.  */
630   for (p = col = url; *p != '@'; p++)
631     {
632       if (*p == ':' && !*user)
633         {
634           *user = (char *)xmalloc (p - url + 1);
635           memcpy (*user, url, p - url);
636           (*user)[p - url] = '\0';
637           col = p + 1;
638         }
639     }
640   /* Decide whether you have only the username or both.  */
641   where = *user ? passwd : user;
642   *where = (char *)xmalloc (p - col + 1);
643   memcpy (*where, col, p - col);
644   (*where)[p - col] = '\0';
645   return URLOK;
646 }
647
648 /* If PATH ends with `;type=X', return the character X.  */
649 static char
650 process_ftp_type (char *path)
651 {
652   int len = strlen (path);
653
654   if (len >= 7
655       && !memcmp (path + len - 7, ";type=", 6))
656     {
657       path[len - 7] = '\0';
658       return path[len - 1];
659     }
660   else
661     return '\0';
662 }
663 \f
664 /* Return the URL as fine-formed string, with a proper protocol,
665    optional port number, directory and optional user/password.  If
666    HIDE is non-zero, password will be hidden.  The forbidden
667    characters in the URL will be cleansed.  */
668 char *
669 str_url (const struct urlinfo *u, int hide)
670 {
671   char *res, *host, *user, *passwd, *proto_name, *dir, *file;
672   int i, l, ln, lu, lh, lp, lf, ld;
673   unsigned short proto_default_port;
674
675   /* Look for the protocol name.  */
676   for (i = 0; i < ARRAY_SIZE (sup_protos); i++)
677     if (sup_protos[i].ind == u->proto)
678       break;
679   if (i == ARRAY_SIZE (sup_protos))
680     return NULL;
681   proto_name = sup_protos[i].name;
682   proto_default_port = sup_protos[i].port;
683   host = CLEANDUP (u->host);
684   dir = CLEANDUP (u->dir);
685   file = CLEANDUP (u->file);
686   user = passwd = NULL;
687   if (u->user)
688     user = CLEANDUP (u->user);
689   if (u->passwd)
690     {
691       if (hide)
692         /* Don't output the password, or someone might see it over the user's
693            shoulder (or in saved wget output).  Don't give away the number of
694            characters in the password, either, as we did in past versions of
695            this code, when we replaced the password characters with 'x's. */
696         passwd = "<password>";
697       else
698         passwd = CLEANDUP (u->passwd);
699     }
700   if (u->proto == URLFTP && *dir == '/')
701     {
702       char *tmp = (char *)xmalloc (strlen (dir) + 3);
703       /*sprintf (tmp, "%%2F%s", dir + 1);*/
704       tmp[0] = '%';
705       tmp[1] = '2';
706       tmp[2] = 'F';
707       strcpy (tmp + 3, dir + 1);
708       xfree (dir);
709       dir = tmp;
710     }
711
712   ln = strlen (proto_name);
713   lu = user ? strlen (user) : 0;
714   lp = passwd ? strlen (passwd) : 0;
715   lh = strlen (host);
716   ld = strlen (dir);
717   lf = strlen (file);
718   res = (char *)xmalloc (ln + lu + lp + lh + ld + lf + 20); /* safe sex */
719   /* sprintf (res, "%s%s%s%s%s%s:%d/%s%s%s", proto_name,
720      (user ? user : ""), (passwd ? ":" : ""),
721      (passwd ? passwd : ""), (user ? "@" : ""),
722      host, u->port, dir, *dir ? "/" : "", file); */
723   l = 0;
724   memcpy (res, proto_name, ln);
725   l += ln;
726   if (user)
727     {
728       memcpy (res + l, user, lu);
729       l += lu;
730       if (passwd)
731         {
732           res[l++] = ':';
733           memcpy (res + l, passwd, lp);
734           l += lp;
735         }
736       res[l++] = '@';
737     }
738   memcpy (res + l, host, lh);
739   l += lh;
740   if (u->port != proto_default_port)
741     {
742       res[l++] = ':';
743       long_to_string (res + l, (long)u->port);
744       l += numdigit (u->port);
745     }
746   res[l++] = '/';
747   memcpy (res + l, dir, ld);
748   l += ld;
749   if (*dir)
750     res[l++] = '/';
751   strcpy (res + l, file);
752   xfree (host);
753   xfree (dir);
754   xfree (file);
755   FREE_MAYBE (user);
756   FREE_MAYBE (passwd);
757   return res;
758 }
759
760 /* Check whether two URL-s are equivalent, i.e. pointing to the same
761    location.  Uses parseurl to parse them, and compares the canonical
762    forms.
763
764    Returns 1 if the URL1 is equivalent to URL2, 0 otherwise.  Also
765    return 0 on error.  */
766 int
767 url_equal (const char *url1, const char *url2)
768 {
769   struct urlinfo *u1, *u2;
770   uerr_t err;
771   int res;
772
773   u1 = newurl ();
774   err = parseurl (url1, u1, 0);
775   if (err != URLOK)
776     {
777       freeurl (u1, 1);
778       return 0;
779     }
780   u2 = newurl ();
781   err = parseurl (url2, u2, 0);
782   if (err != URLOK)
783     {
784       freeurl (u2, 1);
785       return 0;
786     }
787   res = !strcmp (u1->url, u2->url);
788   freeurl (u1, 1);
789   freeurl (u2, 1);
790   return res;
791 }
792 \f
793 urlpos *
794 get_urls_file (const char *file)
795 {
796   struct file_memory *fm;
797   urlpos *head, *tail;
798   const char *text, *text_end;
799
800   /* Load the file.  */
801   fm = read_file (file);
802   if (!fm)
803     {
804       logprintf (LOG_NOTQUIET, "%s: %s\n", file, strerror (errno));
805       return NULL;
806     }
807   DEBUGP (("Loaded %s (size %ld).\n", file, fm->length));
808   head = tail = NULL;
809   text = fm->content;
810   text_end = fm->content + fm->length;
811   while (text < text_end)
812     {
813       const char *line_beg = text;
814       const char *line_end = memchr (text, '\n', text_end - text);
815       if (!line_end)
816         line_end = text_end;
817       else
818         ++line_end;
819       text = line_end;
820       while (line_beg < line_end
821              && ISSPACE (*line_beg))
822         ++line_beg;
823       while (line_end > line_beg + 1
824              && ISSPACE (*(line_end - 1)))
825         --line_end;
826       if (line_end > line_beg)
827         {
828           urlpos *entry = (urlpos *)xmalloc (sizeof (urlpos));
829           memset (entry, 0, sizeof (*entry));
830           entry->next = NULL;
831           entry->url = strdupdelim (line_beg, line_end);
832           if (!head)
833             head = entry;
834           else
835             tail->next = entry;
836           tail = entry;
837         }
838     }
839   read_file_free (fm);
840   return head;
841 }
842 \f
843 /* Free the linked list of urlpos.  */
844 void
845 free_urlpos (urlpos *l)
846 {
847   while (l)
848     {
849       urlpos *next = l->next;
850       xfree (l->url);
851       FREE_MAYBE (l->local_name);
852       xfree (l);
853       l = next;
854     }
855 }
856
857 /* Rotate FNAME opt.backups times */
858 void
859 rotate_backups(const char *fname)
860 {
861   int maxlen = strlen (fname) + 1 + numdigit (opt.backups) + 1;
862   char *from = (char *)alloca (maxlen);
863   char *to = (char *)alloca (maxlen);
864   struct stat sb;
865   int i;
866
867   if (stat (fname, &sb) == 0)
868     if (S_ISREG (sb.st_mode) == 0)
869       return;
870
871   for (i = opt.backups; i > 1; i--)
872     {
873       sprintf (from, "%s.%d", fname, i - 1);
874       sprintf (to, "%s.%d", fname, i);
875       /* #### This will fail on machines without the rename() system
876          call.  */
877       rename (from, to);
878     }
879
880   sprintf (to, "%s.%d", fname, 1);
881   rename(fname, to);
882 }
883
884 /* Create all the necessary directories for PATH (a file).  Calls
885    mkdirhier() internally.  */
886 int
887 mkalldirs (const char *path)
888 {
889   const char *p;
890   char *t;
891   struct stat st;
892   int res;
893
894   p = path + strlen (path);
895   for (; *p != '/' && p != path; p--);
896   /* Don't create if it's just a file.  */
897   if ((p == path) && (*p != '/'))
898     return 0;
899   t = strdupdelim (path, p);
900   /* Check whether the directory exists.  */
901   if ((stat (t, &st) == 0))
902     {
903       if (S_ISDIR (st.st_mode))
904         {
905           xfree (t);
906           return 0;
907         }
908       else
909         {
910           /* If the dir exists as a file name, remove it first.  This
911              is *only* for Wget to work with buggy old CERN http
912              servers.  Here is the scenario: When Wget tries to
913              retrieve a directory without a slash, e.g.
914              http://foo/bar (bar being a directory), CERN server will
915              not redirect it too http://foo/bar/ -- it will generate a
916              directory listing containing links to bar/file1,
917              bar/file2, etc.  Wget will lose because it saves this
918              HTML listing to a file `bar', so it cannot create the
919              directory.  To work around this, if the file of the same
920              name exists, we just remove it and create the directory
921              anyway.  */
922           DEBUGP (("Removing %s because of directory danger!\n", t));
923           unlink (t);
924         }
925     }
926   res = make_directory (t);
927   if (res != 0)
928     logprintf (LOG_NOTQUIET, "%s: %s", t, strerror (errno));
929   xfree (t);
930   return res;
931 }
932
933 static int
934 count_slashes (const char *s)
935 {
936   int i = 0;
937   while (*s)
938     if (*s++ == '/')
939       ++i;
940   return i;
941 }
942
943 /* Return the path name of the URL-equivalent file name, with a
944    remote-like structure of directories.  */
945 static char *
946 mkstruct (const struct urlinfo *u)
947 {
948   char *host, *dir, *file, *res, *dirpref;
949   int l;
950
951   assert (u->dir != NULL);
952   assert (u->host != NULL);
953
954   if (opt.cut_dirs)
955     {
956       char *ptr = u->dir + (*u->dir == '/');
957       int slash_count = 1 + count_slashes (ptr);
958       int cut = MINVAL (opt.cut_dirs, slash_count);
959       for (; cut && *ptr; ptr++)
960         if (*ptr == '/')
961           --cut;
962       STRDUP_ALLOCA (dir, ptr);
963     }
964   else
965     dir = u->dir + (*u->dir == '/');
966
967   host = xstrdup (u->host);
968   /* Check for the true name (or at least a consistent name for saving
969      to directory) of HOST, reusing the hlist if possible.  */
970   if (opt.add_hostdir && !opt.simple_check)
971     {
972       char *nhost = realhost (host);
973       xfree (host);
974       host = nhost;
975     }
976   /* Add dir_prefix and hostname (if required) to the beginning of
977      dir.  */
978   if (opt.add_hostdir)
979     {
980       if (!DOTP (opt.dir_prefix))
981         {
982           dirpref = (char *)alloca (strlen (opt.dir_prefix) + 1
983                                     + strlen (host) + 1);
984           sprintf (dirpref, "%s/%s", opt.dir_prefix, host);
985         }
986       else
987         STRDUP_ALLOCA (dirpref, host);
988     }
989   else                         /* not add_hostdir */
990     {
991       if (!DOTP (opt.dir_prefix))
992         dirpref = opt.dir_prefix;
993       else
994         dirpref = "";
995     }
996   xfree (host);
997
998   /* If there is a prefix, prepend it.  */
999   if (*dirpref)
1000     {
1001       char *newdir = (char *)alloca (strlen (dirpref) + 1 + strlen (dir) + 2);
1002       sprintf (newdir, "%s%s%s", dirpref, *dir == '/' ? "" : "/", dir);
1003       dir = newdir;
1004     }
1005   dir = xstrdup (dir);
1006   URL_CLEANSE (dir);
1007   l = strlen (dir);
1008   if (l && dir[l - 1] == '/')
1009     dir[l - 1] = '\0';
1010
1011   if (!*u->file)
1012     file = "index.html";
1013   else
1014     file = u->file;
1015
1016   /* Finally, construct the full name.  */
1017   res = (char *)xmalloc (strlen (dir) + 1 + strlen (file) + 1);
1018   sprintf (res, "%s%s%s", dir, *dir ? "/" : "", file);
1019   xfree (dir);
1020   return res;
1021 }
1022
1023 /* Create a unique filename, corresponding to a given URL.  Calls
1024    mkstruct if necessary.  Does *not* actually create any directories.  */
1025 char *
1026 url_filename (const struct urlinfo *u)
1027 {
1028   char *file, *name;
1029   int have_prefix = 0;          /* whether we must prepend opt.dir_prefix */
1030
1031   if (opt.dirstruct)
1032     {
1033       file = mkstruct (u);
1034       have_prefix = 1;
1035     }
1036   else
1037     {
1038       if (!*u->file)
1039         file = xstrdup ("index.html");
1040       else
1041         file = xstrdup (u->file);
1042     }
1043
1044   if (!have_prefix)
1045     {
1046       /* Check whether the prefix directory is something other than "."
1047          before prepending it.  */
1048       if (!DOTP (opt.dir_prefix))
1049         {
1050           char *nfile = (char *)xmalloc (strlen (opt.dir_prefix)
1051                                          + 1 + strlen (file) + 1);
1052           sprintf (nfile, "%s/%s", opt.dir_prefix, file);
1053           xfree (file);
1054           file = nfile;
1055         }
1056     }
1057   /* DOS-ish file systems don't like `%' signs in them; we change it
1058      to `@'.  */
1059 #ifdef WINDOWS
1060   {
1061     char *p = file;
1062     for (p = file; *p; p++)
1063       if (*p == '%')
1064         *p = '@';
1065   }
1066 #endif /* WINDOWS */
1067
1068   /* Check the cases in which the unique extensions are not used:
1069      1) Clobbering is turned off (-nc).
1070      2) Retrieval with regetting.
1071      3) Timestamping is used.
1072      4) Hierarchy is built.
1073
1074      The exception is the case when file does exist and is a
1075      directory (actually support for bad httpd-s).  */
1076   if ((opt.noclobber || opt.always_rest || opt.timestamping || opt.dirstruct)
1077       && !(file_exists_p (file) && !file_non_directory_p (file)))
1078     return file;
1079
1080   /* Find a unique name.  */
1081   name = unique_name (file);
1082   xfree (file);
1083   return name;
1084 }
1085
1086 /* Like strlen(), but allow the URL to be ended with '?'.  */
1087 static int
1088 urlpath_length (const char *url)
1089 {
1090   const char *q = strchr (url, '?');
1091   if (q)
1092     return q - url;
1093   return strlen (url);
1094 }
1095
1096 /* Find the last occurrence of character C in the range [b, e), or
1097    NULL, if none are present.  This is almost completely equivalent to
1098    { *e = '\0'; return strrchr(b); }, except that it doesn't change
1099    the contents of the string.  */
1100 static const char *
1101 find_last_char (const char *b, const char *e, char c)
1102 {
1103   for (; e > b; e--)
1104     if (*e == c)
1105       return e;
1106   return NULL;
1107 }
1108
1109 /* Construct a URL by concatenating an absolute URL and a path, which
1110    may or may not be absolute.  This tries to behave "reasonably" in
1111    all foreseeable cases.  It employs little specific knowledge about
1112    protocols or URL-specific stuff -- it just works on strings.  */
1113 static char *
1114 construct (const char *url, const char *sub, int subsize, int no_proto)
1115 {
1116   char *constr;
1117
1118   if (no_proto)
1119     {
1120       const char *end = url + urlpath_length (url);
1121
1122       if (*sub != '/')
1123         {
1124           /* SUB is a relative URL: we need to replace everything
1125              after last slash (possibly empty) with SUB.
1126
1127              So, if URL is "whatever/foo/bar", and SUB is "qux/xyzzy",
1128              our result should be "whatever/foo/qux/xyzzy".  */
1129           int need_explicit_slash = 0;
1130           int span;
1131           const char *start_insert;
1132           const char *last_slash = find_last_char (url, end, '/'); /* the last slash. */
1133           if (!last_slash)
1134             {
1135               /* No slash found at all.  Append SUB to what we have,
1136                  but we'll need a slash as a separator.
1137
1138                  Example: if url == "foo" and sub == "qux/xyzzy", then
1139                  we cannot just append sub to url, because we'd get
1140                  "fooqux/xyzzy", whereas what we want is
1141                  "foo/qux/xyzzy".
1142
1143                  To make sure the / gets inserted, we set
1144                  need_explicit_slash to 1.  We also set start_insert
1145                  to end + 1, so that the length calculations work out
1146                  correctly for one more (slash) character.  Accessing
1147                  that character is fine, since it will be the
1148                  delimiter, '\0' or '?'.  */
1149               /* example: "foo?..." */
1150               /*               ^    ('?' gets changed to '/') */
1151               start_insert = end + 1;
1152               need_explicit_slash = 1;
1153             }
1154           else if (last_slash && last_slash != url && *(last_slash - 1) == '/')
1155             {
1156               /* example: http://host"  */
1157               /*                      ^ */
1158               start_insert = end + 1;
1159               need_explicit_slash = 1;
1160             }
1161           else
1162             {
1163               /* example: "whatever/foo/bar" */
1164               /*                        ^    */
1165               start_insert = last_slash + 1;
1166             }
1167
1168           span = start_insert - url;
1169           constr = (char *)xmalloc (span + subsize + 1);
1170           if (span)
1171             memcpy (constr, url, span);
1172           if (need_explicit_slash)
1173             constr[span - 1] = '/';
1174           if (subsize)
1175             memcpy (constr + span, sub, subsize);
1176           constr[span + subsize] = '\0';
1177         }
1178       else /* *sub == `/' */
1179         {
1180           /* SUB is an absolute path: we need to replace everything
1181              after (and including) the FIRST slash with SUB.
1182
1183              So, if URL is "http://host/whatever/foo/bar", and SUB is
1184              "/qux/xyzzy", our result should be
1185              "http://host/qux/xyzzy".  */
1186           int span;
1187           const char *slash;
1188           const char *start_insert = NULL; /* for gcc to shut up. */
1189           const char *pos = url;
1190           int seen_slash_slash = 0;
1191           /* We're looking for the first slash, but want to ignore
1192              double slash. */
1193         again:
1194           slash = memchr (pos, '/', end - pos);
1195           if (slash && !seen_slash_slash)
1196             if (*(slash + 1) == '/')
1197               {
1198                 pos = slash + 2;
1199                 seen_slash_slash = 1;
1200                 goto again;
1201               }
1202
1203           /* At this point, SLASH is the location of the first / after
1204              "//", or the first slash altogether.  START_INSERT is the
1205              pointer to the location where SUB will be inserted.  When
1206              examining the last two examples, keep in mind that SUB
1207              begins with '/'. */
1208
1209           if (!slash && !seen_slash_slash)
1210             /* example: "foo" */
1211             /*           ^    */
1212             start_insert = url;
1213           else if (!slash && seen_slash_slash)
1214             /* example: "http://foo" */
1215             /*                     ^ */
1216             start_insert = end;
1217           else if (slash && !seen_slash_slash)
1218             /* example: "foo/bar" */
1219             /*           ^        */
1220             start_insert = url;
1221           else if (slash && seen_slash_slash)
1222             /* example: "http://something/" */
1223             /*                           ^  */
1224             start_insert = slash;
1225
1226           span = start_insert - url;
1227           constr = (char *)xmalloc (span + subsize + 1);
1228           if (span)
1229             memcpy (constr, url, span);
1230           if (subsize)
1231             memcpy (constr + span, sub, subsize);
1232           constr[span + subsize] = '\0';
1233         }
1234     }
1235   else /* !no_proto */
1236     {
1237       constr = strdupdelim (sub, sub + subsize);
1238     }
1239   return constr;
1240 }
1241
1242 /* Like the function above, but with a saner caller interface. */
1243 char *
1244 url_concat (const char *base_url, const char *new_url)
1245 {
1246   return construct (base_url, new_url, strlen (new_url), !has_proto (new_url));
1247 }
1248 \f
1249 /* Optimize URL by host, destructively replacing u->host with realhost
1250    (u->host).  Do this regardless of opt.simple_check.  */
1251 void
1252 opt_url (struct urlinfo *u)
1253 {
1254   /* Find the "true" host.  */
1255   char *host = realhost (u->host);
1256   xfree (u->host);
1257   u->host = host;
1258   assert (u->dir != NULL);      /* the URL must have been parsed */
1259   /* Refresh the printed representation.  */
1260   xfree (u->url);
1261   u->url = str_url (u, 0);
1262 }
1263
1264 /* This beautiful kludge is fortunately not needed, as I've made
1265    parse_dir do the (almost) right thing, so that a query can never
1266    become a part of directory.  */
1267 #if 0
1268 /* Call path_simplify, but make sure that the part after the
1269    question-mark, if any, is not destroyed by path_simplify's
1270    "optimizations".  */
1271 void
1272 path_simplify_with_kludge (char *path)
1273 {
1274   char *query = strchr (path, '?');
1275   if (query)
1276     /* path_simplify also works destructively, so we also have the
1277        license to write. */
1278     *query = '\0';
1279   path_simplify (path);
1280   if (query)
1281     {
1282       char *newend = path + strlen (path);
1283       *query = '?';
1284       if (newend != query)
1285         memmove (newend, query, strlen (query) + 1);
1286     }
1287 }
1288 #endif
1289 \f
1290 /* Returns proxy host address, in accordance with PROTO.  */
1291 char *
1292 getproxy (uerr_t proto)
1293 {
1294   if (proto == URLHTTP)
1295     return opt.http_proxy ? opt.http_proxy : getenv ("http_proxy");
1296   else if (proto == URLFTP)
1297     return opt.ftp_proxy ? opt.ftp_proxy : getenv ("ftp_proxy");
1298 #ifdef HAVE_SSL
1299   else if (proto == URLHTTPS)
1300     return opt.https_proxy ? opt.https_proxy : getenv ("https_proxy");
1301 #endif /* HAVE_SSL */
1302   else
1303     return NULL;
1304 }
1305
1306 /* Should a host be accessed through proxy, concerning no_proxy?  */
1307 int
1308 no_proxy_match (const char *host, const char **no_proxy)
1309 {
1310   if (!no_proxy)
1311     return 1;
1312   else
1313     return !sufmatch (no_proxy, host);
1314 }
1315 \f
1316 static void write_backup_file PARAMS ((const char *, downloaded_file_t));
1317 static void replace_attr PARAMS ((const char **, int, FILE *, const char *));
1318
1319 /* Change the links in an HTML document.  Accepts a structure that
1320    defines the positions of all the links.  */
1321 void
1322 convert_links (const char *file, urlpos *l)
1323 {
1324   struct file_memory *fm;
1325   FILE               *fp;
1326   const char         *p;
1327   downloaded_file_t  downloaded_file_return;
1328
1329   logprintf (LOG_VERBOSE, _("Converting %s... "), file);
1330
1331   {
1332     /* First we do a "dry run": go through the list L and see whether
1333        any URL needs to be converted in the first place.  If not, just
1334        leave the file alone.  */
1335     int count = 0;
1336     urlpos *dry = l;
1337     for (dry = l; dry; dry = dry->next)
1338       if (dry->convert != CO_NOCONVERT)
1339         ++count;
1340     if (!count)
1341       {
1342         logputs (LOG_VERBOSE, _("nothing to do.\n"));
1343         return;
1344       }
1345   }
1346
1347   fm = read_file (file);
1348   if (!fm)
1349     {
1350       logprintf (LOG_NOTQUIET, _("Cannot convert links in %s: %s\n"),
1351                  file, strerror (errno));
1352       return;
1353     }
1354
1355   downloaded_file_return = downloaded_file (CHECK_FOR_FILE, file);
1356   if (opt.backup_converted && downloaded_file_return)
1357     write_backup_file (file, downloaded_file_return);
1358
1359   /* Before opening the file for writing, unlink the file.  This is
1360      important if the data in FM is mmaped.  In such case, nulling the
1361      file, which is what fopen() below does, would make us read all
1362      zeroes from the mmaped region.  */
1363   if (unlink (file) < 0 && errno != ENOENT)
1364     {
1365       logprintf (LOG_NOTQUIET, _("Unable to delete `%s': %s\n"),
1366                  file, strerror (errno));
1367       read_file_free (fm);
1368       return;
1369     }
1370   /* Now open the file for writing.  */
1371   fp = fopen (file, "wb");
1372   if (!fp)
1373     {
1374       logprintf (LOG_NOTQUIET, _("Cannot convert links in %s: %s\n"),
1375                  file, strerror (errno));
1376       read_file_free (fm);
1377       return;
1378     }
1379   /* Here we loop through all the URLs in file, replacing those of
1380      them that are downloaded with relative references.  */
1381   p = fm->content;
1382   for (; l; l = l->next)
1383     {
1384       char *url_start = fm->content + l->pos;
1385
1386       if (l->pos >= fm->length)
1387         {
1388           DEBUGP (("Something strange is going on.  Please investigate."));
1389           break;
1390         }
1391       /* If the URL is not to be converted, skip it.  */
1392       if (l->convert == CO_NOCONVERT)
1393         {
1394           DEBUGP (("Skipping %s at position %d.\n", l->url, l->pos));
1395           continue;
1396         }
1397
1398       /* Echo the file contents, up to the offending URL's opening
1399          quote, to the outfile.  */
1400       fwrite (p, 1, url_start - p, fp);
1401       p = url_start;
1402       if (l->convert == CO_CONVERT_TO_RELATIVE)
1403         {
1404           /* Convert absolute URL to relative. */
1405           char *newname = construct_relative (file, l->local_name);
1406           char *quoted_newname = html_quote_string (newname);
1407           replace_attr (&p, l->size, fp, quoted_newname);
1408           DEBUGP (("TO_RELATIVE: %s to %s at position %d in %s.\n",
1409                    l->url, newname, l->pos, file));
1410           xfree (newname);
1411           xfree (quoted_newname);
1412         }
1413       else if (l->convert == CO_CONVERT_TO_COMPLETE)
1414         {
1415           /* Convert the link to absolute URL. */
1416           char *newlink = l->url;
1417           char *quoted_newlink = html_quote_string (newlink);
1418           replace_attr (&p, l->size, fp, quoted_newlink);
1419           DEBUGP (("TO_COMPLETE: <something> to %s at position %d in %s.\n",
1420                    newlink, l->pos, file));
1421           xfree (quoted_newlink);
1422         }
1423     }
1424   /* Output the rest of the file. */
1425   if (p - fm->content < fm->length)
1426     fwrite (p, 1, fm->length - (p - fm->content), fp);
1427   fclose (fp);
1428   read_file_free (fm);
1429   logputs (LOG_VERBOSE, _("done.\n"));
1430 }
1431
1432 /* Construct and return a malloced copy of the relative link from two
1433    pieces of information: local name S1 of the referring file and
1434    local name S2 of the referred file.
1435
1436    So, if S1 is "jagor.srce.hr/index.html" and S2 is
1437    "jagor.srce.hr/images/news.gif", the function will return
1438    "images/news.gif".
1439
1440    Alternately, if S1 is "fly.cc.fer.hr/ioccc/index.html", and S2 is
1441    "fly.cc.fer.hr/images/fly.gif", the function will return
1442    "../images/fly.gif".
1443
1444    Caveats: S1 should not begin with `/', unless S2 also begins with
1445    '/'.  S1 should not contain things like ".." and such --
1446    construct_relative ("fly/ioccc/../index.html",
1447    "fly/images/fly.gif") will fail.  (A workaround is to call
1448    something like path_simplify() on S1).  */
1449 static char *
1450 construct_relative (const char *s1, const char *s2)
1451 {
1452   int i, cnt, sepdirs1;
1453   char *res;
1454
1455   if (*s2 == '/')
1456     return xstrdup (s2);
1457   /* S1 should *not* be absolute, if S2 wasn't.  */
1458   assert (*s1 != '/');
1459   i = cnt = 0;
1460   /* Skip the directories common to both strings.  */
1461   while (1)
1462     {
1463       while (s1[i] && s2[i]
1464              && (s1[i] == s2[i])
1465              && (s1[i] != '/')
1466              && (s2[i] != '/'))
1467         ++i;
1468       if (s1[i] == '/' && s2[i] == '/')
1469         cnt = ++i;
1470       else
1471         break;
1472     }
1473   for (sepdirs1 = 0; s1[i]; i++)
1474     if (s1[i] == '/')
1475       ++sepdirs1;
1476   /* Now, construct the file as of:
1477      - ../ repeated sepdirs1 time
1478      - all the non-mutual directories of S2.  */
1479   res = (char *)xmalloc (3 * sepdirs1 + strlen (s2 + cnt) + 1);
1480   for (i = 0; i < sepdirs1; i++)
1481     memcpy (res + 3 * i, "../", 3);
1482   strcpy (res + 3 * i, s2 + cnt);
1483   return res;
1484 }
1485 \f
1486 /* Add URL to the head of the list L.  */
1487 urlpos *
1488 add_url (urlpos *l, const char *url, const char *file)
1489 {
1490   urlpos *t;
1491
1492   t = (urlpos *)xmalloc (sizeof (urlpos));
1493   memset (t, 0, sizeof (*t));
1494   t->url = xstrdup (url);
1495   t->local_name = xstrdup (file);
1496   t->next = l;
1497   return t;
1498 }
1499
1500 static void
1501 write_backup_file (const char *file, downloaded_file_t downloaded_file_return)
1502 {
1503   /* Rather than just writing over the original .html file with the
1504      converted version, save the former to *.orig.  Note we only do
1505      this for files we've _successfully_ downloaded, so we don't
1506      clobber .orig files sitting around from previous invocations. */
1507
1508   /* Construct the backup filename as the original name plus ".orig". */
1509   size_t         filename_len = strlen(file);
1510   char*          filename_plus_orig_suffix;
1511   boolean        already_wrote_backup_file = FALSE;
1512   slist*         converted_file_ptr;
1513   static slist*  converted_files = NULL;
1514
1515   if (downloaded_file_return == FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED)
1516     {
1517       /* Just write "orig" over "html".  We need to do it this way
1518          because when we're checking to see if we've downloaded the
1519          file before (to see if we can skip downloading it), we don't
1520          know if it's a text/html file.  Therefore we don't know yet
1521          at that stage that -E is going to cause us to tack on
1522          ".html", so we need to compare vs. the original URL plus
1523          ".orig", not the original URL plus ".html.orig". */
1524       filename_plus_orig_suffix = alloca (filename_len + 1);
1525       strcpy(filename_plus_orig_suffix, file);
1526       strcpy((filename_plus_orig_suffix + filename_len) - 4, "orig");
1527     }
1528   else /* downloaded_file_return == FILE_DOWNLOADED_NORMALLY */
1529     {
1530       /* Append ".orig" to the name. */
1531       filename_plus_orig_suffix = alloca (filename_len + sizeof(".orig"));
1532       strcpy(filename_plus_orig_suffix, file);
1533       strcpy(filename_plus_orig_suffix + filename_len, ".orig");
1534     }
1535
1536   /* We can get called twice on the same URL thanks to the
1537      convert_all_links() call in main().  If we write the .orig file
1538      each time in such a case, it'll end up containing the first-pass
1539      conversion, not the original file.  So, see if we've already been
1540      called on this file. */
1541   converted_file_ptr = converted_files;
1542   while (converted_file_ptr != NULL)
1543     if (strcmp(converted_file_ptr->string, file) == 0)
1544       {
1545         already_wrote_backup_file = TRUE;
1546         break;
1547       }
1548     else
1549       converted_file_ptr = converted_file_ptr->next;
1550
1551   if (!already_wrote_backup_file)
1552     {
1553       /* Rename <file> to <file>.orig before former gets written over. */
1554       if (rename(file, filename_plus_orig_suffix) != 0)
1555         logprintf (LOG_NOTQUIET, _("Cannot back up %s as %s: %s\n"),
1556                    file, filename_plus_orig_suffix, strerror (errno));
1557
1558       /* Remember that we've already written a .orig backup for this file.
1559          Note that we never free this memory since we need it till the
1560          convert_all_links() call, which is one of the last things the
1561          program does before terminating.  BTW, I'm not sure if it would be
1562          safe to just set 'converted_file_ptr->string' to 'file' below,
1563          rather than making a copy of the string...  Another note is that I
1564          thought I could just add a field to the urlpos structure saying
1565          that we'd written a .orig file for this URL, but that didn't work,
1566          so I had to make this separate list.
1567          -- Dan Harkless <wget@harkless.org>
1568
1569          This [adding a field to the urlpos structure] didn't work
1570          because convert_file() is called twice: once after all its
1571          sublinks have been retrieved in recursive_retrieve(), and
1572          once at the end of the day in convert_all_links().  The
1573          original linked list collected in recursive_retrieve() is
1574          lost after the first invocation of convert_links(), and
1575          convert_all_links() makes a new one (it calls get_urls_html()
1576          for each file it covers.)  That's why your first approach didn't
1577          work.  The way to make it work is perhaps to make this flag a
1578          field in the `urls_html' list.
1579          -- Hrvoje Niksic <hniksic@arsdigita.com>
1580       */
1581       converted_file_ptr = xmalloc(sizeof(*converted_file_ptr));
1582       converted_file_ptr->string = xstrdup(file);  /* die on out-of-mem. */
1583       converted_file_ptr->next = converted_files;
1584       converted_files = converted_file_ptr;
1585     }
1586 }
1587
1588 static int find_fragment PARAMS ((const char *, int, const char **,
1589                                   const char **));
1590
1591 static void
1592 replace_attr (const char **pp, int raw_size, FILE *fp, const char *new_str)
1593 {
1594   const char *p = *pp;
1595   int quote_flag = 0;
1596   int size = raw_size;
1597   char quote_char = '\"';
1598   const char *frag_beg, *frag_end;
1599
1600   /* Structure of our string is:
1601        "...old-contents..."
1602        <---  l->size   --->  (with quotes)
1603      OR:
1604        ...old-contents...
1605        <---  l->size  -->    (no quotes)   */
1606
1607   if (*p == '\"' || *p == '\'')
1608     {
1609       quote_char = *p;
1610       quote_flag = 1;
1611       ++p;
1612       size -= 2;                /* disregard opening and closing quote */
1613     }
1614   putc (quote_char, fp);
1615   fputs (new_str, fp);
1616
1617   /* Look for fragment identifier, if any. */
1618   if (find_fragment (p, size, &frag_beg, &frag_end))
1619     fwrite (frag_beg, 1, frag_end - frag_beg, fp);
1620   p += size;
1621   if (quote_flag)
1622     ++p;
1623   putc (quote_char, fp);
1624   *pp = p;
1625 }
1626
1627 /* Find the first occurrence of '#' in [BEG, BEG+SIZE) that is not
1628    preceded by '&'.  If the character is not found, return zero.  If
1629    the character is found, return 1 and set BP and EP to point to the
1630    beginning and end of the region.
1631
1632    This is used for finding the fragment indentifiers in URLs.  */
1633
1634 static int
1635 find_fragment (const char *beg, int size, const char **bp, const char **ep)
1636 {
1637   const char *end = beg + size;
1638   int saw_amp = 0;
1639   for (; beg < end; beg++)
1640     {
1641       switch (*beg)
1642         {
1643         case '&':
1644           saw_amp = 1;
1645           break;
1646         case '#':
1647           if (!saw_amp)
1648             {
1649               *bp = beg;
1650               *ep = end;
1651               return 1;
1652             }
1653           /* fallthrough */
1654         default:
1655           saw_amp = 0;
1656         }
1657     }
1658   return 0;
1659 }
1660
1661 typedef struct _downloaded_file_list {
1662   char*                          file;
1663   downloaded_file_t              download_type;
1664   struct _downloaded_file_list*  next;
1665 } downloaded_file_list;
1666
1667 static downloaded_file_list *downloaded_files;
1668
1669 /* Remembers which files have been downloaded.  In the standard case, should be
1670    called with mode == FILE_DOWNLOADED_NORMALLY for each file we actually
1671    download successfully (i.e. not for ones we have failures on or that we skip
1672    due to -N).
1673
1674    When we've downloaded a file and tacked on a ".html" extension due to -E,
1675    call this function with FILE_DOWNLOADED_AND_HTML_EXTENSION_ADDED rather than
1676    FILE_DOWNLOADED_NORMALLY.
1677
1678    If you just want to check if a file has been previously added without adding
1679    it, call with mode == CHECK_FOR_FILE.  Please be sure to call this function
1680    with local filenames, not remote URLs. */
1681 downloaded_file_t
1682 downloaded_file (downloaded_file_t  mode, const char*  file)
1683 {
1684   boolean                       found_file = FALSE;
1685   downloaded_file_list*         rover = downloaded_files;
1686
1687   while (rover != NULL)
1688     if (strcmp(rover->file, file) == 0)
1689       {
1690         found_file = TRUE;
1691         break;
1692       }
1693     else
1694       rover = rover->next;
1695
1696   if (found_file)
1697     return rover->download_type;  /* file had already been downloaded */
1698   else
1699     {
1700       if (mode != CHECK_FOR_FILE)
1701         {
1702           rover = xmalloc(sizeof(*rover));
1703           rover->file = xstrdup(file); /* use xstrdup() so die on out-of-mem. */
1704           rover->download_type = mode;
1705           rover->next = downloaded_files;
1706           downloaded_files = rover;
1707         }
1708
1709       return FILE_NOT_ALREADY_DOWNLOADED;
1710     }
1711 }
1712
1713 void
1714 downloaded_files_free (void)
1715 {
1716   downloaded_file_list*         rover = downloaded_files;
1717   while (rover)
1718     {
1719       downloaded_file_list *next = rover->next;
1720       xfree (rover->file);
1721       xfree (rover);
1722       rover = next;
1723     }
1724 }
1725 \f
1726 /* Initialization of static stuff. */
1727 void
1728 url_init (void)
1729 {
1730   init_unsafe_char_table ();
1731 }