Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7dd2dd0

Browse files
committed
i18n security fix. Details will be posted shortly to the Django mailing lists and the official weblog.
git-svn-id: http://code.djangoproject.com/svn/django/branches/0.96-bugfixes@6607 bcc190cf-cafb-0310-a4f2-bffc1f526a37
1 parent 6c1c7c9 commit 7dd2dd0

5 files changed

Lines changed: 92 additions & 55 deletions

File tree

django/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
VERSION = (0, 96, None)
1+
VERSION = (0, 96.1, None)

django/conf/global_settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@
237237

238238
# The User-Agent string to use when checking for URL validity through the
239239
# isExistingURL validator.
240-
URL_VALIDATOR_USER_AGENT = "Django/0.96pre (http://www.djangoproject.com)"
240+
URL_VALIDATOR_USER_AGENT = "Django/0.96.1 (http://www.djangoproject.com)"
241241

242242
##############
243243
# MIDDLEWARE #

django/utils/translation/trans_real.py

Lines changed: 71 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
"Translation helper functions"
22

3-
import os, re, sys
3+
import locale
4+
import os
5+
import re
6+
import sys
47
import gettext as gettext_module
58
from cStringIO import StringIO
69
from django.utils.functional import lazy
@@ -25,15 +28,25 @@ def currentThread():
2528
# The default translation is based on the settings file.
2629
_default = None
2730

28-
# This is a cache for accept-header to translation object mappings to prevent
29-
# the accept parser to run multiple times for one user.
31+
# This is a cache for normalised accept-header languages to prevent multiple
32+
# file lookups when checking the same locale on repeated requests.
3033
_accepted = {}
3134

32-
def to_locale(language):
35+
# Format of Accept-Language header values. From RFC 2616, section 14.4 and 3.9.
36+
accept_language_re = re.compile(r'''
37+
([A-Za-z]{1,8}(?:-[A-Za-z]{1,8})*|\*) # "en", "en-au", "x-y-z", "*"
38+
(?:;q=(0(?:\.\d{,3})?|1(?:.0{,3})?))? # Optional "q=1.00", "q=0.8"
39+
(?:\s*,\s*|$) # Multiple accepts per header.
40+
''', re.VERBOSE)
41+
42+
def to_locale(language, to_lower=False):
3343
"Turns a language name (en-us) into a locale name (en_US)."
3444
p = language.find('-')
3545
if p >= 0:
36-
return language[:p].lower()+'_'+language[p+1:].upper()
46+
if to_lower:
47+
return language[:p].lower()+'_'+language[p+1:].lower()
48+
else:
49+
return language[:p].lower()+'_'+language[p+1:].upper()
3750
else:
3851
return language.lower()
3952

@@ -309,46 +322,40 @@ def get_language_from_request(request):
309322
if lang_code in supported and lang_code is not None and check_for_language(lang_code):
310323
return lang_code
311324

312-
lang_code = request.COOKIES.get('django_language', None)
313-
if lang_code in supported and lang_code is not None and check_for_language(lang_code):
325+
lang_code = request.COOKIES.get('django_language')
326+
if lang_code and lang_code in supported and check_for_language(lang_code):
314327
return lang_code
315328

