Skip to content

Commit

Permalink
Updated for Python 3 compatibility.
Browse files Browse the repository at this point in the history
  • Loading branch information
tomatohater committed Nov 22, 2016
1 parent 8bd067d commit 04879c9
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 14 deletions.
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -26,7 +26,7 @@ def read(fname):
include_package_data = True,
package_data = {},
install_requires = [
'pycrypto>=2.0.1',
'pycrypto>=2.0.1,six',
],
classifiers = [
'Environment :: Web Environment',
Expand Down
28 changes: 27 additions & 1 deletion tests/tests.py
Expand Up @@ -3,6 +3,8 @@

import datetime

import six

from django.http import HttpResponseNotFound
from django.template.defaultfilters import slugify
from django.test import TestCase
Expand All @@ -23,7 +25,7 @@ class UnfriendlyTests(TestCase):
urls = 'tests.urls'

def setUp(self):
self.juice = 'Lorem ipsum dolor sit amet'
self.juice = six.b('Lorem ipsum dolor sit amet')

def test_encryption(self):
"""
Expand Down Expand Up @@ -86,6 +88,18 @@ def test_obfuscate_filter(self):
})
self.assertEqual(view_url, obfuscated_url)

def test_obfuscate_long_filter(self):
"""
Test the obfuscate filter.
"""
test_url = reverse('unfriendly-test-long')
obfuscated_url = obfuscate(test_url)
view_url = reverse('unfriendly-deobfuscate', kwargs={
'key': encrypt(test_url, settings.UNFRIENDLY_SECRET,
settings.UNFRIENDLY_IV),
})
self.assertEqual(view_url, obfuscated_url)

def test_obfuscate_filter_with_juice(self):
"""
Test the obfuscate filter.
Expand Down Expand Up @@ -142,6 +156,18 @@ def test_deobfuscate_view(self):

self.assertEqual(test_response.content, obfuscated_response.content)

def test_deobfuscate_long_view(self):
"""
Test the deobfuscate view.
"""
test_url = reverse('unfriendly-test-long')
obfuscated_url = obfuscate(test_url)

test_response = self.client.get(test_url)
obfuscated_response = self.client.get(obfuscated_url)

self.assertEqual(test_response.content, obfuscated_response.content)

def test_deobfuscate_view_with_juice(self):
"""
Test the deobfuscate view with seo juice.
Expand Down
1 change: 1 addition & 0 deletions tests/urls.py
Expand Up @@ -8,5 +8,6 @@

urlpatterns = [
url(r'^test-view/$', test_view, name='unfriendly-test'),
url(r'^' + 'test-view' * 20 + '/$', test_view, name='unfriendly-test-long'),
url(r'^', include('unfriendly.urls')),
]
2 changes: 1 addition & 1 deletion unfriendly/__init__.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
"""Main django-unfriendly package."""

VERSION = (0, 5, 0)
VERSION = (0, 6, 'dev')
__version__ = '.'.join([str(x) for x in VERSION])
31 changes: 24 additions & 7 deletions unfriendly/utils.py
Expand Up @@ -2,9 +2,12 @@
"""Various encryption utilites."""

import base64
import binascii
import struct
import zlib

import six

from Crypto.Cipher import AES


Expand All @@ -23,6 +26,15 @@ def _lazysecret(secret, blocksize=32, padding='}'):
return secret + (blocksize - len(secret)) * padding
return secret

def _crc(plaintext):
"""Generates crc32. Modulo keep the value within int range."""
if not isinstance(plaintext, six.binary_type):
plaintext = six.b(plaintext)
return (zlib.crc32(plaintext) % 2147483647) & 0xffffffff

def _pack_crc(plaintext):
"""Packs plaintext crc32 as binary data."""
return struct.pack('i', _crc(plaintext))

def encrypt(plaintext, secret, inital_vector, checksum=True, lazy=True):
"""Encrypts plaintext with secret
Expand All @@ -33,15 +45,21 @@ def encrypt(plaintext, secret, inital_vector, checksum=True, lazy=True):
checksum - attach crc32 byte encoded (default: True)
returns ciphertext
"""
if not isinstance(plaintext, six.binary_type):
plaintext = six.b(plaintext)

secret = _lazysecret(secret) if lazy else secret
encobj = AES.new(secret, AES.MODE_CFB, inital_vector)

if checksum:
plaintext += base64.urlsafe_b64encode(
struct.pack("i", zlib.crc32(plaintext)))
packed = _pack_crc(plaintext)
plaintext += base64.urlsafe_b64encode(packed)

return base64.urlsafe_b64encode(encobj.encrypt(plaintext)).replace('=', '')
encoded = base64.urlsafe_b64encode(encobj.encrypt(plaintext))
if isinstance(plaintext, six.binary_type):
encoded = encoded.decode()

return encoded.replace('=', '')


def decrypt(ciphertext, secret, inital_vector, checksum=True, lazy=True):
Expand All @@ -53,23 +71,22 @@ def decrypt(ciphertext, secret, inital_vector, checksum=True, lazy=True):
checksum - verify crc32 byte encoded checksum (default: True)
returns plaintext
"""

secret = _lazysecret(secret) if lazy else secret
encobj = AES.new(secret, AES.MODE_CFB, inital_vector)
try:
plaintext = encobj.decrypt(base64.urlsafe_b64decode(
ciphertext + ('=' * (len(ciphertext) % 4))))
except TypeError:
except (TypeError, binascii.Error):
raise InvalidKeyError("invalid key")

if checksum:
try:
crc, plaintext = (base64.urlsafe_b64decode(
plaintext[-8:]), plaintext[:-8])
except TypeError:
except (TypeError, binascii.Error):
raise CheckSumError("checksum mismatch")

if not crc == struct.pack("i", zlib.crc32(plaintext)):
if not crc == _pack_crc(plaintext):
raise CheckSumError("checksum mismatch")

return plaintext
15 changes: 11 additions & 4 deletions unfriendly/views.py
@@ -1,17 +1,19 @@
# -*- coding: utf-8 -*-
"""Django view handlers."""

from urllib import unquote
from urlparse import urlparse

from django.http import HttpResponseNotFound

try:
from urllib.parse import unquote, urlparse
except ImportError:
from urllib import unquote
from urlparse import urlparse

try:
from django.urls import Resolver404, resolve
except ImportError:
from django.core.urlresolvers import Resolver404, resolve


from unfriendly import settings
from unfriendly.utils import CheckSumError, InvalidKeyError, decrypt

Expand All @@ -29,6 +31,11 @@ def deobfuscate(request, key, juice=None):
except (CheckSumError, InvalidKeyError):
return HttpResponseNotFound()

try:
url = url.decode('utf-8')
except UnicodeDecodeError:
return HttpResponseNotFound()

url_parts = urlparse(unquote(url))
path = url_parts.path
query = url_parts.query
Expand Down

0 comments on commit 04879c9

Please sign in to comment.