Django

Code

Changeset 6608

Show
Ignore:
Timestamp:
10/26/07 14:52:42 (10 months ago)
Author:
jacob
Message:

i18n security fix. Details will be posted shortly to the Django mailing lists and the official weblog.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/django/utils/translation/trans_real.py

    r6582 r6608  
    11"Translation helper functions" 
    22 
    3 import os, re, sys 
     3import locale 
     4import os 
     5import re 
     6import sys 
    47import gettext as gettext_module 
    58from cStringIO import StringIO 
     9 
    610from django.utils.encoding import force_unicode 
    711 
     
    2630_default = None 
    2731 
    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
     32# This is a cache for normalised accept-header languages to prevent multiple 
     33# file lookups when checking the same locale on repeated requests
    3034_accepted = {} 
    3135 
    32 def to_locale(language): 
     36# Format of Accept-Language header values. From RFC 2616, section 14.4 and 3.9. 
     37accept_language_re = re.compile(r''' 
     38        ([A-Za-z]{1,8}(?:-[A-Za-z]{1,8})*|\*)   # "en", "en-au", "x-y-z", "*" 
     39        (?:;q=(0(?:\.\d{,3})?|1(?:.0{,3})?))?   # Optional "q=1.00", "q=0.8" 
     40        (?:\s*,\s*|$)                            # Multiple accepts per header. 
     41        ''', re.VERBOSE) 
     42 
     43def to_locale(language, to_lower=False): 
    3344    "Turns a language name (en-us) into a locale name (en_US)." 
    3445    p = language.find('-') 
    3546    if p >= 0: 
    36         return language[:p].lower()+'_'+language[p+1:].upper() 
     47        if to_lower: 
     48            return language[:p].lower()+'_'+language[p+1:].lower() 
     49        else: 
     50            return language[:p].lower()+'_'+language[p+1:].upper() 
    3751    else: 
    3852        return language.lower() 
     
    335349            return lang_code 
    336350 
    337     lang_code = request.COOKIES.get('django_language', None
    338     if lang_code in supported and lang_code is not None and check_for_language(lang_code): 
     351    lang_code = request.COOKIES.get('django_language'
     352    if lang_code and lang_code in supported and check_for_language(lang_code): 
    339353        return lang_code 
    340354 
    341     accept = request.META.get('HTTP_ACCEPT_LANGUAGE', None) 
    342     if accept is not None: 
    343  
    344         t = _accepted.get(accept, None) 
    345         if t is not None: 
    346             return t 
    347  
    348         def _parsed(el): 
    349             p = el.find(';q=') 
    350             if p >= 0: 
    351                 lang = el[:p].strip() 
    352                 order = int(float(el[p+3:].strip())*100) 
    353             else: 
    354                 lang = el 
    355                 order = 100 
    356             p = lang.find('-') 
    357             if p >= 0: 
    358                 mainlang = lang[:p] 
    359             else: 
    360                 mainlang = lang 
    361             return (lang, mainlang, order) 
    362  
    363         langs = [_parsed(el) for el in accept.split(',')] 
    364         langs.sort(lambda a,b: -1*cmp(a[2], b[2])) 
    365  
    366         for lang, mainlang, order in langs: 
    367             if lang in supported or mainlang in supported: 
    368                 langfile = gettext_module.find('django', globalpath, [to_locale(lang)]) 
    369                 if langfile: 
    370                     # reconstruct the actual language from the language 
    371                     # filename, because otherwise we might incorrectly 
    372                     # report de_DE if we only have de available, but 
    373                     # did find de_DE because of language normalization 
    374                     lang = langfile[len(globalpath):].split(os.path.sep)[1] 
    375                     _accepted[accept] = lang 
    376                     return lang 
     355    accept = request.META.get('HTTP_ACCEPT_LANGUAGE', '') 
     356    for lang, unused in parse_accept_lang_header(accept): 
     357        if lang == '*': 
     358            break 
     359 
     360        # We have a very restricted form for our language files (no encoding 
     361        # specifier, since they all must be UTF-8 and only one possible 
     362        # language each time. So we avoid the overhead of gettext.find() and 
     363        # look up the MO file manually. 
     364 
     365        normalized = locale.locale_alias.get(to_locale(lang, True)) 
     366        if not normalized: 
     367            continue 
     368 
     369        # Remove the default encoding from locale_alias 
     370        normalized = normalized.split('.')[0] 
     371 
     372        if normalized in _accepted: 
     373            # We've seen this locale before and have an MO file for it, so no 
     374            # need to check again. 
     375            return _accepted[normalized] 
     376 
     377        for lang in (normalized, normalized.split('_')[0]): 
     378            if lang not in supported: 
     379                continue 
     380            langfile = os.path.join(globalpath, lang, 'LC_MESSAGES', 
     381                    'django.mo') 
     382            if os.path.exists(langfile): 
     383                _accepted[normalized] = lang 
     384            return lang 
    377385 
    378386    return settings.LANGUAGE_CODE 
     
    506514    return out.getvalue() 
    507515 
     516def parse_accept_lang_header(lang_string): 
     517    """ 
     518    Parses the lang_string, which is the body of an HTTP Accept-Language 
     519    header, and returns a list of (lang, q-value), ordered by 'q' values. 
     520 
     521    Any format errors in lang_string results in an empty list being returned. 
     522    """ 
     523    result = [] 
     524    pieces = accept_language_re.split(lang_string) 
     525    if pieces[-1]: 
     526        return [] 
     527    for i in range(0, len(pieces) - 1, 3): 
     528        first, lang, priority = pieces[i : i + 3] 
     529        if first: 
     530            return [] 
     531        priority = priority and float(priority) or 1.0 
     532        result.append((lang, priority)) 
     533    result.sort(lambda x, y: -cmp(x[1], y[1])) 
     534    return result 
     535 
  • django/trunk/tests/regressiontests/i18n/tests.py

    r6446 r6608  
    11# coding: utf-8 
     2import misc 
    23 
    3 ur""" 
     4regressions = ur""" 
    45Format string interpolation should work with *_lazy objects. 
    56 
     
    4041u'django' 
    4142""" 
     43 
     44__test__ = { 
     45    'regressions': regressions, 
     46    'misc': misc.tests, 
     47}