Django

Code

Changeset 6607

Show
Ignore:
Timestamp:
10/26/07 14:52:29 (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.96-bugfixes/django/conf/global_settings.py

    r4766 r6607  
    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)" 
     240URL_VALIDATOR_USER_AGENT = "Django/0.96.1 (http://www.djangoproject.com)" 
    241241 
    242242############## 
  • django/branches/0.96-bugfixes/django/__init__.py

    r4809 r6607  
    1 VERSION = (0, 96, None) 
     1VERSION = (0, 96.1, None) 
  • django/branches/0.96-bugfixes/django/utils/translation/trans_real.py

    r4486 r6607  
    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.96-bugfixes/docs/release_notes_0.96.txt

    r4803 r6607  
    1 ================================= 
    2 Django version 0.96 release notes 
    3 ================================= 
    4  
    5 Welcome to Django 0.96
     1=================================== 
     2Django version 0.96.1 release notes 
     3=================================== 
     4 
     5Welcome to Django 0.96.1
    66 
    77The primary goal for 0.96 is a cleanup and stabilization of the features 
    88introduced in 0.95. There have been a few small `backwards-incompatible 
    9 changes`_ since 0.95, but the upgrade process should be fairly simple 
     9changes since 0.95`_, but the upgrade process should be fairly simple 
    1010and should not require major changes to existing applications. 
    1111 
     
    1818development version of Django. 
    1919 
    20 Backwards-incompatible changes 
     20Changes since the 0.96 release 
    2121============================== 
     22 
     23This release contains fixes for a security vulnerability discovered after the 
     24initial release of Django 0.96. A bug in the i18n framework could allow an 
     25attacker to send extremely large strings in the Accept-Language header and 
     26cause a denial of service by filling available memory. 
     27 
     28Because this problems wasn't discovered and fixed until after the 0.96 
     29release, it's recommended that you use this release rather than the original 
     300.96. 
     31 
     32Backwards-incompatible changes since 0.95 
     33========================================= 
    2234 
    2335The following changes may require you to update your code when you switch from 
  • django/branches/0.96-bugfixes/setup.py

    r4550 r6607  
    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  
    3835setup( 
    3936    name = "Django", 
    40     version = version
     37    version = "0.96.1"
    4138    url = 'http://www.djangoproject.com/', 
    4239    author = 'Lawrence Journal-World',