Don't use os.system() in compilemessages.

Fixes #19584.

This implies stop storing file path command line arguments in envvars as
a security measure to start relying on with Popen's shell=False instead,
and addition of an 'utils' module.

Thanks kmichel_wgs for the report.
This commit is contained in:
Ramiro Morales 2013-02-12 13:58:49 -03:00
parent 5c51d71f9a
commit dfa9324966
5 changed files with 67 additions and 14 deletions

View File

@ -2,9 +2,10 @@ from __future__ import unicode_literals
import codecs import codecs
import os import os
import sys
from optparse import make_option from optparse import make_option
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django.core.management.utils import popen_wrapper
from django.utils._os import npath from django.utils._os import npath
def has_bom(fn): def has_bom(fn):
@ -41,18 +42,15 @@ def compile_messages(stderr, locale=None):
if has_bom(fn): if has_bom(fn):
raise CommandError("The %s file has a BOM (Byte Order Mark). Django only supports .po files encoded in UTF-8 and without any BOM." % fn) raise CommandError("The %s file has a BOM (Byte Order Mark). Django only supports .po files encoded in UTF-8 and without any BOM." % fn)
pf = os.path.splitext(fn)[0] pf = os.path.splitext(fn)[0]
# Store the names of the .mo and .po files in an environment program = 'msgfmt'
# variable, rather than doing a string replacement into the args = [program, '--check-format', '-o', npath(pf + '.mo'), npath(pf + '.po')]
# command, so that we can take advantage of shell quoting, to output, errors, status = popen_wrapper(args)
# quote any malicious characters/escaping. if status:
# See http://cyberelk.net/tim/articles/cmdline/ar01s02.html if errors:
os.environ['djangocompilemo'] = npath(pf + '.mo') msg = "Execution of %s failed: %s" % (program, errors)
os.environ['djangocompilepo'] = npath(pf + '.po') else:
if sys.platform == 'win32': # Different shell-variable syntax msg = "Execution of %s failed" % program
cmd = 'msgfmt --check-format -o "%djangocompilemo%" "%djangocompilepo%"' raise CommandError(msg)
else:
cmd = 'msgfmt --check-format -o "$djangocompilemo" "$djangocompilepo"'
os.system(cmd)
class Command(BaseCommand): class Command(BaseCommand):

View File

@ -0,0 +1,14 @@
import os
from subprocess import PIPE, Popen
def popen_wrapper(args):
"""
Friendly wrapper around Popen.
Returns stdout output, stderr output and OS status code.
"""
p = Popen(args, shell=False, stdout=PIPE, stderr=PIPE,
close_fds=os.name != 'nt', universal_newlines=True)
output, errors = p.communicate()
return output, errors, p.returncode

View File

@ -99,3 +99,22 @@ class MultipleLocaleCompilationTests(MessageCompilationTests):
self.assertTrue(os.path.exists(self.MO_FILE_HR)) self.assertTrue(os.path.exists(self.MO_FILE_HR))
self.assertTrue(os.path.exists(self.MO_FILE_FR)) self.assertTrue(os.path.exists(self.MO_FILE_FR))
class CompilationErrorHandling(MessageCompilationTests):
LOCALE='ja'
MO_FILE='locale/%s/LC_MESSAGES/django.mo' % LOCALE
def setUp(self):
super(CompilationErrorHandling, self).setUp()
self.addCleanup(self._rmfile, os.path.join(test_dir, self.MO_FILE))
def _rmfile(self, filepath):
if os.path.exists(filepath):
os.remove(filepath)
def test_error_reported_by_msgfmt(self):
os.chdir(test_dir)
with self.assertRaises(CommandError):
call_command('compilemessages', locale=self.LOCALE, stderr=StringIO())

View File

@ -0,0 +1,21 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2011-12-04 04:59-0600\n"
"PO-Revision-Date: 2013-02-26 21:29-0300\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: ja\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#, brainfuck-format
msgwhat!? "This is an invalid PO file. GNU msgfmt should reject it."

View File

@ -41,7 +41,8 @@ if can_run_extraction_tests:
MultipleLocaleExtractionTests) MultipleLocaleExtractionTests)
if can_run_compilation_tests: if can_run_compilation_tests:
from .commands.compilation import (PoFileTests, PoFileContentsTests, from .commands.compilation import (PoFileTests, PoFileContentsTests,
PercentRenderingTests, MultipleLocaleCompilationTests) PercentRenderingTests, MultipleLocaleCompilationTests,
CompilationErrorHandling)
from .contenttypes.tests import ContentTypeTests from .contenttypes.tests import ContentTypeTests
from .forms import I18nForm, SelectDateForm, SelectDateWidget, CompanyForm from .forms import I18nForm, SelectDateForm, SelectDateWidget, CompanyForm
from .models import Company, TestModel from .models import Company, TestModel