diff --git a/README b/README
index 0d91854ef2d024a56fad46e9e7637447a2c93276..4dc26a8a65343116e3c054a8e8ab7cfab3d042a6 100644
--- a/README
+++ b/README
@@ -121,7 +121,7 @@ Extensions and their implementation status:
     fileinto: full
     reject: full
     envelope: full
-    encoded-character: skeleton
+    encoded-character: full
 
   Other RFCs/drafts:
     subaddress: full
@@ -193,10 +193,9 @@ TODO
 ----
 
 Current:
-* Implement encoded-character extension
+* Implement variables extension.
 
 Next (in order of descending priority/precedence):
-* Implement variables extension.
 * Finish implementing all extensions supported by cmusieve, except notify.
 * Limit the maximum number of errors. 
 * Verify outgoing mail addresses
diff --git a/sieve/errors/encoded-character.sieve b/sieve/errors/encoded-character.sieve
new file mode 100644
index 0000000000000000000000000000000000000000..f8a7686df62a370a52e268a006c8b800a4f00f88
--- /dev/null
+++ b/sieve/errors/encoded-character.sieve
@@ -0,0 +1,5 @@
+require "encoded-character";
+require "fileinto";
+
+fileinto "INBOX.${unicode:200000}";
+fileinto "INBOX.${Unicode:DF01}";
diff --git a/sieve/tests/encoded-character.sieve b/sieve/tests/encoded-character.sieve
index d085ae8d2edd26d1f19b971bb80b5088080c7538..f9972a5055741e5254dcb7ac931a3d6c7c698abb 100644
--- a/sieve/tests/encoded-character.sieve
+++ b/sieve/tests/encoded-character.sieve
@@ -4,6 +4,8 @@ require "reject";
 
 if address :contains "from" "idiot.com" {
 	reject "You are an ${hex: 69 64 69 6F 74}.";
-} else {
+} elsif header :contains "subject" "idiot" {
 	fileinto "INBOX.${hex: 49 44 49 4F 54}";
+} else {
+	fileinto "INBOX.${unicode: 0052 00e6}vh${unicode: 00f8 006c}";
 }
diff --git a/src/lib-sieve/ext-encoded-character.c b/src/lib-sieve/ext-encoded-character.c
index 9e03039195c54fdfcdbc7c9e8592495800730a6b..a213c086bddbb5e4472cfe0a7c13ca8086e56c4a 100644
--- a/src/lib-sieve/ext-encoded-character.c
+++ b/src/lib-sieve/ext-encoded-character.c
@@ -3,12 +3,13 @@
  *
  * Authors: Stephan Bosch
  * Specification: draft-ietf-sieve-3028bis-13.txt
- * Implementation: skeleton  
- * Status: under development
+ * Implementation: full 
+ * Status: experimental, largely untested
  *
  */
 
 #include "lib.h"
+#include "unichar.h"
 
 #include "sieve-extensions.h"
 #include "sieve-commands.h"
@@ -100,7 +101,7 @@ static bool _parse_hexint
 	return ( digit > 0 );
 }
 
-static bool _decode_hex
+static int _decode_hex
 (const char **in, const char *inend, string_t *result) 
 {
 	int values = 0;
@@ -116,11 +117,12 @@ static bool _decode_hex
 		values++;
 	}
 	
-	return ( values > 0 );
+	return ( values > 0 ? 1 : 0 );
 }
 
-static bool _decode_unicode
-(const char **in, const char *inend, string_t *result) 
+static int _decode_unicode
+(struct sieve_validator *validator, struct sieve_command_context *cmd, 
+	const char **in, const char *inend, string_t *result) 
 {
 	int values = 0;
 	
@@ -130,19 +132,28 @@ static bool _decode_unicode
 		if ( !_skip_whitespace(in, inend) ) return FALSE;
 		
 		if ( !_parse_hexint(in, inend, 6, &unicode_hex) ) break;
-		
-		/* FIXME: unicode is unimplemented */
-		str_append(result, ">>unicode unimplemented<<");
+
+		if ( (unicode_hex <= 0xD7FF) || 
+			(unicode_hex >= 0xE000 && unicode_hex <= 0x10FFFF)	) 
+			uni_ucs4_to_utf8_c((unichar_t) unicode_hex, result);
+		else {
+			sieve_command_validate_error(validator, cmd, 
+				"invalid unicode character 0x%08x in encoded character substitution",
+					unicode_hex);
+			return -1;
+		}	
 		values++;
 	}
 	
