diff --git a/TODO b/TODO
index e6f7886b33837ab5cc954fa32b158bf4df4d0533..5372b7ef7d236cb861b2a9f3c7f97646faeb112f 100644
--- a/TODO
+++ b/TODO
@@ -8,8 +8,6 @@ Current:
 	- Limit the number of notifications generated (on a per-method basis)
 * Implement mailto method for the enotify extension:
 	- Implement verification of recipient addresses in mailto uris.
-	- Implement verification of (unstructured) header field bodies in 
-	  in mailto uris.
 	- Add addresses contained in to to and cc URI headers to the 
 	  recipients. Conversely, the generated To header must include the
 	  URI recipients apart from those stated explicitly in the To URI
diff --git a/src/lib-sieve/plugins/enotify/ntfy-mailto.c b/src/lib-sieve/plugins/enotify/ntfy-mailto.c
index ba5374c784af50666cc32f765231929c20ccfe3d..50c806b8a41e954338c77b1ff5bc5d9b29ce6412 100644
--- a/src/lib-sieve/plugins/enotify/ntfy-mailto.c
+++ b/src/lib-sieve/plugins/enotify/ntfy-mailto.c
@@ -109,6 +109,31 @@ static inline bool _ntfy_mailto_header_allowed(const char *field_name)
 /* FIXME: much of this implementation will be common to other URI schemes. This
  *        should be merged into a common implementation.
  */
+
+static const char _qchar_lookup[256] = {
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 00
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 10
+	0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0,  // 20
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,  // 30
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 40
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,  // 50
+	0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 60
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0,  // 70
+
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 80
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // 90
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // A0
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // B0
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // C0
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // D0
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // E0
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // F0
+};
+
+static inline bool _is_qchar(unsigned char c)
+{
+	return _qchar_lookup[c];
+}
   
 static inline int _decode_hex_digit(char digit)
 {
@@ -251,7 +276,12 @@ static bool _uri_parse_headers
 					*error_r = "invalid % encoding";
 					return FALSE;
 				}
+			} else if ( ch != '=' && !_is_qchar(ch) ) {
+				*error_r = t_strdup_printf
+					("invalid character '%c' in header field name part", *p);
+				return FALSE;
 			}
+
 			str_append_c(field, ch);
 		}
 		if ( *p != '\0' ) p++;
@@ -292,14 +322,20 @@ static bool _uri_parse_headers
 					*error_r = "invalid % encoding";
 					return FALSE;
 				}
+			} else if ( ch != '=' && !_is_qchar(ch) ) {
+				*error_r = t_strdup_printf
+					("invalid character '%c' in header field value part", *p);
+				return FALSE;
 			}
 			str_append_c(field, ch);
 		}
 		if ( *p != '\0' ) p++;
 		
 		/* Verify field body */
-		
-		// FIXME ....
+		if ( !rfc2822_header_field_body_verify(str_c(field), str_len(field)) ) {
+			*error_r = "invalid header field body";
+			return FALSE;
+		}
 		
 		/* Assign field body */
 
diff --git a/src/lib-sieve/rfc2822.c b/src/lib-sieve/rfc2822.c
index 6aed0c81522200703082833962d0b7a3ec5c77cd..15e2a4f735725e489c6767fe5845d606bb88e8d8 100644
--- a/src/lib-sieve/rfc2822.c
+++ b/src/lib-sieve/rfc2822.c
@@ -1,6 +1,10 @@
 /* Copyright (c) 2002-2008 Dovecot Sieve authors, see the included COPYING file 
  */
 
+/* NOTE: much of the functionality implemented here should eventually appear
+ * somewhere in Dovecot itself.
+ */
+
 #include "lib.h"
 #include "str.h"
 
@@ -8,10 +12,6 @@
 
 #include <stdio.h>
 #include <ctype.h>
-
-/* NOTE: much of the functionality implemented here should eventually appear
- * somewhere in Dovecot itself.
- */
  
 bool rfc2822_header_field_name_verify
 (const char *field_name, unsigned int len) 
@@ -35,6 +35,40 @@ bool rfc2822_header_field_name_verify
 	return TRUE;
 }
 
+bool rfc2822_header_field_body_verify
+(const char *field_body, unsigned int len) 
+{
+	const char *p = field_body;
+	const char *pend = p + len;
+
+	/* unstructured    =       *([FWS] utext) [FWS]
+	 * FWS             =       ([*WSP CRLF] 1*WSP) /   ; Folding white space
+	 *                         obs-FWS
+	 * utext           =       NO-WS-CTL /     ; Non white space controls
+	 *                         %d33-126 /      ; The rest of US-ASCII
+	 *                         obs-utext
+	 * NO-WS-CTL       =       %d1-8 /         ; US-ASCII control characters
+	 *                         %d11 /          ;  that do not include the
+	 *                         %d12 /          ;  carriage return, line feed,
+	 *                         %d14-31 /       ;  and white space characters
+	 *                         %d127
+	 * WSP             =  SP / HTAB
+	 */
+
+	/* This verification does not allow content to be folded. This should done
+	 * automatically upon message composition.
+	 */
+
+	while ( p < pend ) {
+		if ( *p == '\0' || *p == '\r' || *p == '\n' || *p > 127 )
+			return FALSE;
+
+		p++;
+	}	
+	
+	return TRUE;
+}
+
 /*
  *
  */
diff --git a/src/lib-sieve/rfc2822.h b/src/lib-sieve/rfc2822.h
index fc85ac2a67ab7f97a49a63103867fcf21ca7561f..2476d11baca0cb1e3cbe4dc57529456a852f811a 100644
--- a/src/lib-sieve/rfc2822.h
+++ b/src/lib-sieve/rfc2822.h
@@ -14,6 +14,8 @@
  
 bool rfc2822_header_field_name_verify
 	(const char *field_name, unsigned int len);
+bool rfc2822_header_field_body_verify
+(const char *field_body, unsigned int len);
 
 /*
  *