~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/config.py

  • Committer: Alexander Belchenko
  • Date: 2007-01-30 23:05:35 UTC
  • mto: This revision was merged to the branch mainline in revision 2259.
  • Revision ID: bialix@ukr.net-20070130230535-kx1rd478rtigyc3v
standalone installer: win98 support

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2007 Canonical Ltd
 
1
# Copyright (C) 2005 Canonical Ltd
2
2
#   Authors: Robert Collins <robert.collins@canonical.com>
3
 
#            and others
4
3
#
5
4
# This program is free software; you can redistribute it and/or modify
6
5
# it under the terms of the GNU General Public License as published by
70
69
import errno
71
70
from fnmatch import fnmatch
72
71
import re
73
 
from cStringIO import StringIO
 
72
from StringIO import StringIO
74
73
 
75
74
import bzrlib
76
75
from bzrlib import (
77
 
    debug,
78
76
    errors,
79
 
    mail_client,
80
77
    osutils,
81
78
    symbol_versioning,
82
 
    trace,
83
 
    ui,
84
79
    urlutils,
85
80
    win32utils,
86
81
    )
87
 
from bzrlib.util.configobj import configobj
 
82
import bzrlib.util.configobj.configobj as configobj
88
83
""")
89
84
 
 
85
from bzrlib.trace import mutter, warning
 
86
 
90
87
 
91
88
CHECK_IF_POSSIBLE=0
92
89
CHECK_ALWAYS=1
144
141
        """Get the users pop up editor."""
145
142
        raise NotImplementedError
146
143
 
147
 
    def get_mail_client(self):
148
 
        """Get a mail client to use"""
149
 
        selected_client = self.get_user_option('mail_client')
150
 
        try:
151
 
            mail_client_class = {
152
 
                None: mail_client.DefaultMail,
153
 
                # Specific clients
154
 
                'evolution': mail_client.Evolution,
155
 
                'kmail': mail_client.KMail,
156
 
                'mutt': mail_client.Mutt,
157
 
                'thunderbird': mail_client.Thunderbird,
158
 
                # Generic options
159
 
                'default': mail_client.DefaultMail,
160
 
                'editor': mail_client.Editor,
161
 
                'mapi': mail_client.MAPIClient,
162
 
                'xdg-email': mail_client.XDGEmail,
163
 
            }[selected_client]
164
 
        except KeyError:
165
 
            raise errors.UnknownMailClient(selected_client)
166
 
        return mail_client_class(self)
167
 
 
168
144
    def _get_signature_checking(self):
169
145
        """Template method to override signature checking policy."""
170
146
 
236
212
        v = os.environ.get('BZR_EMAIL')
237
213
        if v:
238
214
            return v.decode(bzrlib.user_encoding)
239
 
 
 
215
        v = os.environ.get('BZREMAIL')
 
216
        if v:
 
217
            warning('BZREMAIL is deprecated in favor of BZR_EMAIL. Please update your configuration.')
 
218
            return v.decode(bzrlib.user_encoding)
 
219
    
240
220
        v = self._get_user_id()
241
221
        if v:
242
222
            return v
243
 
 
 
223
        
244
224
        v = os.environ.get('EMAIL')
245
225
        if v:
246
226
            return v.decode(bzrlib.user_encoding)
271
251
        if policy is None:
272
252
            policy = self._get_signature_checking()
273
253
            if policy is not None:
274
 
                trace.warning("Please use create_signatures,"
275
 
                              " not check_signatures to set signing policy.")
 
254
                warning("Please use create_signatures, not check_signatures "
 
255
                        "to set signing policy.")
276
256
            if policy == CHECK_ALWAYS:
277
257
                return True
278
258
        elif policy == SIGN_ALWAYS:
291
271
    def _get_nickname(self):
292
272
        return None
293
273
 
294
 
    def get_bzr_remote_path(self):
295
 
        try:
296
 
            return os.environ['BZR_REMOTE_PATH']
297
 
        except KeyError:
298
 
            path = self.get_user_option("bzr_remote_path")
299
 
            if path is None:
300
 
                path = 'bzr'
301
 
            return path
302
 
 
303
274
 
304
275
class IniBasedConfig(Config):
305
276
    """A configuration policy that draws from ini files."""
456
427
 
457
428
    def __init__(self, location):
458
429
        name_generator = locations_config_filename
459
 
        if (not os.path.exists(name_generator()) and
 
430
        if (not os.path.exists(name_generator()) and 
460
431
                os.path.exists(branches_config_filename())):
461
432
            if sys.platform == 'win32':
462
 
                trace.warning('Please rename %s to %s'
463
 
                              % (branches_config_filename(),
464
 
                                 locations_config_filename()))
 
433
                warning('Please rename %s to %s' 
 
434
                         % (branches_config_filename(),
 
435
                            locations_config_filename()))
465
436
            else:
466
 
                trace.warning('Please rename ~/.bazaar/branches.conf'
467
 
                              ' to ~/.bazaar/locations.conf')
 
437
                warning('Please rename ~/.bazaar/branches.conf'
 
438
                        ' to ~/.bazaar/locations.conf')
468
439
            name_generator = branches_config_filename
469
440
        super(LocationConfig, self).__init__(name_generator)
470
441
        # local file locations are looked up by local path, rather than
667
638
                return value
668
639
        return None
669
640
 
670
 
    def set_user_option(self, name, value, store=STORE_BRANCH,
671
 
        warn_masked=False):
 
641
    def set_user_option(self, name, value, store=STORE_BRANCH):
672
642
        if store == STORE_BRANCH:
673
643
            self._get_branch_data_config().set_option(value, name)
674
644
        elif store == STORE_GLOBAL:
675
645
            self._get_global_config().set_user_option(name, value)
676
646
        else:
677
647
            self._get_location_config().set_user_option(name, value, store)
678
 
        if not warn_masked:
679
 
            return
680
 
        if store in (STORE_GLOBAL, STORE_BRANCH):
681
 
            mask_value = self._get_location_config().get_user_option(name)
682
 
            if mask_value is not None:
683
 
                trace.warning('Value "%s" is masked by "%s" from'
684
 
                              ' locations.conf', value, mask_value)
685
 
            else:
686
 
                if store == STORE_GLOBAL:
687
 
                    branch_config = self._get_branch_data_config()
688
 
                    mask_value = branch_config.get_user_option(name)
689
 
                    if mask_value is not None:
690
 
                        trace.warning('Value "%s" is masked by "%s" from'
691
 
                                      ' branch.conf', value, mask_value)
692
 
 
693
648
 
694
649
    def _gpg_signing_command(self):
695
650
        """See Config.gpg_signing_command."""
739
694
        if sys.platform == 'win32':
740
695
            parent_dir = os.path.dirname(path)
741
696
            if not os.path.isdir(parent_dir):
742
 
                trace.mutter('creating config parent directory: %r', parent_dir)
 
697
                mutter('creating config parent directory: %r', parent_dir)
743
698
            os.mkdir(parent_dir)
744
 
        trace.mutter('creating config directory: %r', path)
 
699
        mutter('creating config directory: %r', path)
745
700
        os.mkdir(path)
746
701
 
747
702
 
759
714
        if base is None:
760
715
            base = os.environ.get('HOME', None)
761
716
        if base is None:
762
 
            raise errors.BzrError('You must have one of BZR_HOME, APPDATA,'
763
 
                                  ' or HOME set')
 
717
            raise errors.BzrError('You must have one of BZR_HOME, APPDATA, or HOME set')
764
718
        return osutils.pathjoin(base, 'bazaar', '2.0')
765
719
    else:
766
720
        # cygwin, linux, and darwin all have a $HOME directory
784
738
    return osutils.pathjoin(config_dir(), 'locations.conf')
785
739
 
786
740
 
787
 
def authentication_config_filename():
788
 
    """Return per-user authentication ini file filename."""
789
 
    return osutils.pathjoin(config_dir(), 'authentication.conf')
790
 
 
791
 
 
792
741
def user_ignore_config_filename():
793
742
    """Return the user default ignore filename"""
794
743
    return osutils.pathjoin(config_dir(), 'ignore')
862
811
    return realname, (username + '@' + socket.gethostname())
863
812
 
864
813
 
865
 
def parse_username(username):
866
 
    """Parse e-mail username and return a (name, address) tuple."""
867
 
    match = re.match(r'(.*?)\s*<?([\w+.-]+@[\w+.-]+)>?', username)
868
 
    if match is None:
869
 
        return (username, '')
870
 
    else:
871
 
        return (match.group(1), match.group(2))
872
 
 
873
 
 
874
814
def extract_email_address(e):
875
815
    """Return just the address part of an email string.
