Skip to content

Commit 7ba5ec2

Browse files
committed
encoding
1 parent 7a65b53 commit 7ba5ec2

1 file changed

Lines changed: 39 additions & 44 deletions

File tree

src/pybase64/_pybase64.c

Lines changed: 39 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -598,13 +598,16 @@ static int decode_slow(const uint8_t *src, size_t srclen, uint8_t* out, size_t*
598598

599599
static PyObject* pybase64_encode_impl_core(PyObject* self, Py_buffer const* buffer, char const* alphabet, Py_ssize_t wrapcol, unsigned int flags)
600600
{
601+
size_t groups;
602+
size_t groups_remainder;
601603
size_t out_len;
602604
PyObject* out_object;
603605
#if PY_VERSION_HEX >= 0x030f0000
604606
PyBytesWriter* writer;
605607
#endif
606608
char* dst_start;
607609
char* dst;
610+
struct base64_state b64_state;
608611

609612
if (wrapcol < 0) {
610613
PyErr_SetString(PyExc_ValueError, "wrapcol must be >= 0");
@@ -619,7 +622,20 @@ static PyObject* pybase64_encode_impl_core(PyObject* self, Py_buffer const* buff
619622
return PyErr_NoMemory(); /* GCOVR_EXCL_LINE */
620623
}
621624

622-
out_len = (size_t)(((buffer->len + 2) / 3) * 4);
625+
groups = (size_t)(buffer->len / 3);
626+
groups_remainder = (size_t)buffer->len - groups * 3U;
627+
out_len = groups * 4;
628+
switch (groups_remainder)
629+
{
630+
case 1:
631+
out_len += (flags & PYBASE64_FLAGS_NO_PADDING) ? 2U : 4U;
632+
break;
633+
case 2:
634+
out_len += (flags & PYBASE64_FLAGS_NO_PADDING) ? 3U : 4U;
635+
break;
636+
default:
637+
break;
638+
}
623639
if (wrapcol > 0 && out_len > 0) {
624640
size_t newlines = (out_len - 1U) / (size_t)wrapcol;
625641
if (newlines > ((size_t)PY_SSIZE_T_MAX - out_len)) { /* GCOVR_EXCL_BR_WITHOUT_HIT: 1/2 */
@@ -674,6 +690,10 @@ static PyObject* pybase64_encode_impl_core(PyObject* self, Py_buffer const* buff
674690
/* not interacting with Python objects from here, release the GIL */
675691
Py_BEGIN_ALLOW_THREADS
676692

693+
int const b64_flags = ((flags & PYBASE64_FLAGS_NO_PADDING) ? BASE64_NO_PADDING : 0);
694+
695+
base64_stream_encode_init(&b64_state, b64_flags);
696+
677697
if (flags & PYBASE64_FLAGS_APPEND_NEW_LINE) {
678698
out_len--; /* only consider len without new line terminator */
679699
}
@@ -683,13 +703,14 @@ static PyObject* pybase64_encode_impl_core(PyObject* self, Py_buffer const* buff
683703
const Py_ssize_t src_slice = (Py_ssize_t)((dst_slice / 4U) * 3U);
684704
Py_ssize_t len = buffer->len;
685705
const char* src = (const char*)buffer->buf;
686-
size_t remainder;
687706

688707
if (alphabet) {
708+
size_t remainder;
709+
689710
while (out_len > dst_slice) {
690711
size_t dst_len = (size_t)wrapcol;
691712

692-
base64_encode(src, src_slice, dst, &dst_len, 0);
713+
base64_stream_encode(&b64_state, src, src_slice, dst, &dst_len);
693714
translate_inplace(dst, dst_len, alphabet);
694715
dst[dst_len] = '\n';
695716

@@ -698,25 +719,28 @@ static PyObject* pybase64_encode_impl_core(PyObject* self, Py_buffer const* buff
698719
out_len -= dst_slice;
699720
dst += dst_slice;
700721
}
722+
base64_stream_encode(&b64_state, src, len, dst, &out_len);
701723
remainder = out_len;
702-
base64_encode(src, len, dst, &remainder, 0);
724+
base64_stream_encode_final(&b64_state, dst + out_len, &out_len);
725+
remainder += out_len;
703726
translate_inplace(dst, remainder, alphabet);
704727
dst += remainder;
705728
}
706729
else {
707730
while (out_len > dst_slice) {
708731
size_t dst_len = (size_t)wrapcol;
709-
base64_encode(src, src_slice, dst, &dst_len, 0);
732+
base64_stream_encode(&b64_state, src, src_slice, dst, &dst_len);
710733
dst[dst_len] = '\n';
711734

712735
len -= src_slice;
713736
src += src_slice;
714737
out_len -= dst_slice;
715738
dst += dst_slice;
716739
}
717-
remainder = out_len;
718-
base64_encode(src, len, dst, &remainder, 0);
719-
dst += remainder;
740+
base64_stream_encode(&b64_state, src, len, dst, &out_len);
741+
dst += out_len;
742+
base64_stream_encode_final(&b64_state, dst, &out_len);
743+
dst += out_len;
720744
}
721745
}
722746
else if (alphabet) {
@@ -730,32 +754,26 @@ static PyObject* pybase64_encode_impl_core(PyObject* self, Py_buffer const* buff
730754
while (out_len > dst_slice) {
731755
size_t dst_len = dst_slice;
732756

733-
base64_encode(src, src_slice, dst, &dst_len, 0);
757+
base64_stream_encode(&b64_state, src, src_slice, dst, &dst_len);
734758
translate_inplace(dst, dst_slice, alphabet);
735759

736760
len -= src_slice;
737761
src += src_slice;
738762
out_len -= dst_slice;
739763
dst += dst_slice;
740764
}
765+
base64_stream_encode(&b64_state, src, len, dst, &out_len);
741766
remainder = out_len;
742-
base64_encode(src, len, dst, &out_len, 0);
767+
base64_stream_encode_final(&b64_state, dst + out_len, &out_len);
768+
remainder += out_len;
743769
translate_inplace(dst, remainder, alphabet);
744770
dst += remainder;
745771
}
746772
else {
747-
base64_encode(buffer->buf, buffer->len, dst, &out_len, 0);
773+
base64_stream_encode(&b64_state, buffer->buf, buffer->len, dst, &out_len);
774+
dst += out_len;
775+
base64_stream_encode_final(&b64_state, dst, &out_len);
748776
dst += out_len;
749-
}
750-
if (flags & PYBASE64_FLAGS_NO_PADDING) {
751-
/* we have at least 4 bytes, at most 2 '=' */
752-
assert((dst - dst_start) >= 4);
753-
if (dst[-1] == '=') {
754-
dst -= 1;
755-
}
756-
if (dst[-1] == '=') {
757-
dst -= 1;
758-
}
759777
}
760778
if (flags & PYBASE64_FLAGS_APPEND_NEW_LINE) {
761779
*dst++ = '\n';
@@ -764,29 +782,6 @@ static PyObject* pybase64_encode_impl_core(PyObject* self, Py_buffer const* buff
764782
/* restore the GIL */
765783
Py_END_ALLOW_THREADS
766784

767-
if (flags & PYBASE64_FLAGS_NO_PADDING)
768-
{
769-
if (flags & PYBASE64_FLAGS_ENCODE_AS_STRING) {
770-
#if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)
771-
/* SystemError: PyUnicode_Resize called on already created string... */
772-
/* we'll be less efficient */
773-
PyObject* temp_object = PyUnicode_FromKindAndData(PyUnicode_1BYTE_KIND, dst_start, dst - dst_start);
774-
Py_DECREF(out_object);
775-
out_object = temp_object;
776-
#else
777-
if (PyUnicode_Resize(&out_object, dst - dst_start) != 0) { /* GCOVR_EXCL_BR_WITHOUT_HIT: 1/2 */
778-
Py_DECREF(out_object); /* GCOVR_EXCL_LINE */
779-
return NULL; /* GCOVR_EXCL_LINE */
780-
}
781-
#endif
782-
}
783-
else {
784-
#if PY_VERSION_HEX < 0x030f0000
785-
_PyBytes_Resize(&out_object, dst - dst_start);
786-
#endif
787-
}
788-
}
789-
790785
#if PY_VERSION_HEX >= 0x030f0000
791786
if (!(flags & PYBASE64_FLAGS_ENCODE_AS_STRING)) {
792787
out_object = PyBytesWriter_FinishWithPointer(writer, dst);

0 commit comments

Comments
 (0)