1 import unittest
2
3 import sys, io, subprocess
4 import quopri
5
6 from test import support
7
8
9 ENCSAMPLE = b"""\
10 Here'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
24 characters... have fun!
25 """
26
27 # First line ends with a space
28 DECSAMPLE = b"Here's a bunch of special \n" + \
29 b"""\
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
43 characters... have fun!
44 """
45
46
47 def 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
65 class ESC[4;38;5;81mQuopriTestCase(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
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=
90 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'''),
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=
99 zz'''),
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=
104 zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz'''),
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
110 zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz''',
111 b'''yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy=
112 yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
113 zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz'''),
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
212 if __name__ == "__main__":
213 unittest.main()