tools: Replace duplicated code in mime-maker.
[gnupg.git] / tools / rfc822parse.c
index 3379886..0a4e2bc 100644 (file)
@@ -1,33 +1,31 @@
 /* rfc822parse.c - Simple mail and MIME parser
- *     Copyright (C) 1999, 2000 Werner Koch, Duesseldorf
- *      Copyright (C) 2003, 2004 g10 Code GmbH
+ * Copyright (C) 1999, 2000 Werner Koch, Duesseldorf
+ * Copyright (C) 2003, 2004 g10 Code GmbH
  *
- * 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 file 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 2.1 of
+ * the License, or (at your option) any later version.
  *
- * This program is is distributed in the hope that it will be useful,
+ * This file 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.
+ * 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 General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
  */
 
 
-/* According to RFC822 binary 0 are allowed at many places. We
- * do not handle this correct especially in the field parsing code.  It
- * should be easy to fix and the API provides a interfcaes which returns
- * the length but in addition makes sure that returned strings are always
- * ended by a \0.  
+/* According to RFC822 binary zeroes are allowed at many places. We do
+ * not handle this correct especially in the field parsing code.  It
+ * should be easy to fix and the API provides a interfaces which
+ * returns the length but in addition makes sure that returned strings
+ * are always ended by a \0.
  *
  * Furthermore, the case of field names is changed and thus it is not
  * always a good idea to use these modified header
  * lines (e.g. signatures may break).
- * 
  */
 
 #ifdef HAVE_CONFIG_H
 
 #include "rfc822parse.h"
 
+/* All valid characters in a header name.  */
+#define HEADER_NAME_CHARS  ("abcdefghijklmnopqrstuvwxyz" \
+                            "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
+                            "-01234567890")
+
+
 enum token_type
-{
-  tSPACE,
-  tATOM,
-  tQUOTED,
-  tDOMAINLIT,
-  tSPECIAL
-};
+  {
+    tSPACE,
+    tATOM,
+    tQUOTED,
+    tDOMAINLIT,
+    tSPECIAL
+  };
 
 /* For now we directly use our TOKEN as the parse context */
 typedef struct rfc822parse_field_context *TOKEN;
@@ -107,7 +111,7 @@ length_sans_trailing_ws (const unsigned char *line, size_t len)
 {
   const unsigned char *p, *mark;
   size_t n;
-  
+
   for (mark=NULL, p=line, n=0; n < len; n++, p++)
     {
       if (strchr (" \t\r\n", *p ))
@@ -118,8 +122,8 @@ length_sans_trailing_ws (const unsigned char *line, size_t len)
       else
         mark = NULL;
     }
-  
-  if (mark) 
+
+  if (mark)
     return mark - line;
   return len;
 }
@@ -133,38 +137,43 @@ lowercase_string (unsigned char *string)
       *string = *string - 'A' + 'a';
 }
 
-/* Transform a header name into a standard capitalized format; i.e
-   "Content-Type".  Conversion stops at the colon.  As usual we don't
-   use the localized versions of ctype.h.
- */
-static void
-capitalize_header_name (unsigned char *name)
+
+static int
+my_toupper (int c)
 {
-  int first = 1;
+  if (c >= 'a' && c <= 'z')
+    c &= ~0x20;
+  return c;
+}
 
-  for (; *name && *name != ':'; name++)
-    if (*name == '-')
-      first = 1;
-    else if (first)
-      {
-        if (*name >= 'a' && *name <= 'z')
-          *name = *name - 'a' + 'A';
-        first = 0;
-      }
-    else if (*name >= 'A' && *name <= 'Z')
-      *name = *name - 'A' + 'a';
+/* This is the same as ascii_strcasecmp.  */
+static int
+my_strcasecmp (const char *a, const char *b)
+{
+  if (a == b)
+    return 0;
+
+  for (; *a && *b; a++, b++)
+    {
+      if (*a != *b && my_toupper(*a) != my_toupper(*b))
+        break;
+    }
+  return *a == *b? 0 : (my_toupper (*a) - my_toupper (*b));
 }
 
 