876
 
 
 
816
    
877
817
    That is just the user@domain part, nothing else. 
878
818
    This part is required to contain only ascii characters.
879
819
    If it can't be extracted, raises an error.
880
 
 
 
820
    
881
821
    >>> extract_email_address('Jane Tester <jane@test.com>')
882
822
    "jane@test.com"
883
823
    """
884
 
    name, email = parse_username(e)
885
 
    if not email:
 
824
    m = re.search(r'[\w+.-]+@[\w+.-]+', e)
 
825
    if not m:
886
826
        raise errors.NoEmailInUsername(e)
887
 
    return email
 
827
    return m.group(0)
888
828
 
889
829
 
890
830
class TreeConfig(IniBasedConfig):
891
831
    """Branch configuration data associated with its contents, not location"""
892
 
 
893
832
    def __init__(self, branch):
894
833
        self.branch = branch
895
834
 
900
839
 
901
840
    def _get_config(self):
902
841
        try:
903
 
            obj = ConfigObj(self.branch.control_files.get('branch.conf'),
 
842
            obj = ConfigObj(self.branch.control_files.get('branch.conf'), 
904
843
                            encoding='utf-8')
905
844
        except errors.NoSuchFile:
906
845
            obj = ConfigObj(encoding='utf=8')
912
851
            obj = self._get_config()
913
852
            try:
914
853
                if section is not None:
915
 
                    obj = obj[section]
 
854
                    obj[section]
916
855
                result = obj[name]
917
856
            except KeyError:
918
857
                result = default
940
879
            self.branch.control_files.put('branch.conf', out_file)
941
880
        finally:
942
881
            self.branch.unlock()
943
 
 
944
 
 
945
 
class AuthenticationConfig(object):
946
 
    """The authentication configuration file based on a ini file.
