Django

Code

Changeset 6606

Show
Ignore:
Timestamp:
10/26/07 14:52:16 (1 year 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/branches/0.95-bugfixes/django/__init__.py

    r4362 r6606  
    1 VERSION = (0, 95.1, None) 
     1VERSION = (0, 95.2, None) 
  • django/branches/0.95-bugfixes/django/utils/translation/trans_real.py

    r3271 r6606  
    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 
     
    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. 
     36accept_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 
     42def 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() 
     
    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 
     
    495502 
    496503string_concat = lazy(string_concat, str) 
     504 
     505def 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 
  • django/branches/0.95-bugfixes/docs/release_notes_0.95.txt

    r4384 r6606  
    11=================================== 
    2 Django version 0.95.1 release notes 
     2Django version 0.95.2 release notes 
    33=================================== 
    44 
    5  
    6 Welcome to the Django 0.95.1 release. 
     5Welcome to the Django 0.95.2 release. 
    76 
    87This represents a significant advance in Django development since the 0.91 
     
    108107      package Django uses to launch its FastCGI server, which prevents 
    109108      tracebacks from bubbling up during production use. 
     109       
     110    * A security fix to the i18n framework which could allow an  
     111      attacker to send extremely large strings in the Accept-Language  
     112      header and cause a denial of service by filling available memory. 
    110113 
    111114Because these problems weren't discovered and fixed until after the 
  • django/branches/0.95-bugfixes/setup.py

    r4386 r6606  
    66setup( 
    77    name = "Django", 
    8     version = "0.95.1", 
     8    version = "0.95.2", 
    99    url = 'http://www.djangoproject.com/', 
    1010    author = 'Lawrence Journal-World',