+#ifndef HAVE_STPCPY
 static char *
-stpcpy (char *a,const char *b)
+my_stpcpy (char *a,const char *b)
 {
   while (*b)
     *a++ = *b++;
   *a = 0;
-  
+
   return (char*)a;
 }
+#define stpcpy my_stpcpy
+#endif
 
 
 /* If a callback has been registerd, call it for the event of type
@@ -228,6 +237,62 @@ release_handle_data (rfc822parse_t msg)
 }
 
 
+/* Check that the header name is valid.  We allow all lower and
+ * uppercase letters and, except for the first character, digits and
+ * the dash.  The check stops at the first colon or at string end.
+ * Returns true if the name is valid.  */
+int
+rfc822_valid_header_name_p (const char *name)
+{
+  const char *s;
+  size_t namelen;
+
+  if ((s=strchr (name, ':')))
+    namelen = s - name;
+  else
+    namelen = strlen (name);
+
+  if (!namelen
+      || strspn (name, HEADER_NAME_CHARS) != namelen
+      || strchr ("-0123456789", *name))
+    return 0;
+  return 1;
+}
+
+
+/* Transform a header NAME into a standard capitalized format.
+ * Conversion stops at the colon. */
+void
+rfc822_capitalize_header_name (char *name)
+{
+  unsigned char *p = name;
+  int first = 1;
+
+  /* Special cases first.  */
+  if (!my_strcasecmp (name, "MIME-Version"))
+    {
+      strcpy (name, "MIME-Version");
+      return;
+    }
+
+  /* Regular cases.  */
+  for (; *p && *p != ':'; p++)
+    {
+      if (*p == '-')
+        first = 1;
+      else if (first)
+        {
+          if (*p >= 'a' && *p <= 'z')
+            *p = *p - 'a' + 'A';
+          first = 0;
+        }
+      else if (*p >= 'A' && *p <= 'Z')
+        *p = *p - 'A' + 'a';
+    }
+}
+
+
+
 /* Create a new parsing context for an entire rfc822 message and
    return it.  CB and CB_VALUE may be given to callback for certain
    events.  NULL is returned on error with errno set appropriately. */
