티스토리 수익 글 보기

티스토리 수익 글 보기

i18n security fix. Details will be posted shortly to the Django maili… · django/django@8bc36e7 · GitHub
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 = 'holovaty@gmail.com',

0 commit comments

Comments
 (0)