From c46e0cf485f959d287cd920071c4f3140ec7e10d Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Tue, 22 Apr 2014 15:55:19 -0700 Subject: [PATCH] Make UTF-8 encoding of configuration file explicit This is an important step in the i18n move. ESSIDs in profile names and the profiles can now be non-ASCII. Signed-off-by: Sean Robinson --- test/data/wifi-radar-test.conf | 1 + test/unit/config.py | 15 +++++----- wifiradar/config.py | 67 +++++++++++++++++++++++------------------- 3 files changed, 44 insertions(+), 39 deletions(-) diff --git a/test/data/wifi-radar-test.conf b/test/data/wifi-radar-test.conf index 0e4a9bd..1847e95 100644 --- a/test/data/wifi-radar-test.conf +++ b/test/data/wifi-radar-test.conf @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- [DEFAULT] auto_profile_order = [u'WinterPalace:00:09:5B:D5:03:4A'] commit_required = False diff --git a/test/unit/config.py b/test/unit/config.py index 99173ce..8b42688 100644 --- a/test/unit/config.py +++ b/test/unit/config.py @@ -307,15 +307,16 @@ class TestConfigFileManager(unittest.TestCase): [u'WinterPalace:00:09:5B:D5:03:4A']) @mock.patch('wifiradar.config.move') - @mock.patch('os.fdopen') + @mock.patch('codecs.open') @mock.patch('tempfile.mkstemp') - def test_write(self, mock_mkstemp, mock_fdopen, mock_move): + def test_write(self, mock_mkstemp, mock_open, mock_move): """ Test write method. """ + mock_temp_file = 'mock_file_tempXXXXXX' test_file = StringIO() test_file._close = test_file.close test_file.close = lambda: None - mock_mkstemp.return_value = (1, 'mock_file_tempXXXXXX') - mock_fdopen.return_value = test_file + mock_mkstemp.return_value = (1, mock_temp_file) + mock_open.return_value = test_file self.config = ConfigFileManager('mock_file', self.DEFAULT) self.config.set_section('DHCP', self.DHCP) self.config.set_section('WPA', self.WPA) @@ -328,10 +329,8 @@ class TestConfigFileManager(unittest.TestCase): self.assertEqual(test_file.getvalue(), test_data) test_file._close() mock_mkstemp.assert_called_once_with(prefix='wifi-radar.conf.') - # The first value tested with mock_fdopen is the first value in - # mock_mkstemp.return_value. - mock_fdopen.assert_called_once_with(1, 'w') - mock_move.assert_called_once_with('mock_file_tempXXXXXX', 'mock_file') + mock_open.assert_called_once_with(mock_temp_file, 'w', encoding='utf8') + mock_move.assert_called_once_with(mock_temp_file, 'mock_file') class TestConfigFunctions(unittest.TestCase): diff --git a/wifiradar/config.py b/wifiradar/config.py index e59405e..15922b4 100644 --- a/wifiradar/config.py +++ b/wifiradar/config.py @@ -33,6 +33,7 @@ from __future__ import unicode_literals +import codecs import logging import os import tempfile @@ -316,15 +317,14 @@ class ConfigFileManager(ConfigManager): def read(self): """ Read configuration file from disk into instance variables. """ - fp = open(self.filename, 'r') - self.readfp(fp) + with codecs.open(self.filename, 'r', encoding='utf8') as f: + self.read_file(f, self.filename) # convert the auto_profile_order to a list for ordering self.auto_profile_order = eval(self.get_opt('DEFAULT', 'auto_profile_order')) for ap in self.profiles(): self.set_bool_opt(ap, 'known', True) if ap in self.auto_profile_order: continue self.auto_profile_order.append(ap) - fp.close() # Remove any auto_profile_order AP without a matching section. auto_profile_order_copy = self.auto_profile_order[:] for ap in auto_profile_order_copy: @@ -340,36 +340,41 @@ class ConfigFileManager(ConfigManager): self.set_opt('DEFAULT', 'auto_profile_order', str(self.auto_profile_order)) self.set_opt('DEFAULT', 'version', WIFI_RADAR_VERSION) + # Safely create a temporary file, ... (fd, tempfilename) = tempfile.mkstemp(prefix='wifi-radar.conf.') - fp = os.fdopen(fd, 'w') - # write DEFAULT section first - if self._defaults: - fp.write('[DEFAULT]\n') - for key in sorted(self._defaults.keys()): - fp.write('{KEY} = {VALUE}\n'.format(KEY=key, - VALUE=str(self._defaults[key]).replace('\n','\n\t'))) - fp.write('\n') - # write other non-profile sections next - for section in self._sections: - if section not in self.profiles(): - fp.write('[{SECT}]\n'.format(SECT=section)) - for key in sorted(self._sections[section].keys()): - if key != '__name__': - fp.write('{KEY} = {VALUE}\n'.format(KEY=key, - VALUE=str(self._sections[section][key] - ).replace('\n', '\n\t'))) + # ....close the file descriptor to the temporary file, ... + os.close(fd) + # ....and re-open the temporary file with a codec filter. + with codecs.open(tempfilename, 'w', encoding='utf8') as fp: + # Write a byte-encoding note on the first line. + fp.write('# -*- coding: utf-8 -*-\n') + # write DEFAULT section + if self._defaults: + fp.write('[DEFAULT]\n') + for key in sorted(self._defaults.keys()): + fp.write('{KEY} = {VALUE}\n'.format(KEY=key, + VALUE=str(self._defaults[key]).replace('\n','\n\t'))) fp.write('\n') - # write profile sections - for section in self._sections: - if section in self.profiles(): - fp.write('[{SECT}]\n'.format(SECT=section)) - for key in sorted(self._sections[section].keys()): - if key != '__name__': - fp.write('{KEY} = {VALUE}\n'.format(KEY=key, - VALUE=str(self._sections[section][key] - ).replace('\n', '\n\t'))) - fp.write('\n') - fp.close() + # write other non-profile sections next + for section in self._sections: + if section not in self.profiles(): + fp.write('[{SECT}]\n'.format(SECT=section)) + for key in sorted(self._sections[section].keys()): + if key != '__name__': + fp.write('{KEY} = {VALUE}\n'.format(KEY=key, + VALUE=str(self._sections[section][key] + ).replace('\n', '\n\t'))) + fp.write('\n') + # write profile sections + for section in self._sections: + if section in self.profiles(): + fp.write('[{SECT}]\n'.format(SECT=section)) + for key in sorted(self._sections[section].keys()): + if key != '__name__': + fp.write('{KEY} = {VALUE}\n'.format(KEY=key, + VALUE=str(self._sections[section][key] + ).replace('\n', '\n\t'))) + fp.write('\n') move(tempfilename, self.filename) -- 2.11.4.GIT