316-
accept = request.META.get('HTTP_ACCEPT_LANGUAGE', None)
317-
if accept is not None:
318-
319-
t = _accepted.get(accept, None)
320-
if t is not None:
321-
return t
322-
323-
def _parsed(el):
324-
p = el.find(';q=')
325-
if p >= 0:
326-
lang = el[:p].strip()
327-
order = int(float(el[p+3:].strip())*100)
328-
else:
329-
lang = el
330-
order = 100
331-
p = lang.find('-')
332-
if p >= 0:
333-
mainlang = lang[:p]
334-
else:
335-
mainlang = lang
336-
return (lang, mainlang, order)
337-
338-
langs = [_parsed(el) for el in accept.split(',')]
339-
langs.sort(lambda a,b: -1*cmp(a[2], b[2]))
340-
341-
for lang, mainlang, order in langs:
342-
if lang in supported or mainlang in supported:
343-
langfile = gettext_module.find('django', globalpath, [to_locale(lang)])
344-
if langfile:
345-
# reconstruct the actual language from the language
346-
# filename, because otherwise we might incorrectly
347-
# report de_DE if we only have de available, but
348-
# did find de_DE because of language normalization
349-
lang = langfile[len(globalpath):].split(os.path.sep)[1]
350-
_accepted[accept] = lang
351-
return lang
329+
accept = request.META.get('HTTP_ACCEPT_LANGUAGE', '')
330+
for lang, unused in parse_accept_lang_header(accept):
331+
if lang == '*':
332+
break
333+
334+
# We have a very restricted form for our language files (no encoding
335+
# specifier, since they all must be UTF-8 and only one possible
336+
# language each time. So we avoid the overhead of gettext.find() and
337+
# look up the MO file manually.
338+
339+
normalized = locale.locale_alias.get(to_locale(lang, True))
340+
if not normalized:
341+
continue
342+
343+
# Remove the default encoding from locale_alias
344+
normalized = normalized.split('.')[0]
345+
346+
if normalized in _accepted:
347+
# We've seen this locale before and have an MO file for it, so no
348+
# need to check again.
349+
return _accepted[normalized]
350+
351+
for lang in (normalized, normalized.split('_')[0]):
352+
if lang not in supported:
353+
continue
354+
langfile = os.path.join(globalpath, lang, 'LC_MESSAGES',
355+
'django.mo')
356+
if os.path.exists(langfile):
357+
_accepted[normalized] = lang
358+
return lang
352359

353360
return settings.LANGUAGE_CODE
354361

@@ -494,3 +501,24 @@ def string_concat(*strings):
494501
return ''.join([str(el) for el in strings])
495502

496503
string_concat = lazy(string_concat, str)
504+
505+
def parse_accept_lang_header(lang_string):
506+
"""
507+
Parses the lang_string, which is the body of an HTTP Accept-Language
508+
header, and returns a list of (lang, q-value), ordered by 'q' values.
509+
510+
Any format errors in lang_string results in an empty list being returned.
511+
"""
512+
result = []
513+
pieces = accept_language_re.split(lang_string)
514+
if pieces[-1]:
515+
return []
516+
for i in range(0, len(pieces) - 1, 3):
517+
first, lang, priority = pieces[i : i + 3]
518+
if first:
519+
return []
520+
priority = priority and float(priority) or 1.0
521+
result.append((lang, priority))
522+
result.sort(lambda x, y: -cmp(x[1], y[1]))
523+
return result
524+

docs/release_notes_0.96.txt

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
=================================
2-
Django version 0.96 release notes
3-
=================================
1+
===================================
2+
Django version 0.96.1 release notes
3+
===================================
44

5-
Welcome to Django 0.96!
5+
Welcome to Django 0.96.1!
66

77
The primary goal for 0.96 is a cleanup and stabilization of the features
88
introduced in 0.95. There have been a few small `backwards-incompatible
9-
changes`_ since 0.95, but the upgrade process should be fairly simple
9+
changes since 0.95`_, but the upgrade process should be fairly simple
1010
and should not require major changes to existing applications.
1111

1212
However, we're also releasing 0.96 now because we have a set of
@@ -17,9 +17,21 @@ next official release; then you'll be able to upgrade in one step
1717
instead of needing to make incremental changes to keep up with the
1818
development version of Django.
1919

20-
Backwards-incompatible changes
20+
Changes since the 0.96 release
2121
==============================
2222

23+
This release contains fixes for a security vulnerability discovered after the
24+
initial release of Django 0.96. A bug in the i18n framework could allow an
25+
attacker to send extremely large strings in the Accept-Language header and
26+
cause a denial of service by filling available memory.
27+
28+
Because this problems wasn't discovered and fixed until after the 0.96
29+
release, it's recommended that you use this release rather than the original
30+
0.96.
31+
32+
Backwards-incompatible changes since 0.95
33+
=========================================
34+
2335
The following changes may require you to update your code when you switch from
2436
0.95 to 0.96:
2537

setup.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,9 @@
3232
for file_info in data_files:
3333
file_info[0] = '/PURELIB/%s' % file_info[0]
3434

35-
# Dynamically calculate the version based on django.VERSION.
36-
version = "%d.%d-%s" % (__import__('django').VERSION)
37-
3835
setup(
3936
name = "Django",
40-
version = version,
37+
version = "0.96.1",
4138
url = 'http://www.djangoproject.com/',
4239
author = 'Lawrence Journal-World',
4340
author_email = '[email protected]',

0 commit comments

Comments
 (0)