-	return ( values > 0 );
+	return ( values > 0 ? 1 : 0 );
 }
 
 bool arg_encoded_string_validate
-(struct sieve_validator *validator ATTR_UNUSED, struct sieve_ast_argument **arg, 
+(struct sieve_validator *validator, struct sieve_ast_argument **arg, 
 		struct sieve_command_context *cmd)
 {
+	bool result = TRUE;
+	int ret;
 	enum { ST_NONE, ST_OPEN, ST_TYPE, ST_CLOSE } 
 		state = ST_NONE;
 	string_t *str = sieve_ast_argument_str(*arg);
@@ -156,7 +167,7 @@ bool arg_encoded_string_validate
 			
 		p = strval;
 		strstart = p;
-		while ( p < strend ) {
+		while ( result && p < strend ) {
 			switch ( state ) {
 			case ST_NONE:
 				if ( *p == '$' ) {
@@ -186,12 +197,18 @@ bool arg_encoded_string_validate
 				str_truncate(tmpstr, 0);
 				if ( strncasecmp(mark, "hex", p - mark) == 0 ) {
 					p++;
-					if ( !_decode_hex(&p, strend, tmpstr) ) 
+					ret = _decode_hex(&p, strend, tmpstr);
+					if ( ret <= 0 ) { 
 						state = ST_NONE;
+						if ( ret < 0 ) result = FALSE;
+					}
 				} else if ( strncasecmp(mark, "unicode", p - mark) == 0 ) {
 					p++;
-					if ( !_decode_unicode(&p, strend, tmpstr) ) 
+					ret = _decode_unicode(validator, cmd, &p, strend, tmpstr);
+					if ( ret <= 0 ) { 
 						state = ST_NONE;
+						if ( ret < 0 ) result = FALSE;
+					}
 				} else {	
 					p++;
 					state = ST_NONE;
diff --git a/src/lib-sieve/ext-envelope.c b/src/lib-sieve/ext-envelope.c
index 6a4013bd75313a8aa47f69be677acad8c91d50d1..b776f7c7921c89679b7800c93aa66e5a97ed2d7c 100644
--- a/src/lib-sieve/ext-envelope.c
+++ b/src/lib-sieve/ext-envelope.c
@@ -2,7 +2,7 @@
  * ------------------
  *
  * Authors: Stephan Bosch
- * Specification: RFC3028
+ * Specification: RFC3028, draft-ietf-sieve-3028bis-13.txt
  * Implementation: full
  * Status: experimental, largely untested
  *
diff --git a/src/lib-sieve/ext-fileinto.c b/src/lib-sieve/ext-fileinto.c
index f4ababf13eed39ab92d54385df567e1d78b0a035..f53cae6ffed5224b8288b61127db48d9a1d33f23 100644
--- a/src/lib-sieve/ext-fileinto.c
+++ b/src/lib-sieve/ext-fileinto.c
@@ -2,7 +2,7 @@
  * ------------------
  *
  * Authors: Stephan Bosch
- * Specification: RFC3028
+ * Specification: RFC3028, draft-ietf-sieve-3028bis-13.txt
  * Implementation: full
  * Status: experimental, largely untested
  *
diff --git a/src/sieve-bin/mail-raw.c b/src/sieve-bin/mail-raw.c
index fb314c156441d75d5fcf8eaa2d44d60249a16492..19b400f2f73f76947afe65e9d542ad29420487be 100644
--- a/src/sieve-bin/mail-raw.c
+++ b/src/sieve-bin/mail-raw.c
@@ -65,8 +65,7 @@ static struct istream *create_raw_stream(int fd)
 		input2 = input;
 		i_stream_ref(input2);
 	} else {
-		input2 = i_stream_create_limit(input, input->v_offset,
-			(uoff_t)-1);
+		input2 = i_stream_create_limit(input, (uoff_t)-1);
 	}
 	i_stream_unref(&input);