947
 
 
948
 
    Implements the authentication.conf file described in
949
 
    doc/developers/authentication-ring.txt.
950
 
    """
951
 
 
952
 
    def __init__(self, _file=None):
953
 
        self._config = None # The ConfigObj
954
 
        if _file is None:
955
 
            self._filename = authentication_config_filename()
956
 
            self._input = self._filename = authentication_config_filename()
957
 
        else:
958
 
            # Tests can provide a string as _file
959
 
            self._filename = None
960
 
            self._input = _file
961
 
 
962
 
    def _get_config(self):
963
 
        if self._config is not None:
964
 
            return self._config
965
 
        try:
966
 
            # FIXME: Should we validate something here ? Includes: empty
967
 
            # sections are useless, at least one of
968
 
            # user/password/password_encoding should be defined, etc.
969
 
 
970
 
            # Note: the encoding below declares that the file itself is utf-8
971
 
            # encoded, but the values in the ConfigObj are always Unicode.
972
 
            self._config = ConfigObj(self._input, encoding='utf-8')
973
 
        except configobj.ConfigObjError, e:
974
 
            raise errors.ParseConfigError(e.errors, e.config.filename)
975
 
        return self._config
976
 
 
977
 
    def _save(self):
978
 
        """Save the config file, only tests should use it for now."""
979
 
        conf_dir = os.path.dirname(self._filename)
980
 
        ensure_config_dir_exists(conf_dir)
981
 
        self._get_config().write(file(self._filename, 'wb'))
982
 
 
983
 
    def _set_option(self, section_name, option_name, value):
984
 
        """Set an authentication configuration option"""
985
 
        conf = self._get_config()
986
 
        section = conf.get(section_name)
987
 
        if section is None:
988
 
            conf[section] = {}
989
 
            section = conf[section]
990
 
        section[option_name] = value
991
 
        self._save()
992
 
 
993
 
    def get_credentials(self, scheme, host, port=None, user=None, path=None):
994
 
        """Returns the matching credentials from authentication.conf file.
995
 
 
996
 
        :param scheme: protocol
997
 
 
998
 
        :param host: the server address
999
 
 
1000
 
        :param port: the associated port (optional)
1001
 
 
1002
 
        :param user: login (optional)
1003
 
 
1004
 
        :param path: the absolute path on the server (optional)
1005
 
 
1006
 
        :return: A dict containing the matching credentials or None.
1007
 
           This includes:
1008
 
           - name: the section name of the credentials in the
1009
 
             authentication.conf file,
1010
 
           - user: can't de different from the provided user if any,
1011
 
           - password: the decoded password, could be None if the credential
1012
 
             defines only the user
1013
 
           - verify_certificates: https specific, True if the server
1014
 
             certificate should be verified, False otherwise.
