summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 7a031eb)
raw | patch | inline | side by side (parent: 7a031eb)
author | kedder <kedder@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Wed, 15 Jan 2003 22:17:20 +0000 (22:17 +0000) | ||
committer | kedder <kedder@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Wed, 15 Jan 2003 22:17:20 +0000 (22:17 +0000) |
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1460 57a73879-2fb5-44c3-a270-3262357dd7e2
diff --git a/CHANGES.txt b/CHANGES.txt
index d52a001d78308a82224a5051929e1e5968bc25c5..cf4737363fd16c4f2ca7e7465aa6214f61b6ccc8 100644 (file)
--- a/CHANGES.txt
+++ b/CHANGES.txt
- fix StringHTMLProperty hyperlinking
- added mysql backend
- fixes to CGI form handling (NEEDS BACKPORTING TO 0.5)
- fix StringHTMLProperty hyperlinking
- added mysql backend
- fixes to CGI form handling (NEEDS BACKPORTING TO 0.5)
+- applied unicode patch. All data is stored in utf-8. Incoming messages
+ converted from any encoding to utf-8, outgoing messages are encoded
+ according to rfc2822 (sf bug 568873)
2003-??-?? 0.5.5
2003-??-?? 0.5.5
index 14b20f94ce14c34b309d69e4e7ff6c41e3df3e0d..2e1bd9b8fa5c399f5d1d8b215e14d89cd0dd59a7 100644 (file)
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-#$Id: back_anydbm.py,v 1.96 2003-01-08 05:39:40 richard Exp $
+#$Id: back_anydbm.py,v 1.97 2003-01-15 22:17:19 kedder Exp $
'''
This module defines a backend that saves the hyperdatabase in a database
chosen by anydbm. It is guaranteed to always be available in python
'''
This module defines a backend that saves the hyperdatabase in a database
chosen by anydbm. It is guaranteed to always be available in python
(self.classname, newid, key))
elif isinstance(prop, String):
(self.classname, newid, key))
elif isinstance(prop, String):
- if type(value) != type(''):
+ if type(value) != type('') and type(value) != type(u''):
raise TypeError, 'new property "%s" not a string'%key
elif isinstance(prop, Password):
raise TypeError, 'new property "%s" not a string'%key
elif isinstance(prop, Password):
journalvalues[propname] = tuple(l)
elif isinstance(prop, String):
journalvalues[propname] = tuple(l)
elif isinstance(prop, String):
- if value is not None and type(value) != type(''):
+ if value is not None and type(value) != type('') and type(value) != type(u''):
raise TypeError, 'new property "%s" not a string'%propname
elif isinstance(prop, Password):
raise TypeError, 'new property "%s" not a string'%propname
elif isinstance(prop, Password):
index 550b51b7024d9b97846274dcce4ce8ac9d44d5ab..30fd3712fe5221a99941bf3c842fbcba1e3d1d20 100644 (file)
-# $Id: rdbms_common.py,v 1.28 2003-01-12 23:53:20 richard Exp $
+# $Id: rdbms_common.py,v 1.29 2003-01-15 22:17:19 kedder Exp $
''' Relational database (SQL) backend common code.
Basics:
''' Relational database (SQL) backend common code.
Basics:
(self.classname, newid, key))
elif isinstance(prop, String):
(self.classname, newid, key))
elif isinstance(prop, String):
- if type(value) != type(''):
+ if type(value) != type('') and type(value) != type(u''):
raise TypeError, 'new property "%s" not a string'%key
elif isinstance(prop, Password):
raise TypeError, 'new property "%s" not a string'%key
elif isinstance(prop, Password):
journalvalues[propname] = tuple(l)
elif isinstance(prop, String):
journalvalues[propname] = tuple(l)
elif isinstance(prop, String):
- if value is not None and type(value) != type(''):
+ if value is not None and type(value) != type('') and type(value) != type(u''):
raise TypeError, 'new property "%s" not a string'%propname
elif isinstance(prop, Password):
raise TypeError, 'new property "%s" not a string'%propname
elif isinstance(prop, Password):
diff --git a/roundup/mailgw.py b/roundup/mailgw.py
index b9dd886b85dd9e770e59e8ec3ffacefbdf194e9e..a12f909c49c20c4b24bf82a2045b89caea7a1a86 100644 (file)
--- a/roundup/mailgw.py
+++ b/roundup/mailgw.py
an exception, the original message is bounced back to the sender with the
explanatory message given in the exception.
an exception, the original message is bounced back to the sender with the
explanatory message given in the exception.
-$Id: mailgw.py,v 1.106 2003-01-12 00:03:10 richard Exp $
+$Id: mailgw.py,v 1.107 2003-01-15 22:17:19 kedder Exp $
'''
import string, re, os, mimetools, cStringIO, smtplib, socket, binascii, quopri
'''
import string, re, os, mimetools, cStringIO, smtplib, socket, binascii, quopri
import traceback, MimeWriter
import hyperdb, date, password
import traceback, MimeWriter
import hyperdb, date, password
+import rfc2822
+
SENDMAILDEBUG = os.environ.get('SENDMAILDEBUG', '')
class MailGWError(ValueError):
SENDMAILDEBUG = os.environ.get('SENDMAILDEBUG', '')
class MailGWError(ValueError):
s.seek(0)
return Message(s)
s.seek(0)
return Message(s)
+ def getheader(self, name, default=None):
+ hdr = mimetools.Message.getheader(self, name, default)
+ return rfc2822.decode_header(hdr)
+
subject_re = re.compile(r'(?P<refwd>\s*\W?\s*(fw|fwd|re|aw)\W\s*)*'
r'\s*(?P<quote>")?(\[(?P<classname>[^\d\s]+)(?P<nodeid>\d+)?\])?'
r'\s*(?P<title>[^[]+)?"?(\[(?P<args>.+?)\])?', re.I)
subject_re = re.compile(r'(?P<refwd>\s*\W?\s*(fw|fwd|re|aw)\W\s*)*'
r'\s*(?P<quote>")?(\[(?P<classname>[^\d\s]+)(?P<nodeid>\d+)?\])?'
r'\s*(?P<title>[^[]+)?"?(\[(?P<args>.+?)\])?', re.I)
writer.addheader('MIME-Version', '1.0')
part = writer.startmultipartbody('mixed')
part = writer.nextpart()
writer.addheader('MIME-Version', '1.0')
part = writer.startmultipartbody('mixed')
part = writer.nextpart()
- body = part.startbody('text/plain')
+ body = part.startbody('text/plain; charset=utf-8')
body.write('\n'.join(error))
# attach the original message to the returned message
body.write('\n'.join(error))
# attach the original message to the returned message
else:
# take it as text
data = part.fp.read()
else:
# take it as text
data = part.fp.read()
- return data
+
+ # Encode message to unicode
+ charset = rfc2822.unaliasCharset(part.getparam("charset"))
+ if charset:
+ # Do conversion only if charset specified
+ edata = unicode(data, charset).encode('utf-8')
+ # Convert from dos eol to unix
+ edata = edata.replace('\r\n', '\n')
+ else:
+ # Leave message content as is
+ edata = data
+
+ return edata
def handle_message(self, message):
''' message - a Message instance
def handle_message(self, message):
''' message - a Message instance
diff --git a/roundup/rfc2822.py b/roundup/rfc2822.py
--- /dev/null
+++ b/roundup/rfc2822.py
@@ -0,0 +1,160 @@
+import re
+from binascii import b2a_base64, a2b_base64
+
+ecre = re.compile(r'''
+ =\? # literal =?
+ (?P<charset>[^?]*?) # non-greedy up to the next ? is the charset
+ \? # literal ?
+ (?P<encoding>[qb]) # either a "q" or a "b", case insensitive
+ \? # literal ?
+ (?P<encoded>.*?) # non-greedy up to the next ?= is the encoded string
+ \?= # literal ?=
+ ''', re.VERBOSE | re.IGNORECASE)
+
+hqre = re.compile(r'^[-a-zA-Z0-9!*+/\[\]., ]+$')
+
+def base64_decode(s, convert_eols=None):
+ """Decode a raw base64 string.
+
+ If convert_eols is set to a string value, all canonical email linefeeds,
+ e.g. "\\r\\n", in the decoded text will be converted to the value of
+ convert_eols. os.linesep is a good choice for convert_eols if you are
+ decoding a text attachment.
+
+ This function does not parse a full MIME header value encoded with
+ base64 (like =?iso-8895-1?b?bmloISBuaWgh?=) -- please use the high
+ level email.Header class for that functionality.
+
+ Taken from 'email' module
+ """
+ if not s:
+ return s
+
+ dec = a2b_base64(s)
+ if convert_eols:
+ return dec.replace(CRLF, convert_eols)
+ return dec
+
+def unquote_match(match):
+ """Turn a match in the form =AB to the ASCII character with value 0xab
+
+ Taken from 'email' module
+ """
+ s = match.group(0)
+ return chr(int(s[1:3], 16))
+
+def qp_decode(s):
+ """Decode a string encoded with RFC 2045 MIME header `Q' encoding.
+
+ This function does not parse a full MIME header value encoded with
+ quoted-printable (like =?iso-8895-1?q?Hello_World?=) -- please use
+ the high level email.Header class for that functionality.
+
+ Taken from 'email' module
+ """
+ s = s.replace('_', ' ')
+ return re.sub(r'=\w{2}', unquote_match, s)
+
+def _decode_header(header):
+ """Decode a message header value without converting charset.
+
+ Returns a list of (decoded_string, charset) pairs containing each of the
+ decoded parts of the header. Charset is None for non-encoded parts of the
+ header, otherwise a lower-case string containing the name of the character
+ set specified in the encoded string.
+
+ Taken from 'email' module
+ """
+ # If no encoding, just return the header
+ header = str(header)
+ if not ecre.search(header):
+ return [(header, None)]
+
+ decoded = []
+ dec = ''
+ for line in header.splitlines():
+ # This line might not have an encoding in it
+ if not ecre.search(line):
+ decoded.append((line, None))
+ continue
+
+ parts = ecre.split(line)
+ while parts:
+ unenc = parts.pop(0)
+ if unenc:
+ if unenc.strip():
+ decoded.append((unenc, None))
+ if parts:
+ charset, encoding = [s.lower() for s in parts[0:2]]
+ encoded = parts[2]
+ dec = ''
+ if encoding == 'q':
+ dec = qp_decode(encoded)
+ elif encoding == 'b':
+ dec = base64_decode(encoded)
+ else:
+ dec = encoded
+
+ if decoded and decoded[-1][1] == charset:
+ decoded[-1] = (decoded[-1][0] + dec, decoded[-1][1])
+ else:
+ decoded.append((dec, charset))
+ del parts[0:3]
+ return decoded
+
+def decode_header(hdr):
+ """ Decodes rfc2822 encoded header and return utf-8 encoded string
+ """
+ if not hdr:
+ return None
+ outs = u""
+ for section in _decode_header(hdr):
+ charset = unaliasCharset(section[1])
+ outs += unicode(section[0], charset or 'iso-8859-1', 'replace')
+ return outs.encode('utf-8')
+
+def encode_header(header):
+ """ Will encode in quoted-printable encoding only if header
+ contains non latin characters
+ """
+
+ # Return empty headers unchanged
+ if not header:
+ return header
+
+ global hqre
+ # return plain header if it is not contains non-ascii characters
+ if hqre.match(header):
+ return header
+
+ charset = 'utf-8'
+ quoted = ''
+ #max_encoded = 76 - len(charset) - 7
+ for c in header:
+ # Space may be represented as _ instead of =20 for readability
+ if c == ' ':
+ quoted += '_'
+ # These characters can be included verbatim
+ elif hqre.match(c):
+ quoted += c
+ # Otherwise, replace with hex value like =E2
+ else:
+ quoted += "=%02X" % ord(c)
+ plain = 0
+
+ return '=?%s?q?%s?=' % (charset, quoted)
+
+def unaliasCharset(charset):
+ if charset:
+ return charset.lower().replace("windows-", 'cp')
+ #return charset_table.get(charset.lower(), charset)
+ return None
+
+def test():
+ print encode_header("Contrary, Mary")
+ #print unaliasCharset('Windows-1251')
+
+if __name__ == '__main__':
+ test()
+
+# vim: et
diff --git a/roundup/roundupdb.py b/roundup/roundupdb.py
index a45ec16ac1748360388d003881da9ac3d841b85a..4b3761aebd9772f0f18bf56717e1f5205a047237 100644 (file)
--- a/roundup/roundupdb.py
+++ b/roundup/roundupdb.py
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: roundupdb.py,v 1.77 2003-01-14 22:19:27 richard Exp $
+# $Id: roundupdb.py,v 1.78 2003-01-15 22:17:19 kedder Exp $
__doc__ = """
Extending hyperdb with types specific to issue-tracking.
__doc__ = """
Extending hyperdb with types specific to issue-tracking.
import re, os, smtplib, socket, time, random
import MimeWriter, cStringIO
import base64, quopri, mimetypes
import re, os, smtplib, socket, time, random
import MimeWriter, cStringIO
import base64, quopri, mimetypes
+
+from rfc2822 import encode_header
+
# if available, use the 'email' module, otherwise fallback to 'rfc822'
try :
from email.Utils import formataddr as straddr
# if available, use the 'email' module, otherwise fallback to 'rfc822'
try :
from email.Utils import formataddr as straddr
# create the message
message = cStringIO.StringIO()
writer = MimeWriter.MimeWriter(message)
# create the message
message = cStringIO.StringIO()
writer = MimeWriter.MimeWriter(message)
- writer.addheader('Subject', '[%s%s] %s'%(cn, nodeid, title))
+ writer.addheader('Subject', '[%s%s] %s'%(cn, nodeid, encode_header(title)))
writer.addheader('To', ', '.join(sendto))
writer.addheader('To', ', '.join(sendto))
- writer.addheader('From', straddr((authname + from_tag, from_address)))
+ writer.addheader('From', straddr((encode_header(authname) +
+ from_tag, from_address)))
writer.addheader('Reply-To', straddr((self.db.config.TRACKER_NAME,
from_address)))
writer.addheader('Date', time.strftime("%a, %d %b %Y %H:%M:%S +0000",
writer.addheader('Reply-To', straddr((self.db.config.TRACKER_NAME,
from_address)))
writer.addheader('Date', time.strftime("%a, %d %b %Y %H:%M:%S +0000",
part = writer.startmultipartbody('mixed')
part = writer.nextpart()
part.addheader('Content-Transfer-Encoding', 'quoted-printable')
part = writer.startmultipartbody('mixed')
part = writer.nextpart()
part.addheader('Content-Transfer-Encoding', 'quoted-printable')
- body = part.startbody('text/plain')
+ body = part.startbody('text/plain; charset=utf-8')
body.write(content_encoded)
for fileid in message_files:
name = files.get(fileid, 'name')
body.write(content_encoded)
for fileid in message_files:
name = files.get(fileid, 'name')
writer.lastpart()
else:
writer.addheader('Content-Transfer-Encoding', 'quoted-printable')
writer.lastpart()
else:
writer.addheader('Content-Transfer-Encoding', 'quoted-printable')
- body = writer.startbody('text/plain')
+ body = writer.startbody('text/plain; charset=utf-8')
body.write(content_encoded)
# now try to send the message
body.write(content_encoded)
# now try to send the message
diff --git a/roundup/templates/classic/html/_generic.help b/roundup/templates/classic/html/_generic.help
index 0197597a74ee23816f5d1277592af45737474bba..9cb15354aff5c8f6dfb189eb70a7416ff9b31b28 100644 (file)
<html>
<head>
<link rel="stylesheet" type="text/css" href="_file/style.css">
<html>
<head>
<link rel="stylesheet" type="text/css" href="_file/style.css">
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8;">
</head>
<body class="body" marginwidth="0" marginheight="0">
</head>
<body class="body" marginwidth="0" marginheight="0">
index 8966ce6282d918286375d0fa2298a307af0c070c..033a379f292de3ba1aa9a87136216791e4b0551c 100644 (file)
<html metal:define-macro="icing">
<head>
<title metal:define-slot="head_title">title goes here</title>
<html metal:define-macro="icing">
<head>
<title metal:define-slot="head_title">title goes here</title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8;">
<link rel="stylesheet" type="text/css" href="_file/style.css">
<link rel="stylesheet" type="text/css" href="_file/style.css">
diff --git a/roundup/templates/minimal/html/_generic.help b/roundup/templates/minimal/html/_generic.help
index bced017b165aeb0c4078754e8269c9ae92283a19..ff4c7a3c0f97fea0e42d15b96fb6d743e2444b97 100644 (file)
<html>
<head>
<html>
<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8;">
<link rel="stylesheet" type="text/css" href="_file/style.css">
</head>
<body class="body" marginwidth="0" marginheight="0">
<link rel="stylesheet" type="text/css" href="_file/style.css">
</head>
<body class="body" marginwidth="0" marginheight="0">
index 3c139cfcb165255138430aa4dc4a08026b2d54d6..219a52a90f8a494e01a5f25bc09ef6d7b98b5a88 100644 (file)
<html metal:define-macro="icing">
<head>
<title metal:define-slot="head_title">title goes here</title>
<html metal:define-macro="icing">
<head>
<title metal:define-slot="head_title">title goes here</title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8;">
<link rel="stylesheet" type="text/css" href="_file/style.css">
<link rel="stylesheet" type="text/css" href="_file/style.css">
diff --git a/test/test_mailgw.py b/test/test_mailgw.py
index a680dfb8472751c39b933291a002632729b076ef..5fed0029dc83f2217b1a48cad58741b589f7f589 100644 (file)
--- a/test/test_mailgw.py
+++ b/test/test_mailgw.py
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
-# $Id: test_mailgw.py,v 1.37 2002-12-18 00:42:03 richard Exp $
+# $Id: test_mailgw.py,v 1.38 2003-01-15 22:17:20 kedder Exp $
import unittest, cStringIO, tempfile, os, shutil, errno, imp, sys, difflib
import unittest, cStringIO, tempfile, os, shutil, errno, imp, sys, difflib
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
'''FROM: roundup-admin@your.tracker.email.domain.example
TO: chef@bork.bork.bork, mary@test, richard@test
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
'''FROM: roundup-admin@your.tracker.email.domain.example
TO: chef@bork.bork.bork, mary@test, richard@test
-Content-Type: text/plain
+Content-Type: text/plain; charset=utf-8
Subject: [issue1] Testing...
To: chef@bork.bork.bork, mary@test, richard@test
From: "Bork, Chef" <issue_tracker@your.tracker.email.domain.example>
Subject: [issue1] Testing...
To: chef@bork.bork.bork, mary@test, richard@test
From: "Bork, Chef" <issue_tracker@your.tracker.email.domain.example>
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
'''FROM: roundup-admin@your.tracker.email.domain.example
TO: chef@bork.bork.bork, richard@test
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
'''FROM: roundup-admin@your.tracker.email.domain.example
TO: chef@bork.bork.bork, richard@test
-Content-Type: text/plain
+Content-Type: text/plain; charset=utf-8
Subject: [issue1] Testing...
To: chef@bork.bork.bork, richard@test
From: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
Subject: [issue1] Testing...
To: chef@bork.bork.bork, richard@test
From: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
'''FROM: roundup-admin@your.tracker.email.domain.example
TO: chef@bork.bork.bork, john@test, mary@test
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
'''FROM: roundup-admin@your.tracker.email.domain.example
TO: chef@bork.bork.bork, john@test, mary@test
-Content-Type: text/plain
+Content-Type: text/plain; charset=utf-8
Subject: [issue1] Testing...
To: chef@bork.bork.bork, john@test, mary@test
From: richard <issue_tracker@your.tracker.email.domain.example>
Subject: [issue1] Testing...
To: chef@bork.bork.bork, john@test, mary@test
From: richard <issue_tracker@your.tracker.email.domain.example>
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
'''FROM: roundup-admin@your.tracker.email.domain.example
TO: chef@bork.bork.bork, john@test, mary@test
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
'''FROM: roundup-admin@your.tracker.email.domain.example
TO: chef@bork.bork.bork, john@test, mary@test
-Content-Type: text/plain
+Content-Type: text/plain; charset=utf-8
Subject: [issue1] Testing...
To: chef@bork.bork.bork, john@test, mary@test
From: richard <issue_tracker@your.tracker.email.domain.example>
Subject: [issue1] Testing...
To: chef@bork.bork.bork, john@test, mary@test
From: richard <issue_tracker@your.tracker.email.domain.example>
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
'''FROM: roundup-admin@your.tracker.email.domain.example
TO: chef@bork.bork.bork, richard@test
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
'''FROM: roundup-admin@your.tracker.email.domain.example
TO: chef@bork.bork.bork, richard@test
-Content-Type: text/plain
+Content-Type: text/plain; charset=utf-8
Subject: [issue1] Testing...
To: chef@bork.bork.bork, richard@test
From: John Doe <issue_tracker@your.tracker.email.domain.example>
Subject: [issue1] Testing...
To: chef@bork.bork.bork, richard@test
From: John Doe <issue_tracker@your.tracker.email.domain.example>
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
'''FROM: roundup-admin@your.tracker.email.domain.example
TO: chef@bork.bork.bork
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
'''FROM: roundup-admin@your.tracker.email.domain.example
TO: chef@bork.bork.bork
-Content-Type: text/plain
+Content-Type: text/plain; charset=utf-8
Subject: [issue1] Testing...
To: chef@bork.bork.bork
From: richard <issue_tracker@your.tracker.email.domain.example>
Subject: [issue1] Testing...
To: chef@bork.bork.bork
From: richard <issue_tracker@your.tracker.email.domain.example>
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
'''FROM: roundup-admin@your.tracker.email.domain.example
TO: chef@bork.bork.bork, john@test, richard@test
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
'''FROM: roundup-admin@your.tracker.email.domain.example
TO: chef@bork.bork.bork, john@test, richard@test
-Content-Type: text/plain
+Content-Type: text/plain; charset=utf-8
Subject: [issue1] Testing...
To: chef@bork.bork.bork, john@test, richard@test
From: John Doe <issue_tracker@your.tracker.email.domain.example>
Subject: [issue1] Testing...
To: chef@bork.bork.bork, john@test, richard@test
From: John Doe <issue_tracker@your.tracker.email.domain.example>
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
'''FROM: roundup-admin@your.tracker.email.domain.example
TO: chef@bork.bork.bork, richard@test
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
'''FROM: roundup-admin@your.tracker.email.domain.example
TO: chef@bork.bork.bork, richard@test
-Content-Type: text/plain
+Content-Type: text/plain; charset=utf-8
Subject: [issue1] Testing...
To: chef@bork.bork.bork, richard@test
From: John Doe <issue_tracker@your.tracker.email.domain.example>
Subject: [issue1] Testing...
To: chef@bork.bork.bork, richard@test
From: John Doe <issue_tracker@your.tracker.email.domain.example>
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
'''FROM: roundup-admin@your.tracker.email.domain.example
TO: chef@bork.bork.bork
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
'''FROM: roundup-admin@your.tracker.email.domain.example
TO: chef@bork.bork.bork
-Content-Type: text/plain
+Content-Type: text/plain; charset=utf-8
Subject: [issue1] Testing...
To: chef@bork.bork.bork
From: richard <issue_tracker@your.tracker.email.domain.example>
Subject: [issue1] Testing...
To: chef@bork.bork.bork
From: richard <issue_tracker@your.tracker.email.domain.example>
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
'''FROM: roundup-admin@your.tracker.email.domain.example
TO: chef@bork.bork.bork, richard@test
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
'''FROM: roundup-admin@your.tracker.email.domain.example
TO: chef@bork.bork.bork, richard@test
-Content-Type: text/plain
+Content-Type: text/plain; charset=utf-8
Subject: [issue1] Testing...
To: chef@bork.bork.bork, richard@test
From: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
Subject: [issue1] Testing...
To: chef@bork.bork.bork, richard@test
From: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
Contrary, Mary <mary@test> added the comment:
Contrary, Mary <mary@test> added the comment:
-A message with encoding (encoded oe =F6)
+A message with encoding (encoded oe =C3=B6)
----------
status: unread -> chatting
----------
status: unread -> chatting
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
'''FROM: roundup-admin@your.tracker.email.domain.example
TO: chef@bork.bork.bork, richard@test
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
'''FROM: roundup-admin@your.tracker.email.domain.example
TO: chef@bork.bork.bork, richard@test
-Content-Type: text/plain
+Content-Type: text/plain; charset=utf-8
Subject: [issue1] Testing...
To: chef@bork.bork.bork, richard@test
From: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
Subject: [issue1] Testing...
To: chef@bork.bork.bork, richard@test
From: "Contrary, Mary" <issue_tracker@your.tracker.email.domain.example>
Contrary, Mary <mary@test> added the comment:
Contrary, Mary <mary@test> added the comment:
-A message with first part encoded (encoded oe =F6)
+A message with first part encoded (encoded oe =C3=B6)
----------
status: unread -> chatting
----------
status: unread -> chatting
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
'''FROM: roundup-admin@your.tracker.email.domain.example
TO: chef@bork.bork.bork
self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(),
'''FROM: roundup-admin@your.tracker.email.domain.example
TO: chef@bork.bork.bork
-Content-Type: text/plain
+Content-Type: text/plain; charset=utf-8
Subject: [issue1] Testing...
To: chef@bork.bork.bork
From: richard <issue_tracker@your.tracker.email.domain.example>
Subject: [issue1] Testing...
To: chef@bork.bork.bork
From: richard <issue_tracker@your.tracker.email.domain.example>