1import unittest 2 3import sys, io, subprocess 4import quopri 5 6from test import support 7 8 9ENCSAMPLE = b"""\ 10Here's a bunch of special=20 11 12=A1=A2=A3=A4=A5=A6=A7=A8=A9 13=AA=AB=AC=AD=AE=AF=B0=B1=B2=B3 14=B4=B5=B6=B7=B8=B9=BA=BB=BC=BD=BE 15=BF=C0=C1=C2=C3=C4=C5=C6 16=C7=C8=C9=CA=CB=CC=CD=CE=CF 17=D0=D1=D2=D3=D4=D5=D6=D7 18=D8=D9=DA=DB=DC=DD=DE=DF 19=E0=E1=E2=E3=E4=E5=E6=E7 20=E8=E9=EA=EB=EC=ED=EE=EF 21=F0=F1=F2=F3=F4=F5=F6=F7 22=F8=F9=FA=FB=FC=FD=FE=FF 23 24characters... have fun! 25""" 26 27# First line ends with a space 28DECSAMPLE = b"Here's a bunch of special \n" + \ 29b"""\ 30 31\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9 32\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3 33\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe 34\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6 35\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf 36\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7 37\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf 38\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7 39\xe8\xe9\xea\xeb\xec\xed\xee\xef 40\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7 41\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff 42 43characters... have fun! 44""" 45 46 47def withpythonimplementation(testfunc): 48 def newtest(self): 49 # Test default implementation 50 testfunc(self) 51 # Test Python implementation 52 if quopri.b2a_qp is not None or quopri.a2b_qp is not None: 53 oldencode = quopri.b2a_qp 54 olddecode = quopri.a2b_qp 55 try: 56 quopri.b2a_qp = None 57 quopri.a2b_qp = None 58 testfunc(self) 59 finally: 60 quopri.b2a_qp = oldencode 61 quopri.a2b_qp = olddecode 62 newtest.__name__ = testfunc.__name__ 63 return newtest 64 65class QuopriTestCase(unittest.TestCase): 66 # Each entry is a tuple of (plaintext, encoded string). These strings are 67 # used in the "quotetabs=0" tests. 68 STRINGS = ( 69 # Some normal strings 70 (b'hello', b'hello'), 71 (b'''hello 72 there 73 world''', b'''hello 74 there 75 world'''), 76 (b'''hello 77 there 78 world 79''', b'''hello 80 there 81 world 82'''), 83 (b'\201\202\203', b'=81=82=83'), 84 # Add some trailing MUST QUOTE strings 85 (b'hello ', b'hello=20'), 86 (b'hello\t', b'hello=09'), 87 # Some long lines. First, a single line of 108 characters 88 (b'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\xd8\xd9\xda\xdb\xdc\xdd\xde\xdfxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 89 b'''xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=D8=D9=DA=DB=DC=DD=DE=DFx= 90xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'''), 91 # A line of exactly 76 characters, no soft line break should be needed 92 (b'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy', 93 b'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy'), 94 # A line of 77 characters, forcing a soft line break at position 75, 95 # and a second line of exactly 2 characters (because the soft line 96 # break `=' sign counts against the line length limit). 97 (b'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz', 98 b'''zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz= 99zz'''), 100 # A line of 151 characters, forcing a soft line break at position 75, 101 # with a second line of exactly 76 characters and no trailing = 102 (b'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz', 103 b'''zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz= 104zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz'''), 105 # A string containing a hard line break, but which the first line is 106 # 151 characters and the second line is exactly 76 characters. This 107 # should leave us with three lines, the first which has a soft line 108 # break, and which the second and third do not. 109 (b'''yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy 110zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz''', 111 b'''yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy= 112yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy 113zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz'''), 114 # Now some really complex stuff ;) 115 (DECSAMPLE, ENCSAMPLE), 116 ) 117 118 # These are used in the "quotetabs=1" tests. 119 ESTRINGS = ( 120 (b'hello world', b'hello=20world'), 121 (b'hello\tworld', b'hello=09world'), 122 ) 123 124 # These are used in the "header=1" tests. 125 HSTRINGS = ( 126 (b'hello world', b'hello_world'), 127 (b'hello_world', b'hello=5Fworld'), 128 ) 129 130 @withpythonimplementation 131 def test_encodestring(self): 132 for p, e in self.STRINGS: 133 self.assertEqual(quopri.encodestring(p), e) 134 135 @withpythonimplementation 136 def test_decodestring(self): 137 for p, e in self.STRINGS: 138 self.assertEqual(quopri.decodestring(e), p) 139 140 @withpythonimplementation 141 def test_decodestring_double_equals(self): 142 # Issue 21511 - Ensure that byte string is compared to byte string 143 # instead of int byte value 144 decoded_value, encoded_value = (b"123=four", b"123==four") 145 self.assertEqual(quopri.decodestring(encoded_value), decoded_value) 146 147 @withpythonimplementation 148 def test_idempotent_string(self): 149 for p, e in self.STRINGS: 150 self.assertEqual(quopri.decodestring(quopri.encodestring(e)), e) 151 152 @withpythonimplementation 153 def test_encode(self): 154 for p, e in self.STRINGS: 155 infp = io.BytesIO(p) 156 outfp = io.BytesIO() 157 quopri.encode(infp, outfp, quotetabs=False) 158 self.assertEqual(outfp.getvalue(), e) 159 160 @withpythonimplementation 161 def test_decode(self): 162 for p, e in self.STRINGS: 163 infp = io.BytesIO(e) 164 outfp = io.BytesIO() 165 quopri.decode(infp, outfp) 166 self.assertEqual(outfp.getvalue(), p) 167 168 @withpythonimplementation 169 def test_embedded_ws(self): 170 for p, e in self.ESTRINGS: 171 self.assertEqual(quopri.encodestring(p, quotetabs=True), e) 172 self.assertEqual(quopri.decodestring(e), p) 173 174 @withpythonimplementation 175 def test_encode_header(self): 176 for p, e in self.HSTRINGS: 177 self.assertEqual(quopri.encodestring(p, header=True), e) 178 179 @withpythonimplementation 180 def test_decode_header(self): 181 for p, e in self.HSTRINGS: 182 self.assertEqual(quopri.decodestring(e, header=True), p) 183 184 @support.requires_subprocess() 185 def test_scriptencode(self): 186 (p, e) = self.STRINGS[-1] 187 process = subprocess.Popen([sys.executable, "-mquopri"], 188 stdin=subprocess.PIPE, stdout=subprocess.PIPE) 189 self.addCleanup(process.stdout.close) 190 cout, cerr = process.communicate(p) 191 # On Windows, Python will output the result to stdout using 192 # CRLF, as the mode of stdout is text mode. To compare this 193 # with the expected result, we need to do a line-by-line comparison. 194 cout = cout.decode('latin-1').splitlines() 195 e = e.decode('latin-1').splitlines() 196 assert len(cout)==len(e) 197 for i in range(len(cout)): 198 self.assertEqual(cout[i], e[i]) 199 self.assertEqual(cout, e) 200 201 @support.requires_subprocess() 202 def test_scriptdecode(self): 203 (p, e) = self.STRINGS[-1] 204 process = subprocess.Popen([sys.executable, "-mquopri", "-d"], 205 stdin=subprocess.PIPE, stdout=subprocess.PIPE) 206 self.addCleanup(process.stdout.close) 207 cout, cerr = process.communicate(e) 208 cout = cout.decode('latin-1') 209 p = p.decode('latin-1') 210 self.assertEqual(cout.splitlines(), p.splitlines()) 211 212if __name__ == "__main__": 213 unittest.main() 214