1015
 
        """
1016
 
        credentials = None
1017
 
        for auth_def_name, auth_def in self._get_config().items():
1018
 
            a_scheme, a_host, a_user, a_path = map(
1019
 
                auth_def.get, ['scheme', 'host', 'user', 'path'])
1020
 
 
1021
 
            try:
1022
 
                a_port = auth_def.as_int('port')
1023
 
            except KeyError:
1024
 
                a_port = None
1025
 
            except ValueError:
1026
 
                raise ValueError("'port' not numeric in %s" % auth_def_name)
1027
 
            try:
1028
 
                a_verify_certificates = auth_def.as_bool('verify_certificates')
1029
 
            except KeyError:
1030
 
                a_verify_certificates = True
1031
 
            except ValueError:
1032
 
                raise ValueError(
1033
 
                    "'verify_certificates' not boolean in %s" % auth_def_name)
1034
 
 
1035
 
            # Attempt matching
1036
 
            if a_scheme is not None and scheme != a_scheme:
1037
 
                continue
1038
 
            if a_host is not None:
1039
 
                if not (host == a_host
1040
 
                        or (a_host.startswith('.') and host.endswith(a_host))):
1041
 
                    continue
1042
 
            if a_port is not None and port != a_port:
1043
 
                continue
1044
 
            if (a_path is not None and path is not None
1045
 
                and not path.startswith(a_path)):
1046
 
                continue
1047
 
            if (a_user is not None and user is not None
1048
 
                and a_user != user):
1049
 
                # Never contradict the caller about the user to be used
1050
 
                continue
1051
 
            if a_user is None:
1052
 
                # Can't find a user
1053
 
                continue
1054
 
            credentials = dict(name=auth_def_name,
1055
 
                               user=a_user, password=auth_def['password'],
1056
 
                               verify_certificates=a_verify_certificates)
1057
 
            self.decode_password(credentials,
1058
 
                                 auth_def.get('password_encoding', None))
1059
 
            if 'auth' in debug.debug_flags:
1060
 
                trace.mutter("Using authentication section: %r", auth_def_name)
1061
 
            break
1062
 
 
1063
 
        return credentials
1064
 
 
1065
 
    def get_user(self, scheme, host, port=None,
1066
 
                 realm=None, path=None, prompt=None):
1067
 
        """Get a user from authentication file.
1068
 
 
1069
 
        :param scheme: protocol
1070
 
 
1071
 
        :param host: the server address
1072
 
 
1073
 
        :param port: the associated port (optional)
1074
 
 
1075
 
        :param realm: the realm sent by the server (optional)
1076
 
 
1077
 
        :param path: the absolute path on the server (optional)
1078
 
 
1079
 
        :return: The found user.
1080
 
        """
1081
 
        credentials = self.get_credentials(scheme, host, port, user=None,
1082
 
                                           path=path)
1083
 
        if credentials is not None:
1084
 
            user = credentials['user']
1085
 
        else:
1086
 
            user = None
1087
 
        return user
1088
 
 
1089
 
    def get_password(self, scheme, host, user, port=None,
1090
 
                     realm=None, path=None, prompt=None):
1091
 
        """Get a password from authentication file or prompt the user for one.
1092
 
 
1093
 
        :param scheme: protocol
1094
 
 
1095
 
        :param host: the server address
1096
 
 
1097
 
        :param port: the associated port (optional)
1098
 
 
1099
 
        :param user: login
1100
 
 
1101
 
        :param realm: the realm sent by the server (optional)
1102
 
 
1103
 
        :param path: the absolute path on the server (optional)
1104
 
 
1105
 
        :return: The found password or the one entered by the user.
1106
 
        """
1107
 
        credentials = self.get_credentials(scheme, host, port, user, path)
1108
 
        if credentials is not None:
1109
 
            password = credentials['password']
1110
 
        else:
1111
 
            password = None
1112
 
        # Prompt user only if we could't find a password
1113
 
        if password is None:
1114
 
            if prompt is None:
1115
 
                # Create a default prompt suitable for most of the cases
1116
 
                prompt = '%s' % scheme.upper() + ' %(user)s@%(host)s password'
1117
 
            # Special handling for optional fields in the prompt
1118
 
            if port is not None:
1119
 
                prompt_host = '%s:%d' % (host, port)
1120
 
            else:
1121
 
                prompt_host = host
1122
 
            password = ui.ui_factory.get_password(prompt,
1123
 
                                                  host=prompt_host, user=user)
1124
 
        return password
1125
 
 
1126
 
    def decode_password(self, credentials, encoding):
1127
 
        return credentials