@@ -357,10 +422,10 @@ transition_to_body (rfc822parse_t msg)
                 {
                   assert (!msg->current_part->boundary);
                   msg->current_part->boundary = malloc (strlen (s) + 1);
-                  if (msg->current_part->boundary) 
+                  if (msg->current_part->boundary)
                     {
                       part_t part;
-                  
+
                       strcpy (msg->current_part->boundary, s);
                       msg->boundary = msg->current_part->boundary;
                       part = new_part ();
@@ -429,10 +494,10 @@ insert_header (rfc822parse_t msg, const unsigned char *line, size_t length)
   hdr->cont = (*line == ' ' || *line == '\t');
   memcpy (hdr->line, line, length);
   hdr->line[length] = 0; /* Make it a string. */
-  
+
   /* Transform a field name into canonical format. */
   if (!hdr->cont && strchr (line, ':'))
-     capitalize_header_name (hdr->line);
+    rfc822_capitalize_header_name (hdr->line);
 
   *msg->current_part->hdr_lines_tail = hdr;
   msg->current_part->hdr_lines_tail = &hdr->next;
@@ -474,7 +539,7 @@ insert_body (rfc822parse_t msg, const unsigned char *line, size_t length)
           msg->boundary = NULL; /* No current boundary anymore. */
           set_current_part_to_parent (msg);
 
-          /* Fixme: The next should acctually be sent right before the
+          /* Fixme: The next should actually be send right before the
              next boundary, so that we can mark the epilogue. */
           if (!rc)
             rc = do_callback (msg, RFC822PARSE_LEVEL_UP);
@@ -491,7 +556,7 @@ insert_body (rfc822parse_t msg, const unsigned char *line, size_t length)
 int
 rfc822parse_insert (rfc822parse_t msg, const unsigned char *line, size_t length)
 {
-  return (msg->in_body 
+  return (msg->in_body
           ? insert_body (msg, line, length)
           : insert_header (msg, line, length));
 }
@@ -515,15 +580,16 @@ rfc822parse_finish (rfc822parse_t msg)
  * part.
  *
  * WHICH gives the mode:
- *  -1 := Take the last occurence
+ *  -1 := Take the last occurrence
  *   n := Take the n-th  one.
- * 
+ *
  * Returns a newly allocated buffer or NULL on error.  errno is set in
  * case of a memory failure or set to 0 if the requested field is not
  * available.
- * 
+ *
  * If VALUEOFF is not NULL it will receive the offset of the first non
- * space character in th value of the line.
+ * space character in the value part of the line (i.e. after the first
+ * colon).
  */
 char *
 rfc822parse_get_field (rfc822parse_t msg, const char *name, int which,
@@ -589,7 +655,7 @@ rfc822parse_enum_header_lines (rfc822parse_t msg, void **context)
   HDR_LINE l;
 
   if (!msg) /* Close. */
-    return NULL;       
+    return NULL;
 
   if (*context == msg || !msg->current_part)
     return NULL;
@@ -758,14 +824,16 @@ parse_field (HDR_LINE hdr)
   static const char specials[] = "<>@.,;:\\[]\"()";
   static const char specials2[] = "<>@.,;:";
   static const char tspecials[] = "/?=<>@,;:\\[]\"()";
-  static const char tspecials2[] = "/?=<>@.,;:";
-  static struct 
+  static const char tspecials2[] = "/?=<>@.,;:";  /* FIXME: really
+                                                     include '.'?*/
+  static struct
   {
     const unsigned char *name;
     size_t namelen;
   } tspecial_header[] = {
     { "Content-Type", 12},
     { "Content-Transfer-Encoding", 25},
+    { "Content-Disposition", 19},
     { NULL, 0}
   };
   const char *delimiters;
@@ -806,10 +874,12 @@ parse_field (HDR_LINE hdr)
   s++; /* Move over the colon. */
   for (;;)
     {
-      if (!*s)
+      while (!*s)
        {
          if (!hdr->next || !hdr->next->cont)
-           break;
+            return tok; /* Ready.  */
+
+          /* Next item is a header continuation line.  */
          hdr = hdr->next;
          s = hdr->line;
        }
@@ -822,10 +892,11 @@ parse_field (HDR_LINE hdr)
          invalid = 0;
          for (s++;; s++)
            {
-             if (!*s)
+             while (!*s)
                {
                  if (!hdr->next || !hdr->next->cont)
-                   break;
+                   goto oparen_out;
+                  /* Next item is a header continuation line.  */
                  hdr = hdr->next;
                  s = hdr->line;
                }
@@ -847,6 +918,7 @@ parse_field (HDR_LINE hdr)
              else if (*s == '\"')
                in_quote = 1;
            }
+        oparen_out:
          if (!*s)
            ; /* Actually this is an error, but we don't care about it. */
          else
@@ -869,15 +941,16 @@ parse_field (HDR_LINE hdr)
                  else if (*s2 == '\\' && s2[1]) /* what about continuation? */
                    s2++;
                }
-              
+
              t = (t
                    ? append_to_token (t, s, s2 - s)
                    : new_token (term == '\"'? tQUOTED : tDOMAINLIT, s, s2 - s));
               if (!t)
                 goto failure;
-                   
+
              if (*s2 || !hdr->next || !hdr->next->cont)
                break;
+              /* Next item is a header continuation line.  */
              hdr = hdr->next;
              s = hdr->line;
            }
@@ -929,8 +1002,7 @@ parse_field (HDR_LINE hdr)
          s++;
        }
     }
-
-  return tok;
+  /*NOTREACHED*/
 
  failure:
   {
@@ -948,7 +1020,7 @@ parse_field (HDR_LINE hdr)
  * Find and parse a header field.
  * WHICH indicates what to do if there are multiple instance of the same
  * field (like "Received"); the following value are defined:
- *  -1 := Take the last occurence
+ *  -1 := Take the last occurrence
  *   0 := Reserved
  *   n := Take the n-th one.
  * Returns a handle for further operations on the parse context of the field
@@ -1005,10 +1077,10 @@ is_parameter (TOKEN t)
    Returns a pointer to the value which is valid as long as the
    parse context is valid; NULL is returned in case that attr is not
    defined in the header, a missing value is reppresented by an empty string.
+
    With LOWER_VALUE set to true, a matching field valuebe be
    lowercased.
+
    Note, that ATTR should be lowercase.
  */
 const char *
@@ -1111,9 +1183,9 @@ dump_structure (rfc822parse_t msg, part_t part, int indent)
     {
       rfc822parse_field_t ctx;
       part_t save_part; /* ugly hack - we should have a function to
-                           get part inforation. */
+                           get part information. */
       const char *s;
-      
+
       save_part = msg->current_part;
       msg->current_part = part;
       ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
@@ -1139,7 +1211,7 @@ dump_structure (rfc822parse_t msg, part_t part, int indent)
       if (part->down)
         dump_structure (msg, part->down, indent + 1);
     }
-  
+
 }
 
 
@@ -1153,7 +1225,7 @@ show_param (rfc822parse_field_t ctx, const char *name)
     return;
   s = rfc822parse_query_parameter (ctx, name, 0);
   if (s)
-    printf ("***   %s: `%s'\n", name, s);
+    printf ("***   %s: '%s'\n", name, s);
 }
 
 
