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

Skip to content

Commit 8bc36e7

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.91-bugfixes@6605 bcc190cf-cafb-0310-a4f2-bffc1f526a37
1 parent 613cfab commit 8bc36e7

File tree

2 files changed

+71
-44
lines changed

2 files changed

+71
-44
lines changed

django/utils/translation.py

Lines changed: 70 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

@@ -297,46 +310,40 @@ def get_language_from_request(request):
297310
if lang_code in supported and lang_code is not None and check_for_language(lang_code):
298311
return lang_code
299312

300-
lang_code = request.COOKIES.get('django_language', None)
301-
if lang_code in supported and lang_code is not None and check_for_language(lang_code):
313+
lang_code = request.COOKIES.get('django_language')
314+
if lang_code and lang_code in supported and check_for_language(lang_code):
302315
return lang_code
303316

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

341348
return settings.LANGUAGE_CODE
342349

@@ -457,3 +464,23 @@ def templatize(src):
457464
else:
458465
out.write(blankout(t.contents, 'X'))
459466
return out.getvalue()
467+
468+
def parse_accept_lang_header(lang_string):
469+
"""
470+
Parses the lang_string, which is the body of an HTTP Accept-Language
471+
header, and returns a list of (lang, q-value), ordered by 'q' values.
472+
473+
Any format errors in lang_string results in an empty list being returned.
474+
"""
475+
result = []
476+
pieces = accept_language_re.split(lang_string)
477+
if pieces[-1]:
478+
return []
479+
for i in range(0, len(pieces) - 1, 3):
480+
first, lang, priority = pieces[i : i + 3]
481+
if first:
482+
return []
483+
priority = priority and float(priority) or 1.0
484+
result.append((lang, priority))
485+
result.sort(lambda x, y: -cmp(x[1], y[1]))
486+
return result

setup.py

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

66
setup(
77
name = "Django",
8-
version = "0.91",
8+
version = "0.91.1",
99
url = 'http://www.djangoproject.com/',
1010
author = 'Lawrence Journal-World',
1111
author_email = '[email protected]',

0 commit comments

Comments
 (0)