Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Continuous Integration

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
lint:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install flake8 black mypy
- name: Lint with flake8
run: |
flake8 *.py
- name: Lint with black
run: |
black --check --diff *.py
- name: Lint with mypy
run: |
mypy --strict *.py

test:
needs: lint
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.x', '3.6', 'pypy-3.6']

steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Test with unittest
run: |
python -m unittest --verbose
15 changes: 0 additions & 15 deletions .travis.yml

This file was deleted.

4 changes: 0 additions & 4 deletions MANIFEST.in

This file was deleted.

16 changes: 4 additions & 12 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
Unpadded Base64
===============

.. image:: https://img.shields.io/pypi/v/unpaddedbase64.svg
:target: https://pypi.python.org/pypi/unpaddedbase64/
:alt: Latest Version

.. image:: https://img.shields.io/travis/matrix-org/python-unpaddedbase64.svg
:target: https://travis-ci.org/matrix-org/python-unpaddedbase64

Encode and decode Base64 without "=" padding.

`RFC 4648`_ specifies that Base64 should be padded to a multiple of 4 bytes
using "=" characters. However this conveys no benefit so many protocols choose
to use Base64 without the "=" padding.
using "=" characters. However many protocols choose to omit the "=" padding.

.. _`RFC 4648`: https://tools.ietf.org/html/rfc4648

Expand All @@ -21,13 +13,13 @@ Installing

.. code:: bash

pip install unpaddedbase64
python3 -m pip install unpaddedbase64

Using
-----

.. code:: python

import unpaddedbase64
assert (unpaddedbase64.encode_base64(b'\x00')) == u'AA'
assert (unpaddedbase64.decode_base64(u'AA')) == b'\x00'
assert (unpaddedbase64.encode_base64(b'\x00')) == 'AA'
assert (unpaddedbase64.decode_base64('AA')) == b'\x00'
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["setuptools>=43", "wheel"]
build-backend = "setuptools.build_meta"
23 changes: 23 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[metadata]
name = unpaddedbase64
version = 2.0.0rc1
description = Unpadded Base64
long_description = file: README.rst
keywords = base64
url = https://github.com/matrix-org/python-unpaddedbase64
project_urls =
Issue Tracker = https://github.com/matrix-org/python-unpaddedbase64/issues
classifiers =
License :: OSI Approved :: Apache Software License
Programming Language :: Python :: 3

[options]
py_modules = unpaddedbase64
python_requires = >=3.6

[mypy]
python_version = 3.6

[flake8]
max-line-length = 88
extend-ignore = E203, W503
49 changes: 0 additions & 49 deletions setup.py

This file was deleted.

36 changes: 18 additions & 18 deletions test_unpaddedbase64.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Copyright 2015 OpenMarket Ltd
# Copyright 2021 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -17,25 +18,24 @@


class TestUnpaddedBase64(unittest.TestCase):
def test_encode(self) -> None:
self.assertEqual(encode_base64(b""), "")
self.assertEqual(encode_base64(b"\x00"), "AA")
self.assertEqual(encode_base64(b"\x00\x00"), "AAA")
self.assertEqual(encode_base64(b"\x00\x00\x00"), "AAAA")

def test_encode(self):
self.assertEqual(encode_base64(b''), u'')
self.assertEqual(encode_base64(b'\x00'), u'AA')
self.assertEqual(encode_base64(b'\x00\x00'), u'AAA')
self.assertEqual(encode_base64(b'\x00\x00\x00'), u'AAAA')

def test_decode(self):
self.assertEqual(decode_base64(u''), b'')
self.assertEqual(decode_base64(u'AA'), b'\x00')
self.assertEqual(decode_base64(u'AAA'), b'\x00\x00')
self.assertEqual(decode_base64(u'AAAA'), b'\x00\x00\x00')
def test_decode(self) -> None:
self.assertEqual(decode_base64(""), b"")
self.assertEqual(decode_base64("AA"), b"\x00")
self.assertEqual(decode_base64("AAA"), b"\x00\x00")
self.assertEqual(decode_base64("AAAA"), b"\x00\x00\x00")
with self.assertRaises(Exception):
decode_base64(u'A')
decode_base64("A")

def test_encode_urlunsafe_chars(self):
self.assertEqual(encode_base64(b'\xff\xe6\x9a'), u'/+aa')
self.assertEqual(encode_base64(b'\xff\xe6\x9a', True), u'_-aa')
def test_encode_urlunsafe_chars(self) -> None:
self.assertEqual(encode_base64(b"\xff\xe6\x9a"), "/+aa")
self.assertEqual(encode_base64(b"\xff\xe6\x9a", True), "_-aa")

def test_decode_urlunsafe_chars(self):
self.assertEqual(decode_base64(u'/+aa'), b'\xff\xe6\x9a')
self.assertEqual(decode_base64(u'_-aa'), b'\xff\xe6\x9a')
def test_decode_urlunsafe_chars(self) -> None:
self.assertEqual(decode_base64("/+aa"), b"\xff\xe6\x9a")
self.assertEqual(decode_base64("_-aa"), b"\xff\xe6\x9a")
27 changes: 0 additions & 27 deletions tox.ini

This file was deleted.

25 changes: 13 additions & 12 deletions unpaddedbase64.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Copyright 2014, 2015 OpenMarket Ltd
# Copyright 2021 The Matrix.org Foundation C.I.C.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -14,27 +15,27 @@

import base64

__version__ = "1.1.0"

def encode_base64(input_bytes: bytes, urlsafe: bool = False) -> str:
"""Encode bytes as an unpadded base64 string."""

def encode_base64(input_bytes, urlsafe=False):
"""Encode bytes as a base64 string without any padding."""
if urlsafe:
encode = base64.urlsafe_b64encode
else:
encode = base64.b64encode

encode = base64.urlsafe_b64encode if urlsafe else base64.b64encode
output_bytes = encode(input_bytes)
output_string = output_bytes.decode("ascii")
return output_string.rstrip(u"=")
return output_string.rstrip("=")


def decode_base64(input_string):
"""Decode a base64 string to bytes inferring padding from the length of the
string."""
def decode_base64(input_string: str) -> bytes:
"""Decode an unpadded standard or urlsafe base64 string to bytes."""

input_bytes = input_string.encode("ascii")
input_len = len(input_bytes)
padding = b"=" * (3 - ((input_len + 3) % 4))
decode = base64.b64decode
if u'-' in input_string or u'_' in input_string:
decode = base64.urlsafe_b64decode
output_bytes = decode(input_bytes + padding)

# Passing altchars here allows decoding both standard and urlsafe base64
output_bytes = base64.b64decode(input_bytes + padding, altchars=b"-_")
return output_bytes