@@ -1171,8 +1243,13 @@ show_event (rfc822parse_event_t event)
     case RFC822PARSE_T2BODY: s= "T2Body"; break;
     case RFC822PARSE_FINISH: s= "Finish"; break;
     case RFC822PARSE_RCVD_SEEN: s= "Rcvd_Seen"; break;
+    case RFC822PARSE_LEVEL_DOWN: s= "Level_Down"; break;
+    case RFC822PARSE_LEVEL_UP:   s= "Level_Up"; break;
     case RFC822PARSE_BOUNDARY: s= "Boundary"; break;
     case RFC822PARSE_LAST_BOUNDARY: s= "Last_Boundary"; break;
+    case RFC822PARSE_BEGIN_HEADER: s= "Begin_Header"; break;
+    case RFC822PARSE_PREAMBLE: s= "Preamble"; break;
+    case RFC822PARSE_EPILOGUE: s= "Epilogue"; break;
     default: s= "***invalid event***"; break;
     }
   printf ("*** got RFC822 event %s\n", s);
@@ -1200,7 +1277,7 @@ msg_cb (void *dummy_arg, rfc822parse_event_t event, rfc822parse_t msg)
           const char *s1, *s2;
           s1 = rfc822parse_query_media_type (ctx, &s2);
           if (s1)
-            printf ("***   media: `%s/%s'\n", s1, s2);
+            printf ("***   media: '%s/%s'\n", s1, s2);
           else
             printf ("***   media: [not found]\n");
           show_param (ctx, "boundary");
@@ -1209,7 +1286,7 @@ msg_cb (void *dummy_arg, rfc822parse_event_t event, rfc822parse_t msg)
         }
       else
         printf ("***   media: text/plain [assumed]\n");
-      
+
     }
 
 
@@ -1249,6 +1326,6 @@ main (int argc, char **argv)
 
 /*
 Local Variables:
-compile-command: "gcc -Wall -g -DTESTING -o rfc822parse rfc822parse.c"
+compile-command: "gcc -Wall -Wno-pointer-sign -g -DTESTING -o rfc822parse rfc822parse.c"
 End:
 */