Skip to content

Commit 8dad7ad

Browse files
authored
Merge pull request #3816 from chang-57/binascii_qp
Add `binascii` `a2b_qp`
2 parents 02e05e4 + 1f13d11 commit 8dad7ad

File tree

2 files changed

+278
-6
lines changed

2 files changed

+278
-6
lines changed

Lib/test/test_binascii.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,8 +266,6 @@ def test_hex_separator(self):
266266
expected1 = s.hex(':').encode('ascii')
267267
self.assertEqual(binascii.b2a_hex(self.type2test(s), ':'), expected1)
268268

269-
# TODO: RUSTPYTHON
270-
@unittest.expectedFailure
271269
def test_qp(self):
272270
type2test = self.type2test
273271
a2b_qp = binascii.a2b_qp

stdlib/src/binascii.rs

Lines changed: 278 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ mod decl {
1111
};
1212
use itertools::Itertools;
1313

14+
const MAXLINESIZE: usize = 76;
15+
1416
#[pyattr(name = "Error", once)]
1517
fn error_type(vm: &VirtualMachine) -> PyTypeRef {
1618
vm.ctx.new_exception_type(
@@ -176,13 +178,285 @@ mod decl {
176178
// The 64 instead of the expected 63 is because
177179
// there are a few uuencodes out there that use
178180
// '`' as zero instead of space.
179-
if !(0x20..=0x60).contains(c) {
181+
if !(b' '..=(b' ' + 64)).contains(c) {
180182
if [b'\r', b'\n'].contains(c) {
181183
return Ok(0);
182184
}
183185
return Err(vm.new_value_error("Illegal char".to_string()));
184186
}
185-
Ok((*c - 0x20) & 0x3f)
187+
Ok((*c - b' ') & 0x3f)
188+
}
189+
190+
#[derive(FromArgs)]
191+
struct A2bQpArgs {
192+
#[pyarg(any)]
193+
data: ArgAsciiBuffer,
194+
#[pyarg(named, default = "false")]
195+
header: bool,
196+
}
197+
#[pyfunction]
198+
fn a2b_qp(args: A2bQpArgs) -> PyResult<Vec<u8>> {
199+
let s = args.data;
200+
let header = args.header;
201+
s.with_ref(|buffer| {
202+
let len = buffer.len();
203+
let mut out_data = Vec::with_capacity(len);
204+
205+
let mut idx = 0;
206+
207+
while idx < len {
208+
if buffer[idx] == b'=' {
209+
idx += 1;
210+
if idx >= len {
211+
break;
212+
}
213+
// Soft line breaks
214+
if (buffer[idx] == b'\n') || (buffer[idx] == b'\r') {
215+
if buffer[idx] != b'\n' {
216+
while idx < len && buffer[idx] != b'\n' {
217+
idx += 1;
218+
}
219+
}
220+
if idx < len {
221+
idx += 1;
222+
}
223+
} else if buffer[idx] == b'=' {
224+
// roken case from broken python qp
225+
out_data.push(b'=');
226+
idx += 1;
227+
} else if idx + 1 < len
228+
&& ((buffer[idx] >= b'A' && buffer[idx] <= b'F')
229+
|| (buffer[idx] >= b'a' && buffer[idx] <= b'f')
230+
|| (buffer[idx] >= b'0' && buffer[idx] <= b'9'))
231+
&& ((buffer[idx + 1] >= b'A' && buffer[idx + 1] <= b'F')
232+
|| (buffer[idx + 1] >= b'a' && buffer[idx + 1] <= b'f')
233+
|| (buffer[idx + 1] >= b'0' && buffer[idx + 1] <= b'9'))
234+
{
235+
// hexval
236+
if let (Some(ch1), Some(ch2)) =
237+
(unhex_nibble(buffer[idx]), unhex_nibble(buffer[idx + 1]))
238+
{
239+
out_data.push(ch1 << 4 | ch2);
240+
}
241+
idx += 2;
242+
} else {
243+
out_data.push(b'=');
244+
}
245+
} else if header && buffer[idx] == b'_' {
246+
out_data.push(b' ');
247+
idx += 1;
248+
} else {
249+
out_data.push(buffer[idx]);
250+
idx += 1;
251+
}
252+
}
253+
254+
Ok(out_data)
255+
})
256+
}
257+
258+
#[derive(FromArgs)]
259+
struct B2aQpArgs {
260+
#[pyarg(any)]
261+
data: ArgAsciiBuffer,
262+
#[pyarg(named, default = "false")]
263+
quotetabs: bool,
264+
#[pyarg(named, default = "true")]
265+
istext: bool,
266+
#[pyarg(named, default = "false")]
267+
header: bool,
268+
}
269+
270+
#[pyfunction]
271+
fn b2a_qp(args: B2aQpArgs) -> PyResult<Vec<u8>> {
272+
let s = args.data;
273+
let quotetabs = args.quotetabs;
274+
let istext = args.istext;
275+
let header = args.header;
276+
s.with_ref(|buf| {
277+
let buflen = buf.len();
278+
let mut linelen = 0;
279+
let mut odatalen = 0;
280+
let mut crlf = false;
281+
let mut ch;
282+
283+
let mut inidx;
284+
let mut outidx;
285+
286+
inidx = 0;
287+
while inidx < buflen {
288+
if buf[inidx] == b'\n' {
289+
break;
290+
}
291+
inidx += 1;
292+
}
293+
if buflen > 0 && inidx < buflen && buf[inidx - 1] == b'\r' {
294+
crlf = true;
295+
}
296+
297+
inidx = 0;
298+
while inidx < buflen {
299+
let mut delta = 0;
300+
if (buf[inidx] > 126)
301+
|| (buf[inidx] == b'=')
302+
|| (header && buf[inidx] == b'_')
303+
|| (buf[inidx] == b'.'
304+
&& linelen == 0
305+
&& (inidx + 1 == buflen
306+
|| buf[inidx + 1] == b'\n'
307+
|| buf[inidx + 1] == b'\r'
308+
|| buf[inidx + 1] == 0))
309+
|| (!istext && ((buf[inidx] == b'\r') || (buf[inidx] == b'\n')))
310+
|| ((buf[inidx] == b'\t' || buf[inidx] == b' ') && (inidx + 1 == buflen))
311+
|| ((buf[inidx] < 33)
312+
&& (buf[inidx] != b'\r')
313+
&& (buf[inidx] != b'\n')
314+
&& (quotetabs || ((buf[inidx] != b'\t') && (buf[inidx] != b' '))))
315+
{
316+
if (linelen + 3) >= MAXLINESIZE {
317+
linelen = 0;
318+
delta += if crlf { 3 } else { 2 };
319+
}
320+
linelen += 3;
321+
delta += 3;
322+
inidx += 1;
323+
} else if istext
324+
&& ((buf[inidx] == b'\n')
325+
|| ((inidx + 1 < buflen)
326+
&& (buf[inidx] == b'\r')
327+
&& (buf[inidx + 1] == b'\n')))
328+
{
329+
linelen = 0;
330+
// Protect against whitespace on end of line
331+
if (inidx != 0) && ((buf[inidx - 1] == b' ') || (buf[inidx - 1] == b'\t')) {
332+
delta += 2;
333+
}
334+
delta += if crlf { 2 } else { 1 };
335+
inidx += if buf[inidx] == b'\r' { 2 } else { 1 };
336+
} else {
337+
if (inidx + 1 != buflen)
338+
&& (buf[inidx + 1] != b'\n')
339+
&& (linelen + 1) >= MAXLINESIZE
340+
{
341+
linelen = 0;
342+
delta += if crlf { 3 } else { 2 };
343+
}
344+
linelen += 1;
345+
delta += 1;
346+
inidx += 1;
347+
}
348+
odatalen += delta;
349+
}
350+
351+
let mut out_data = Vec::with_capacity(odatalen);
352+
inidx = 0;
353+
outidx = 0;
354+
linelen = 0;
355+
356+
while inidx < buflen {
357+
if (buf[inidx] > 126)
358+
|| (buf[inidx] == b'=')
359+
|| (header && buf[inidx] == b'_')
360+
|| ((buf[inidx] == b'.')
361+
&& (linelen == 0)
362+
&& (inidx + 1 == buflen
363+
|| buf[inidx + 1] == b'\n'
364+
|| buf[inidx + 1] == b'\r'
365+
|| buf[inidx + 1] == 0))
366+
|| (!istext && ((buf[inidx] == b'\r') || (buf[inidx] == b'\n')))
367+
|| ((buf[inidx] == b'\t' || buf[inidx] == b' ') && (inidx + 1 == buflen))
368+
|| ((buf[inidx] < 33)
369+
&& (buf[inidx] != b'\r')
370+
&& (buf[inidx] != b'\n')
371+
&& (quotetabs || ((buf[inidx] != b'\t') && (buf[inidx] != b' '))))
372+
{
373+
if (linelen + 3) >= MAXLINESIZE {
374+
// MAXLINESIZE = 76
375+
out_data.push(b'=');
376+
outidx += 1;
377+
if crlf {
378+
out_data.push(b'\r');
379+
outidx += 1;
380+
}
381+
out_data.push(b'\n');
382+
outidx += 1;
383+
linelen = 0;
384+
}
385+
out_data.push(b'=');
386+
outidx += 1;
387+
388+
ch = hex_nibble(buf[inidx] >> 4);
389+
if (b'a'..=b'f').contains(&ch) {
390+
ch -= b' ';
391+
}
392+
out_data.push(ch);
393+
ch = hex_nibble(buf[inidx] & 0xf);
394+
if (b'a'..=b'f').contains(&ch) {
395+
ch -= b' ';
396+
}
397+
out_data.push(ch);
398+
399+
outidx += 2;
400+
inidx += 1;
401+
linelen += 3;
402+
} else if istext
403+
&& ((buf[inidx] == b'\n')
404+
|| ((inidx + 1 < buflen)
405+
&& (buf[inidx] == b'\r')
406+
&& (buf[inidx + 1] == b'\n')))
407+
{
408+
linelen = 0;
409+
if (outidx != 0)
410+
&& ((out_data[outidx - 1] == b' ') || (out_data[outidx - 1] == b'\t'))
411+
{
412+
ch = hex_nibble(out_data[outidx - 1] >> 4);
413+
if (b'a'..=b'f').contains(&ch) {
414+
ch -= b' ';
415+
}
416+
out_data.push(ch);
417+
ch = hex_nibble(out_data[outidx - 1] & 0xf);
418+
if (b'a'..=b'f').contains(&ch) {
419+
ch -= b' ';
420+
}
421+
out_data.push(ch);
422+
out_data[outidx - 1] = b'=';
423+
outidx += 2;
424+
}
425+
426+
if crlf {
427+
out_data.push(b'\r');
428+
outidx += 1;
429+
}
430+
out_data.push(b'\n');
431+
outidx += 1;
432+
inidx += if buf[inidx] == b'\r' { 2 } else { 1 };
433+
} else {
434+
if (inidx + 1 != buflen) && (buf[inidx + 1] != b'\n') && (linelen + 1) >= 76 {
435+
// MAXLINESIZE = 76
436+
out_data.push(b'=');
437+
outidx += 1;
438+
if crlf {
439+
out_data.push(b'\r');
440+
outidx += 1;
441+
}
442+
out_data.push(b'\n');
443+
outidx += 1;
444+
linelen = 0;
445+
}
446+
linelen += 1;
447+
if header && buf[inidx] == b' ' {
448+
out_data.push(b'_');
449+
outidx += 1;
450+
inidx += 1;
451+
} else {
452+
out_data.push(buf[inidx]);
453+
outidx += 1;
454+
inidx += 1;
455+
}
456+
}
457+
}
458+
Ok(out_data)
459+
})
186460
}
187461

188462
#[pyfunction]
@@ -259,7 +533,7 @@ mod decl {
259533
let length = if b.is_empty() {
260534
((-0x20i32) & 0x3fi32) as usize
261535
} else {
262-
((b[0] - 0x20) & 0x3f) as usize
536+
((b[0] - b' ') & 0x3f) as usize
263537
};
264538

265539
// Allocate the buffer
@@ -316,7 +590,7 @@ mod decl {
316590
if backtick && num != 0 {
317591
0x60
318592
} else {
319-
0x20 + num
593+
b' ' + num
320594
}
321595
}
322596

0 commit comments

Comments
 (0)