whois/parser.py
changeset 81 359baebcf0e8
parent 75 81859268375c
child 83 a0a0ae15499b
child 87 0f12faf57d33
child 92 7d3efe9ad172
equal deleted inserted replaced
80:fa9650e9ec23 81:359baebcf0e8
     3 # Copyright (c) 2008 Andrey Petrov
     3 # Copyright (c) 2008 Andrey Petrov
     4 #
     4 #
     5 # This module is part of pywhois and is released under
     5 # This module is part of pywhois and is released under
     6 # the MIT license: http://www.opensource.org/licenses/mit-license.php
     6 # the MIT license: http://www.opensource.org/licenses/mit-license.php
     7 
     7 
       
     8 from __future__ import absolute_import
       
     9 from __future__ import unicode_literals
       
    10 from __future__ import print_function
       
    11 from __future__ import division
       
    12 from future import standard_library
       
    13 standard_library.install_aliases()
       
    14 from builtins import *
       
    15 from builtins import str
       
    16 from past.builtins import basestring
     8 
    17 
     9 import json
    18 import json
    10 from datetime import datetime
    19 from datetime import datetime
    11 import re
    20 import re
    12 try:
    21 try:
    13     import dateutil.parser as dp
    22     import dateutil.parser as dp
    14     from time_zones import tz_data
    23     from .time_zones import tz_data
    15     DATEUTIL = True
    24     DATEUTIL = True
    16 except ImportError:
    25 except ImportError:
    17     DATEUTIL = False
    26     DATEUTIL = False
       
    27 
       
    28 EMAIL_REGEX = "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"
    18 
    29 
    19 KNOWN_FORMATS = [
    30 KNOWN_FORMATS = [
    20     '%d-%b-%Y', 				# 02-jan-2000
    31     '%d-%b-%Y', 				# 02-jan-2000
    21     '%Y-%m-%d', 				# 2000-01-02
    32     '%Y-%m-%d', 				# 2000-01-02
    22     '%d.%m.%Y', 				# 2.1.2000
    33     '%d.%m.%Y', 				# 2.1.2000
    43 
    54 
    44 
    55 
    45 def datetime_parse(s):
    56 def datetime_parse(s):
    46     for known_format in KNOWN_FORMATS:
    57     for known_format in KNOWN_FORMATS:
    47         try:
    58         try:
    48             s = datetime.strptime(s.strip(), known_format)
    59             s = datetime.strptime(s, known_format)
    49             break
    60             break
    50         except ValueError as e:
    61         except ValueError as e:
    51             pass  # Wrong format, keep trying
    62             pass  # Wrong format, keep trying
    52     return s
    63     return s
    53 
    64 
    55     """Convert any date string found in WHOIS to a datetime object.
    66     """Convert any date string found in WHOIS to a datetime object.
    56     """
    67     """
    57     if DATEUTIL:
    68     if DATEUTIL:
    58         try:
    69         try:
    59             return dp.parse(
    70             return dp.parse(
    60                 s.strip(),
    71                 s,
    61                 tzinfos=tz_data,
    72                 tzinfos=tz_data,
    62                 dayfirst=dayfirst,
    73                 dayfirst=dayfirst,
    63                 yearfirst=yearfirst
    74                 yearfirst=yearfirst
    64             ).replace(tzinfo=None)
    75             ).replace(tzinfo=None)
    65         except Exception:
    76         except Exception:
    72     """Base class for parsing a Whois entries.
    83     """Base class for parsing a Whois entries.
    73     """
    84     """
    74     # regular expressions to extract domain data from whois profile
    85     # regular expressions to extract domain data from whois profile
    75     # child classes will override this
    86     # child classes will override this
    76     _regex = {
    87     _regex = {
    77         'domain_name':          'Domain Name:\s?(.+)',
    88         'domain_name':          'Domain Name: *(.+)',
    78         'registrar':            'Registrar:\s?(.+)',
    89         'registrar':            'Registrar: *(.+)',
    79         'whois_server':         'Whois Server:\s?(.+)',
    90         'whois_server':         'Whois Server: *(.+)',
    80         'referral_url':         'Referral URL:\s?(.+)',  # http url of whois_server
    91         'referral_url':         'Referral URL: *(.+)',  # http url of whois_server
    81         'updated_date':         'Updated Date:\s?(.+)',
    92         'updated_date':         'Updated Date: *(.+)',
    82         'creation_date':        'Creation Date:\s?(.+)',
    93         'creation_date':        'Creation Date: *(.+)',
    83         'expiration_date':      'Expir\w+ Date:\s?(.+)',
    94         'expiration_date':      'Expir\w+ Date: *(.+)',
    84         'name_servers':         'Name Server:\s?(.+)',  # list of name servers
    95         'name_servers':         'Name Server: *(.+)',  # list of name servers
    85         'status':               'Status:\s?(.+)',  # list of statuses
    96         'status':               'Status: *(.+)',  # list of statuses
    86         'emails':               '[\w.-]+@[\w.-]+\.[\w]{2,4}',  # list of email s
    97         'emails':               EMAIL_REGEX,  # list of email s
    87         'dnssec':               'dnssec:\s*([\S]+)',
    98         'dnssec':               'dnssec: *([\S]+)',
    88         'name':                 'Registrant Name:\s*(.+)',
    99         'name':                 'Registrant Name: *(.+)',
    89         'org':                  'Registrant\s*Organization:\s*(.+)',
   100         'org':                  'Registrant\s*Organization: *(.+)',
    90         'address':              'Registrant Street:\s*(.+)',
   101         'address':              'Registrant Street: *(.+)',
    91         'city':                 'Registrant City:\s*(.+)',
   102         'city':                 'Registrant City: *(.+)',
    92         'state':                'Registrant State/Province:\s*(.+)',
   103         'state':                'Registrant State/Province: *(.+)',
    93         'zipcode':              'Registrant Postal Code:\s*(.+)',
   104         'zipcode':              'Registrant Postal Code: *(.+)',
    94         'country':              'Registrant Country:\s*(.+)',
   105         'country':              'Registrant Country: *(.+)',
    95     }
   106     }
    96     dayfirst = False
   107     dayfirst = False
    97     yearfirst = False
   108     yearfirst = False
    98 
   109 
    99     def __init__(self, domain, text, regex=None):
   110     def __init__(self, domain, text, regex=None):
   108 
   119 
   109     def parse(self):
   120     def parse(self):
   110         """The first time an attribute is called it will be calculated here.
   121         """The first time an attribute is called it will be calculated here.
   111         The attribute is then set to be accessed directly by subsequent calls.
   122         The attribute is then set to be accessed directly by subsequent calls.
   112         """
   123         """
   113         for attr, regex in self._regex.items():
   124         for attr, regex in list(self._regex.items()):
   114             if regex:
   125             if regex:
   115                 values = []
   126                 values = []
   116                 for value in re.findall(regex, self.text, re.IGNORECASE):
   127                 for value in re.findall(regex, self.text, re.IGNORECASE):
   117                     if isinstance(value, basestring):
   128                     value = value.strip()
       
   129                     if value and isinstance(value, basestring):
   118                         # try casting to date format
   130                         # try casting to date format
   119                         value = cast_date(value.strip(),
   131                         value = cast_date(
   120                                           dayfirst=self.dayfirst,
   132                             value,
   121                                           yearfirst=self.yearfirst)
   133                             dayfirst=self.dayfirst,
       
   134                             yearfirst=self.yearfirst)
   122                     if value and value not in values:
   135                     if value and value not in values:
   123                         # avoid duplicates
   136                         # avoid duplicates
   124                         values.append(value)
   137                         values.append(value)
   125                 if len(values) == 1:
   138                 if len(values) == 1:
   126                     values = values[0]
   139                     values = values[0]
   235 
   248 
   236 class WhoisOrg(WhoisEntry):
   249 class WhoisOrg(WhoisEntry):
   237     """Whois parser for .org domains
   250     """Whois parser for .org domains
   238     """
   251     """
   239     regex = {
   252     regex = {
   240         'domain_name':      'Domain Name:\s?(.+)',
   253         'domain_name':      'Domain Name: *(.+)',
   241         'registrar':        'Registrar:\s?(.+)',
   254         'registrar':        'Registrar: *(.+)',
   242         'whois_server':     'Whois Server:\s?(.+)', # empty usually
   255         'whois_server':     'Whois Server: *(.+)', # empty usually
   243         'referral_url':     'Referral URL:\s?(.+)', # http url of whois_server: empty usually
   256         'referral_url':     'Referral URL: *(.+)', # http url of whois_server: empty usually
   244         'updated_date':     'Updated Date:\s?(.+)',
   257         'updated_date':     'Updated Date: *(.+)',
   245         'creation_date':    'Creation Date:\s?(.+)',
   258         'creation_date':    'Creation Date: *(.+)',
   246         'expiration_date':  'Registry Expiry Date:\s?(.+)',
   259         'expiration_date':  'Registry Expiry Date: *(.+)',
   247         'name_servers':     'Name Server:\s?(.+)', # list of name servers
   260         'name_servers':     'Name Server: *(.+)', # list of name servers
   248         'status':           'Status:\s?(.+)', # list of statuses
   261         'status':           'Status: *(.+)', # list of statuses
   249         'emails':           '[\w.-]+@[\w.-]+\.[\w]{2,4}', # list of email addresses
   262         'emails':           EMAIL_REGEX, # list of email addresses
   250     }
   263     }
   251     
   264     
   252     def __init__(self, domain, text):
   265     def __init__(self, domain, text):
   253         if text.strip() == 'NOT FOUND':
   266         if text.strip() == 'NOT FOUND':
   254             raise PywhoisError(text)
   267             raise PywhoisError(text)
   258 
   271 
   259 class WhoisRu(WhoisEntry):
   272 class WhoisRu(WhoisEntry):
   260     """Whois parser for .ru domains
   273     """Whois parser for .ru domains
   261     """
   274     """
   262     regex = {
   275     regex = {
   263         'domain_name': 'domain:\s*(.+)',
   276         'domain_name': 'domain: *(.+)',
   264         'registrar': 'registrar:\s*(.+)',
   277         'registrar': 'registrar: *(.+)',
   265         'creation_date': 'created:\s*(.+)',
   278         'creation_date': 'created: *(.+)',
   266         'expiration_date': 'paid-till:\s*(.+)',
   279         'expiration_date': 'paid-till: *(.+)',
   267         'name_servers': 'nserver:\s*(.+)',  # list of name servers
   280         'name_servers': 'nserver: *(.+)',  # list of name servers
   268         'status': 'state:\s*(.+)',  # list of statuses
   281         'status': 'state: *(.+)',  # list of statuses
   269         'emails': '[\w.-]+@[\w.-]+\.[\w]{2,4}',  # list of email addresses
   282         'emails': EMAIL_REGEX,  # list of email addresses
   270         'org': 'org:\s*(.+)'
   283         'org': 'org: *(.+)'
   271     }
   284     }
   272 
   285 
   273     def __init__(self, domain, text):
   286     def __init__(self, domain, text):
   274         if text.strip() == 'No entries found':
   287         if text.strip() == 'No entries found':
   275             raise PywhoisError(text)
   288             raise PywhoisError(text)
   307 
   320 
   308 class WhoisName(WhoisEntry):
   321 class WhoisName(WhoisEntry):
   309     """Whois parser for .name domains
   322     """Whois parser for .name domains
   310     """
   323     """
   311     regex = {
   324     regex = {
   312         'domain_name_id':  'Domain Name ID:\s*(.+)',
   325         'domain_name_id':  'Domain Name ID: *(.+)',
   313         'domain_name':     'Domain Name:\s*(.+)',
   326         'domain_name':     'Domain Name: *(.+)',
   314         'registrar_id':    'Sponsoring Registrar ID:\s*(.+)',
   327         'registrar_id':    'Sponsoring Registrar ID: *(.+)',
   315         'registrar':       'Sponsoring Registrar:\s*(.+)',
   328         'registrar':       'Sponsoring Registrar: *(.+)',
   316         'registrant_id':   'Registrant ID:\s*(.+)',
   329         'registrant_id':   'Registrant ID: *(.+)',
   317         'admin_id':        'Admin ID:\s*(.+)',
   330         'admin_id':        'Admin ID: *(.+)',
   318         'technical_id':    'Tech ID:\s*(.+)',
   331         'technical_id':    'Tech ID: *(.+)',
   319         'billing_id':      'Billing ID:\s*(.+)',
   332         'billing_id':      'Billing ID: *(.+)',
   320         'creation_date':   'Created On:\s*(.+)',
   333         'creation_date':   'Created On: *(.+)',
   321         'expiration_date': 'Expires On:\s*(.+)',
   334         'expiration_date': 'Expires On: *(.+)',
   322         'updated_date':    'Updated On:\s*(.+)',
   335         'updated_date':    'Updated On: *(.+)',
   323         'name_server_ids': 'Name Server ID:\s*(.+)',  # list of name server ids
   336         'name_server_ids': 'Name Server ID: *(.+)',  # list of name server ids
   324         'name_servers':    'Name Server:\s*(.+)',  # list of name servers
   337         'name_servers':    'Name Server: *(.+)',  # list of name servers
   325         'status':          'Domain Status:\s*(.+)',  # list of statuses
   338         'status':          'Domain Status: *(.+)',  # list of statuses
   326     }
   339     }
   327 
   340 
   328     def __init__(self, domain, text):
   341     def __init__(self, domain, text):
   329         if 'No match for ' in text:
   342         if 'No match for ' in text:
   330             raise PywhoisError(text)
   343             raise PywhoisError(text)
   334 
   347 
   335 class WhoisUs(WhoisEntry):
   348 class WhoisUs(WhoisEntry):
   336     """Whois parser for .us domains
   349     """Whois parser for .us domains
   337     """
   350     """
   338     regex = {
   351     regex = {
   339         'domain_name':                    'Domain Name:\s*(.+)',
   352         'domain_name':                    'Domain Name: *(.+)',
   340         'domain__id':                     'Domain ID:\s*(.+)',
   353         'domain__id':                     'Domain ID: *(.+)',
   341         'registrar':                      'Sponsoring Registrar:\s*(.+)',
   354         'registrar':                      'Sponsoring Registrar: *(.+)',
   342         'registrar_id':                   'Sponsoring Registrar IANA ID:\s*(.+)',
   355         'registrar_id':                   'Sponsoring Registrar IANA ID: *(.+)',
   343         'registrar_url':                  'Registrar URL \(registration services\):\s*(.+)',
   356         'registrar_url':                  'Registrar URL \(registration services\): *(.+)',
   344         'status':                         'Domain Status:\s*(.+)',  # list of statuses
   357         'status':                         'Domain Status: *(.+)',  # list of statuses
   345         'registrant_id':                  'Registrant ID:\s*(.+)',
   358         'registrant_id':                  'Registrant ID: *(.+)',
   346         'registrant_name':                'Registrant Name:\s*(.+)',
   359         'registrant_name':                'Registrant Name: *(.+)',
   347         'registrant_address1':            'Registrant Address1:\s*(.+)',
   360         'registrant_address1':            'Registrant Address1: *(.+)',
   348         'registrant_address2':            'Registrant Address2:\s*(.+)',
   361         'registrant_address2':            'Registrant Address2: *(.+)',
   349         'registrant_city':                'Registrant City:\s*(.+)',
   362         'registrant_city':                'Registrant City: *(.+)',
   350         'registrant_state_province':      'Registrant State/Province:\s*(.+)',
   363         'registrant_state_province':      'Registrant State/Province: *(.+)',
   351         'registrant_postal_code':         'Registrant Postal Code:\s*(.+)',
   364         'registrant_postal_code':         'Registrant Postal Code: *(.+)',
   352         'registrant_country':             'Registrant Country:\s*(.+)',
   365         'registrant_country':             'Registrant Country: *(.+)',
   353         'registrant_country_code':        'Registrant Country Code:\s*(.+)',
   366         'registrant_country_code':        'Registrant Country Code: *(.+)',
   354         'registrant_phone_number':        'Registrant Phone Number:\s*(.+)',
   367         'registrant_phone_number':        'Registrant Phone Number: *(.+)',
   355         'registrant_email':               'Registrant Email:\s*(.+)',
   368         'registrant_email':               'Registrant Email: *(.+)',
   356         'registrant_application_purpose': 'Registrant Application Purpose:\s*(.+)',
   369         'registrant_application_purpose': 'Registrant Application Purpose: *(.+)',
   357         'registrant_nexus_category':      'Registrant Nexus Category:\s*(.+)',
   370         'registrant_nexus_category':      'Registrant Nexus Category: *(.+)',
   358         'admin_id':                       'Administrative Contact ID:\s*(.+)',
   371         'admin_id':                       'Administrative Contact ID: *(.+)',
   359         'admin_name':                     'Administrative Contact Name:\s*(.+)',
   372         'admin_name':                     'Administrative Contact Name: *(.+)',
   360         'admin_address1':                 'Administrative Contact Address1:\s*(.+)',
   373         'admin_address1':                 'Administrative Contact Address1: *(.+)',
   361         'admin_address2':                 'Administrative Contact Address2:\s*(.+)',
   374         'admin_address2':                 'Administrative Contact Address2: *(.+)',
   362         'admin_city':                     'Administrative Contact City:\s*(.+)',
   375         'admin_city':                     'Administrative Contact City: *(.+)',
   363         'admin_state_province':           'Administrative Contact State/Province:\s*(.+)',
   376         'admin_state_province':           'Administrative Contact State/Province: *(.+)',
   364         'admin_postal_code':              'Administrative Contact Postal Code:\s*(.+)',
   377         'admin_postal_code':              'Administrative Contact Postal Code: *(.+)',
   365         'admin_country':                  'Administrative Contact Country:\s*(.+)',
   378         'admin_country':                  'Administrative Contact Country: *(.+)',
   366         'admin_country_code':             'Administrative Contact Country Code:\s*(.+)',
   379         'admin_country_code':             'Administrative Contact Country Code: *(.+)',
   367         'admin_phone_number':             'Administrative Contact Phone Number:\s*(.+)',
   380         'admin_phone_number':             'Administrative Contact Phone Number: *(.+)',
   368         'admin_email':                    'Administrative Contact Email:\s*(.+)',
   381         'admin_email':                    'Administrative Contact Email: *(.+)',
   369         'admin_application_purpose':      'Administrative Application Purpose:\s*(.+)',
   382         'admin_application_purpose':      'Administrative Application Purpose: *(.+)',
   370         'admin_nexus_category':           'Administrative Nexus Category:\s*(.+)',
   383         'admin_nexus_category':           'Administrative Nexus Category: *(.+)',
   371         'billing_id':                     'Billing Contact ID:\s*(.+)',
   384         'billing_id':                     'Billing Contact ID: *(.+)',
   372         'billing_name':                   'Billing Contact Name:\s*(.+)',
   385         'billing_name':                   'Billing Contact Name: *(.+)',
   373         'billing_address1':               'Billing Contact Address1:\s*(.+)',
   386         'billing_address1':               'Billing Contact Address1: *(.+)',
   374         'billing_address2':               'Billing Contact Address2:\s*(.+)',
   387         'billing_address2':               'Billing Contact Address2: *(.+)',
   375         'billing_city':                   'Billing Contact City:\s*(.+)',
   388         'billing_city':                   'Billing Contact City: *(.+)',
   376         'billing_state_province':         'Billing Contact State/Province:\s*(.+)',
   389         'billing_state_province':         'Billing Contact State/Province: *(.+)',
   377         'billing_postal_code':            'Billing Contact Postal Code:\s*(.+)',
   390         'billing_postal_code':            'Billing Contact Postal Code: *(.+)',
   378         'billing_country':                'Billing Contact Country:\s*(.+)',
   391         'billing_country':                'Billing Contact Country: *(.+)',
   379         'billing_country_code':           'Billing Contact Country Code:\s*(.+)',
   392         'billing_country_code':           'Billing Contact Country Code: *(.+)',
   380         'billing_phone_number':           'Billing Contact Phone Number:\s*(.+)',
   393         'billing_phone_number':           'Billing Contact Phone Number: *(.+)',
   381         'billing_email':                  'Billing Contact Email:\s*(.+)',
   394         'billing_email':                  'Billing Contact Email: *(.+)',
   382         'billing_application_purpose':    'Billing Application Purpose:\s*(.+)',
   395         'billing_application_purpose':    'Billing Application Purpose: *(.+)',
   383         'billing_nexus_category':         'Billing Nexus Category:\s*(.+)',
   396         'billing_nexus_category':         'Billing Nexus Category: *(.+)',
   384         'tech_id':                        'Technical Contact ID:\s*(.+)',
   397         'tech_id':                        'Technical Contact ID: *(.+)',
   385         'tech_name':                      'Technical Contact Name:\s*(.+)',
   398         'tech_name':                      'Technical Contact Name: *(.+)',
   386         'tech_address1':                  'Technical Contact Address1:\s*(.+)',
   399         'tech_address1':                  'Technical Contact Address1: *(.+)',
   387         'tech_address2':                  'Technical Contact Address2:\s*(.+)',
   400         'tech_address2':                  'Technical Contact Address2: *(.+)',
   388         'tech_city':                      'Technical Contact City:\s*(.+)',
   401         'tech_city':                      'Technical Contact City: *(.+)',
   389         'tech_state_province':            'Technical Contact State/Province:\s*(.+)',
   402         'tech_state_province':            'Technical Contact State/Province: *(.+)',
   390         'tech_postal_code':               'Technical Contact Postal Code:\s*(.+)',
   403         'tech_postal_code':               'Technical Contact Postal Code: *(.+)',
   391         'tech_country':                   'Technical Contact Country:\s*(.+)',
   404         'tech_country':                   'Technical Contact Country: *(.+)',
   392         'tech_country_code':              'Technical Contact Country Code:\s*(.+)',
   405         'tech_country_code':              'Technical Contact Country Code: *(.+)',
   393         'tech_phone_number':              'Technical Contact Phone Number:\s*(.+)',
   406         'tech_phone_number':              'Technical Contact Phone Number: *(.+)',
   394         'tech_email':                     'Technical Contact Email:\s*(.+)',
   407         'tech_email':                     'Technical Contact Email: *(.+)',
   395         'tech_application_purpose':       'Technical Application Purpose:\s*(.+)',
   408         'tech_application_purpose':       'Technical Application Purpose: *(.+)',
   396         'tech_nexus_category':            'Technical Nexus Category:\s*(.+)',
   409         'tech_nexus_category':            'Technical Nexus Category: *(.+)',
   397         'name_servers':                   'Name Server:\s*(.+)',  # list of name servers
   410         'name_servers':                   'Name Server: *(.+)',  # list of name servers
   398         'created_by_registrar':           'Created by Registrar:\s*(.+)',
   411         'created_by_registrar':           'Created by Registrar: *(.+)',
   399         'last_updated_by_registrar':      'Last Updated by Registrar:\s*(.+)',
   412         'last_updated_by_registrar':      'Last Updated by Registrar: *(.+)',
   400         'creation_date':                  'Domain Registration Date:\s*(.+)',
   413         'creation_date':                  'Domain Registration Date: *(.+)',
   401         'expiration_date':                'Domain Expiration Date:\s*(.+)',
   414         'expiration_date':                'Domain Expiration Date: *(.+)',
   402         'updated_date':                   'Domain Last Updated Date:\s*(.+)',
   415         'updated_date':                   'Domain Last Updated Date: *(.+)',
   403     }
   416     }
   404 
   417 
   405     def __init__(self, domain, text):
   418     def __init__(self, domain, text):
   406         if 'Not found:' in text:
   419         if 'Not found:' in text:
   407             raise PywhoisError(text)
   420             raise PywhoisError(text)
   411 
   424 
   412 class WhoisPl(WhoisEntry):
   425 class WhoisPl(WhoisEntry):
   413     """Whois parser for .pl domains
   426     """Whois parser for .pl domains
   414     """
   427     """
   415     regex = {
   428     regex = {
   416         'domain_name':                    'DOMAIN NAME:\s*(.+)\n',
   429         'domain_name':                    'DOMAIN NAME: *(.+)\n',
   417         'registrar':                      'REGISTRAR:\n\s*(.+)',
   430         'registrar':                      'REGISTRAR:\n\s*(.+)',
   418         'registrar_url':                  'URL:\s*(.+)',        # not available
   431         'registrar_url':                  'URL: *(.+)',        # not available
   419         'status':                         'Registration status:\n\s*(.+)',  # not available
   432         'status':                         'Registration status:\n\s*(.+)',  # not available
   420         'registrant_name':                'Registrant:\n\s*(.+)',   # not available
   433         'registrant_name':                'Registrant:\n\s*(.+)',   # not available
   421         'creation_date':                  'created:\s*(.+)\n',
   434         'creation_date':                  'created: *(.+)\n',
   422         'expiration_date':                'renewal date:\s*(.+)',
   435         'expiration_date':                'renewal date: *(.+)',
   423         'updated_date':                   'last modified:\s*(.+)\n',
   436         'updated_date':                   'last modified: *(.+)\n',
   424     }
   437     }
   425 
   438 
   426     def __init__(self, domain, text):
   439     def __init__(self, domain, text):
   427         if 'No information available about domain name' in text:
   440         if 'No information available about domain name' in text:
   428             raise PywhoisError(text)
   441             raise PywhoisError(text)
   432 
   445 
   433 class WhoisCa(WhoisEntry):
   446 class WhoisCa(WhoisEntry):
   434     """Whois parser for .ca domains
   447     """Whois parser for .ca domains
   435     """
   448     """
   436     regex = {
   449     regex = {
   437         'registrant_name':                'Name:\s*(.+)',
   450         'registrant_name':                'Name: *(.+)',
   438         'registrant_number':              'Number:\s*(.+)\n',
   451         'registrant_number':              'Number: *(.+)\n',
   439     }
   452     }
   440 
   453 
   441     def __init__(self, domain, text):
   454     def __init__(self, domain, text):
   442         if 'Domain status:         available' in text:
   455         if 'Domain status:         available' in text:
   443             raise PywhoisError(text)
   456             raise PywhoisError(text)
   523     """Whois parser for .uk domains
   536     """Whois parser for .uk domains
   524     """
   537     """
   525     regex = {
   538     regex = {
   526         'domain_name':                    'Domain name:\n\s*(.+)',
   539         'domain_name':                    'Domain name:\n\s*(.+)',
   527         'registrar':                      'Registrar:\n\s*(.+)',
   540         'registrar':                      'Registrar:\n\s*(.+)',
   528         'registrar_url':                  'URL:\s*(.+)',
   541         'registrar_url':                  'URL: *(.+)',
   529         'status':                         'Registration status:\n\s*(.+)',  # list of statuses
   542         'status':                         'Registration status:\n\s*(.+)',  # list of statuses
   530         'registrant_name':                'Registrant:\n\s*(.+)',
   543         'registrant_name':                'Registrant:\n\s*(.+)',
   531         'creation_date':                  'Registered on:\s*(.+)',
   544         'creation_date':                  'Registered on: *(.+)',
   532         'expiration_date':                'Expiry date:\s*(.+)',
   545         'expiration_date':                'Expiry date: *(.+)',
   533         'updated_date':                   'Last updated:\s*(.+)',
   546         'updated_date':                   'Last updated: *(.+)',
   534         'name_servers':                   'Name servers:\s*(.+)',
   547         'name_servers':                   'Name servers: *(.+)',
   535     }
   548     }
   536 
   549 
   537     def __init__(self, domain, text):
   550     def __init__(self, domain, text):
   538         if 'No match for ' in text:
   551         if 'No match for ' in text:
   539             raise PywhoisError(text)
   552             raise PywhoisError(text)
   543 
   556 
   544 class WhoisFr(WhoisEntry):
   557 class WhoisFr(WhoisEntry):
   545     """Whois parser for .fr domains
   558     """Whois parser for .fr domains
   546     """
   559     """
   547     regex = {
   560     regex = {
   548         'domain_name': 'domain:\s*(.+)',
   561         'domain_name': 'domain: *(.+)',
   549         'registrar': 'registrar:\s*(.+)',
   562         'registrar': 'registrar: *(.+)',
   550         'creation_date': 'created:\s*(.+)',
   563         'creation_date': 'created: *(.+)',
   551         'expiration_date': 'anniversary:\s*(.+)',
   564         'expiration_date': 'anniversary: *(.+)',
   552         'name_servers': 'nserver:\s*(.+)',  # list of name servers
   565         'name_servers': 'nserver: *(.+)',  # list of name servers
   553         'status': 'status:\s*(.+)',  # list of statuses
   566         'status': 'status: *(.+)',  # list of statuses
   554         'emails': '[\w.-]+@[\w.-]+\.[\w]{2,4}',  # list of email addresses
   567         'emails': EMAIL_REGEX,  # list of email addresses
   555         'updated_date': 'last-update:\s*(.+)',
   568         'updated_date': 'last-update: *(.+)',
   556     }
   569     }
   557 
   570 
   558     def __init__(self, domain, text):
   571     def __init__(self, domain, text):
   559         if 'No entries found' in text:
   572         if 'No entries found' in text:
   560             raise PywhoisError(text)
   573             raise PywhoisError(text)
   564 
   577 
   565 class WhoisFi(WhoisEntry):
   578 class WhoisFi(WhoisEntry):
   566     """Whois parser for .fi domains
   579     """Whois parser for .fi domains
   567     """
   580     """
   568     regex = {
   581     regex = {
   569         'domain_name':                    'domain:\s*([\S]+)',
   582         'domain_name':                    'domain: *([\S]+)',
   570         'name':                           'descr:\s*([\S\ ]+)',
   583         'name':                           'descr: *([\S\ ]+)',
   571         'address':                        'address:\s*([\S\ ]+)',
   584         'address':                        'address: *([\S\ ]+)',
   572         'phone':                          'phone:\s*([\S\ ]+)',
   585         'phone':                          'phone: *([\S\ ]+)',
   573         'status':                         'status:\s*([\S]+)',  # list of statuses
   586         'status':                         'status: *([\S]+)',  # list of statuses
   574         'creation_date':                  'created:\s*([\S]+)',
   587         'creation_date':                  'created: *([\S]+)',
   575         'updated_date':                   'modified:\s*([\S]+)',
   588         'updated_date':                   'modified: *([\S]+)',
   576         'expiration_date':                'expires:\s*([\S]+)',
   589         'expiration_date':                'expires: *([\S]+)',
   577         'name_servers':                   'nserver:\s*([\S]+) \[\S+\]',  # list of name servers
   590         'name_servers':                   'nserver: *([\S]+) \[\S+\]',  # list of name servers
   578         'name_server_statuses':           'nserver:\s*([\S]+) \[(\S+)\]',  # list of name servers and statuses
   591         'name_server_statuses':           'nserver: *([\S]+) \[(\S+)\]',  # list of name servers and statuses
   579         'dnssec':                         'dnssec:\s*([\S]+)',
   592         'dnssec':                         'dnssec: *([\S]+)',
   580     }
   593     }
   581 
   594 
   582     def __init__(self, domain, text):
   595     def __init__(self, domain, text):
   583         if 'Domain not ' in text:
   596         if 'Domain not ' in text:
   584             raise PywhoisError(text)
   597             raise PywhoisError(text)
   607 
   620 
   608 class WhoisAU(WhoisEntry):
   621 class WhoisAU(WhoisEntry):
   609     """Whois parser for .au domains
   622     """Whois parser for .au domains
   610     """
   623     """
   611     regex = {
   624     regex = {
   612         'domain_name':                    'Domain Name:\s*(.+)\n',
   625         'domain_name':                    'Domain Name: *(.+)\n',
   613         'last_modified':			      'Last Modified:\s*(.+)\n',
   626         'last_modified':			      'Last Modified: *(.+)\n',
   614         'registrar':                      'Registrar Name:\s*(.+)\n',
   627         'registrar':                      'Registrar Name: *(.+)\n',
   615         'status':                         'Status:\s*(.+)',
   628         'status':                         'Status: *(.+)',
   616         'registrant_name':                'Registrant:\s*(.+)',
   629         'registrant_name':                'Registrant: *(.+)',
   617         'name_servers':                   'Name Server:\s*(.+)',
   630         'name_servers':                   'Name Server: *(.+)',
   618     }
   631     }
   619 
   632 
   620     def __init__(self, domain, text):
   633     def __init__(self, domain, text):
   621         if text.strip() == 'No Data Found':
   634         if text.strip() == 'No Data Found':
   622             raise PywhoisError(text)
   635             raise PywhoisError(text)
   626 
   639 
   627 class WhoisEu(WhoisEntry):
   640 class WhoisEu(WhoisEntry):
   628     """Whois parser for .eu domains
   641     """Whois parser for .eu domains
   629     """
   642     """
   630     regex = {
   643     regex = {
   631         'domain_name': r'Domain:\s*([^\n\r]+)',
   644         'domain_name': r'Domain: *([^\n\r]+)',
   632         'tech_name': r'Technical:\s*Name:\s*([^\n\r]+)',
   645         'tech_name': r'Technical: *Name: *([^\n\r]+)',
   633         'tech_org': r'Technical:\s*Name:\s*[^\n\r]+\s*Organisation:\s*([^\n\r]+)',
   646         'tech_org': r'Technical: *Name: *[^\n\r]+\s*Organisation: *([^\n\r]+)',
   634         'tech_phone': r'Technical:\s*Name:\s*[^\n\r]+\s*Organisation:\s*[^\n\r]+\s*Language:\s*[^\n\r]+\s*Phone:\s*([^\n\r]+)',
   647         'tech_phone': r'Technical: *Name: *[^\n\r]+\s*Organisation: *[^\n\r]+\s*Language: *[^\n\r]+\s*Phone: *([^\n\r]+)',
   635         'tech_fax': r'Technical:\s*Name:\s*[^\n\r]+\s*Organisation:\s*[^\n\r]+\s*Language:\s*[^\n\r]+\s*Phone:\s*[^\n\r]+\s*Fax:\s*([^\n\r]+)',
   648         'tech_fax': r'Technical: *Name: *[^\n\r]+\s*Organisation: *[^\n\r]+\s*Language: *[^\n\r]+\s*Phone: *[^\n\r]+\s*Fax: *([^\n\r]+)',
   636         'tech_email': r'Technical:\s*Name:\s*[^\n\r]+\s*Organisation:\s*[^\n\r]+\s*Language:\s*[^\n\r]+\s*Phone:\s*[^\n\r]+\s*Fax:\s*[^\n\r]+\s*Email:\s*([^\n\r]+)',
   649         'tech_email': r'Technical: *Name: *[^\n\r]+\s*Organisation: *[^\n\r]+\s*Language: *[^\n\r]+\s*Phone: *[^\n\r]+\s*Fax: *[^\n\r]+\s*Email: *([^\n\r]+)',
   637         'registrar': r'Registrar:\s*Name:\s*([^\n\r]+)',
   650         'registrar': r'Registrar: *Name: *([^\n\r]+)',
   638         'name_servers': r'Name servers:\s*([^\n\r]+)\s*([^\n\r]*)',  # list of name servers
   651         'name_servers': r'Name servers: *([^\n\r]+)\s*([^\n\r]*)',  # list of name servers
   639     }
   652     }
   640 
   653 
   641     def __init__(self, domain, text):
   654     def __init__(self, domain, text):
   642         if text.strip() == 'Status: AVAILABLE':
   655         if text.strip() == 'Status: AVAILABLE':
   643             raise PywhoisError(text)
   656             raise PywhoisError(text)
   647 
   660 
   648 class WhoisBr(WhoisEntry):
   661 class WhoisBr(WhoisEntry):
   649     """Whois parser for .br domains
   662     """Whois parser for .br domains
   650     """
   663     """
   651     regex = {
   664     regex = {
   652         'domain':                        'domain:\s*(.+)\n',
   665         'domain':                        'domain: *(.+)\n',
   653         'owner':                         'owner:\s*([\S ]+)',
   666         'owner':                         'owner: *([\S ]+)',
   654         'ownerid':                       'ownerid:\s*(.+)',
   667         'ownerid':                       'ownerid: *(.+)',
   655         'country':                       'country:\s*(.+)',
   668         'country':                       'country: *(.+)',
   656         'owner_c':                       'owner-c:\s*(.+)',
   669         'owner_c':                       'owner-c: *(.+)',
   657         'admin_c':                       'admin-c:\s*(.+)',
   670         'admin_c':                       'admin-c: *(.+)',
   658         'tech_c':                        'tech-c:\s*(.+)',
   671         'tech_c':                        'tech-c: *(.+)',
   659         'billing_c':                     'billing-c:\s*(.+)',
   672         'billing_c':                     'billing-c: *(.+)',
   660         'nserver':                       'nserver:\s*(.+)',
   673         'nserver':                       'nserver: *(.+)',
   661         'nsstat':                        'nsstat:\s*(.+)',
   674         'nsstat':                        'nsstat: *(.+)',
   662         'nslastaa':                      'nslastaa:\s*(.+)',
   675         'nslastaa':                      'nslastaa: *(.+)',
   663         'saci':                          'saci:\s*(.+)',
   676         'saci':                          'saci: *(.+)',
   664         'created':                       'created:\s*(.+)',
   677         'created':                       'created: *(.+)',
   665         'expires':                       'expires:\s*(.+)',
   678         'expires':                       'expires: *(.+)',
   666         'changed':                       'changed:\s*(.+)',
   679         'changed':                       'changed: *(.+)',
   667         'status':                        'status:\s*(.+)',
   680         'status':                        'status: *(.+)',
   668         'nic_hdl_br':                    'nic-hdl-br:\s*(.+)',
   681         'nic_hdl_br':                    'nic-hdl-br: *(.+)',
   669         'person':                        'person:\s*([\S ]+)',
   682         'person':                        'person: *([\S ]+)',
   670         'email':                         'e-mail:\s*(.+)',
   683         'email':                         'e-mail: *(.+)',
   671     }
   684     }
   672 
   685 
   673     def __init__(self, domain, text):
   686     def __init__(self, domain, text):
   674 
   687 
   675         if 'Not found:' in text:
   688         if 'Not found:' in text:
   680 
   693 
   681 class WhoisKr(WhoisEntry):
   694 class WhoisKr(WhoisEntry):
   682     """Whois parser for .kr domains
   695     """Whois parser for .kr domains
   683     """
   696     """
   684     regex = {
   697     regex = {
   685         'domain_name': 'Domain Name\s*:\s*(.+)',
   698         'domain_name': 'Domain Name\s*: *(.+)',
   686         'registrant_org': 'Registrant\s*:\s*(.+)',
   699         'registrant_org': 'Registrant\s*: *(.+)',
   687         'registrant_address': 'Registrant Address\s*:\s*(.+)',
   700         'registrant_address': 'Registrant Address\s*: *(.+)',
   688         'registrant_zip': 'Registrant Zip Code\s*:\s*(.+)',
   701         'registrant_zip': 'Registrant Zip Code\s*: *(.+)',
   689         'admin_name': 'Administrative Contact\(AC\)\s*:\s*(.+)',
   702         'admin_name': 'Administrative Contact\(AC\)\s*: *(.+)',
   690         'admin_email': 'AC E-Mail\s*:\s*(.+)',
   703         'admin_email': 'AC E-Mail\s*: *(.+)',
   691         'admin_phone': 'AC Phone Number\s*:\s*(.+)',
   704         'admin_phone': 'AC Phone Number\s*: *(.+)',
   692         'creation_date': 'Registered Date\s*:\s*(.+)',
   705         'creation_date': 'Registered Date\s*: *(.+)',
   693         'updated_date':  'Last updated Date\s*:\s*(.+)',
   706         'updated_date':  'Last updated Date\s*: *(.+)',
   694         'expiration_date':  'Expiration Date\s*:\s*(.+)',
   707         'expiration_date':  'Expiration Date\s*: *(.+)',
   695         'registrar':  'Authorized Agency\s*:\s*(.+)',
   708         'registrar':  'Authorized Agency\s*: *(.+)',
   696         'name_servers': 'Host Name\s*:\s*(.+)',  # list of name servers
   709         'name_servers': 'Host Name\s*: *(.+)',  # list of name servers
   697     }
   710     }
   698 
   711 
   699     def __init__(self, domain, text):
   712     def __init__(self, domain, text):
   700         if text.endswith(' no match'):
   713         if text.endswith(' no match'):
   701             raise PywhoisError(text)
   714             raise PywhoisError(text)
   705 
   718 
   706 class WhoisPt(WhoisEntry):
   719 class WhoisPt(WhoisEntry):
   707     """Whois parser for .pt domains
   720     """Whois parser for .pt domains
   708     """
   721     """
   709     regex = {
   722     regex = {
   710         'domain_name': 'domain name:\s*(.+)',
   723         'domain_name': 'domain name: *(.+)',
   711         'creation_date': 'creation date \(dd\/mm\/yyyy\):\s*(.+)',
   724         'creation_date': 'creation date \(dd\/mm\/yyyy\): *(.+)',
   712         'expiration_date': 'expiration date \(dd\/mm\/yyyy\):\s*(.+)',
   725         'expiration_date': 'expiration date \(dd\/mm\/yyyy\): *(.+)',
   713         'name_servers': '\tNS\t(.+).',  # list of name servers
   726         'name_servers': '\tNS\t(.+).',  # list of name servers
   714         'status': 'status:\s*(.+)',  # list of statuses
   727         'status': 'status: *(.+)',  # list of statuses
   715         'emails': '[\w.-]+@[\w.-]+\.[\w]{2,4}',  # list of email addresses
   728         'emails': EMAIL_REGEX,  # list of email addresses
   716     }
   729     }
   717 
   730 
   718     def __init__(self, domain, text):
   731     def __init__(self, domain, text):
   719         if text.strip() == 'No entries found':
   732         if text.strip() == 'No entries found':
   720             raise PywhoisError(text)
   733             raise PywhoisError(text)
   724 
   737 
   725 class WhoisBg(WhoisEntry):
   738 class WhoisBg(WhoisEntry):
   726     """Whois parser for .bg domains
   739     """Whois parser for .bg domains
   727     """
   740     """
   728     regex = {
   741     regex = {
   729         'expiration_date': 'expires at:\s*(.+)',
   742         'expiration_date': 'expires at: *(.+)',
   730     }
   743     }
   731 
   744 
   732     dayfirst = True
   745     dayfirst = True
   733 
   746 
   734     def __init__(self, domain, text):
   747     def __init__(self, domain, text):
   740 
   753 
   741 class WhoisDe(WhoisEntry):
   754 class WhoisDe(WhoisEntry):
   742     """Whois parser for .de domains
   755     """Whois parser for .de domains
   743     """
   756     """
   744     regex = {
   757     regex = {
   745         'name': 'name:\s*(.+)',
   758         'name': 'name: *(.+)',
   746         'org': 'Organisation:\s*(.+)',
   759         'org': 'Organisation: *(.+)',
   747         'address': 'Address:\s*(.+)',
   760         'address': 'Address: *(.+)',
   748         'zipcode': 'PostalCode:\s*(.+)',
   761         'zipcode': 'PostalCode: *(.+)',
   749         'city': 'City:\s*(.+)',
   762         'city': 'City: *(.+)',
   750         'country_code': 'CountryCode:\s*(.+)',
   763         'country_code': 'CountryCode: *(.+)',
   751         'phone': 'Phone:\s*(.+)',
   764         'phone': 'Phone: *(.+)',
   752         'fax': 'Fax:\s*(.+)'
   765         'fax': 'Fax: *(.+)'
   753     }
   766     }
   754 
   767 
   755     def __init__(self, domain, text):
   768     def __init__(self, domain, text):
   756         if 'Status: free' in text:
   769         if 'Status: free' in text:
   757             raise PywhoisError(text)
   770             raise PywhoisError(text)
   761 
   774 
   762 class WhoisBe(WhoisEntry):
   775 class WhoisBe(WhoisEntry):
   763     """Whois parser for .be domains
   776     """Whois parser for .be domains
   764     """
   777     """
   765     regex = {
   778     regex = {
   766         'name': 'Name:\s*(.+)',
   779         'name': 'Name: *(.+)',
   767         'org': 'Organisation:\s*(.+)',
   780         'org': 'Organisation: *(.+)',
   768         'phone': 'Phone:\s*(.+)',
   781         'phone': 'Phone: *(.+)',
   769         'fax': 'Fax:\s*(.+)',
   782         'fax': 'Fax: *(.+)',
   770         'email': 'Email:\s*(.+)',
   783         'email': 'Email: *(.+)',
   771     }
   784     }
   772 
   785 
   773     def __init__(self, domain, text):
   786     def __init__(self, domain, text):
   774         if 'Status: AVAILABLE' in text:
   787         if 'Status: AVAILABLE' in text:
   775             raise PywhoisError(text)
   788             raise PywhoisError(text)
   780 
   793 
   781 class WhoisInfo(WhoisEntry):
   794 class WhoisInfo(WhoisEntry):
   782     """Whois parser for .info domains
   795     """Whois parser for .info domains
   783     """
   796     """
   784     regex = {
   797     regex = {
   785         'domain_name':      'Domain Name:\s?(.+)',
   798         'domain_name':      'Domain Name: *(.+)',
   786         'registrar':        'Registrar:\s?(.+)',
   799         'registrar':        'Registrar: *(.+)',
   787         'whois_server':     'Whois Server:\s?(.+)', # empty usually
   800         'whois_server':     'Whois Server: *(.+)', # empty usually
   788         'referral_url':     'Referral URL:\s?(.+)', # http url of whois_server: empty usually
   801         'referral_url':     'Referral URL: *(.+)', # http url of whois_server: empty usually
   789         'updated_date':     'Updated Date:\s?(.+)',
   802         'updated_date':     'Updated Date: *(.+)',
   790         'creation_date':    'Creation Date:\s?(.+)',
   803         'creation_date':    'Creation Date: *(.+)',
   791         'expiration_date':  'Registry Expiry Date:\s?(.+)',
   804         'expiration_date':  'Registry Expiry Date: *(.+)',
   792         'name_servers':     'Name Server:\s?(.+)', # list of name servers
   805         'name_servers':     'Name Server: *(.+)', # list of name servers
   793         'status':           'Status:\s?(.+)', # list of statuses
   806         'status':           'Status: *(.+)', # list of statuses
   794         'emails':           '[\w.-]+@[\w.-]+\.[\w]{2,4}', # list of email addresses
   807         'emails':           EMAIL_REGEX, # list of email addresses
   795         'name':             'Registrant Name:\s*(.+)',
   808         'name':             'Registrant Name: *(.+)',
   796         'org':              'Registrant Organization:\s*(.+)',
   809         'org':              'Registrant Organization: *(.+)',
   797         'address':          'Registrant Street:\s*(.+)',
   810         'address':          'Registrant Street: *(.+)',
   798         'city':             'Registrant City:\s*(.+)',
   811         'city':             'Registrant City: *(.+)',
   799         'state':            'Registrant State/Province:\s*(.+)',
   812         'state':            'Registrant State/Province: *(.+)',
   800         'zipcode':          'Registrant Postal Code:\s*(.+)',
   813         'zipcode':          'Registrant Postal Code: *(.+)',
   801         'country':          'Registrant Country:\s*(.+)',
   814         'country':          'Registrant Country: *(.+)',
   802     }
   815     }
   803 
   816 
   804     def __init__(self, domain, text):
   817     def __init__(self, domain, text):
   805         if text.strip() == 'NOT FOUND':
   818         if text.strip() == 'NOT FOUND':
   806             raise PywhoisError(text)
   819             raise PywhoisError(text)
   824 
   837 
   825 class WhoisClub(WhoisEntry):
   838 class WhoisClub(WhoisEntry):
   826     """Whois parser for .us domains
   839     """Whois parser for .us domains
   827     """
   840     """
   828     regex = {
   841     regex = {
   829         'domain_name':                    'Domain Name:\s*(.+)',
   842         'domain_name':                    'Domain Name: *(.+)',
   830         'domain__id':                     'Domain ID:\s*(.+)',
   843         'domain__id':                     'Domain ID: *(.+)',
   831         'registrar':                      'Sponsoring Registrar:\s*(.+)',
   844         'registrar':                      'Sponsoring Registrar: *(.+)',
   832         'registrar_id':                   'Sponsoring Registrar IANA ID:\s*(.+)',
   845         'registrar_id':                   'Sponsoring Registrar IANA ID: *(.+)',
   833         'registrar_url':                  'Registrar URL \(registration services\):\s*(.+)',
   846         'registrar_url':                  'Registrar URL \(registration services\): *(.+)',
   834         # list of statuses
   847         # list of statuses
   835         'status':                         'Domain Status:\s*(.+)',
   848         'status':                         'Domain Status: *(.+)',
   836         'registrant_id':                  'Registrant ID:\s*(.+)',
   849         'registrant_id':                  'Registrant ID: *(.+)',
   837         'registrant_name':                'Registrant Name:\s*(.+)',
   850         'registrant_name':                'Registrant Name: *(.+)',
   838         'registrant_address1':            'Registrant Address1:\s*(.+)',
   851         'registrant_address1':            'Registrant Address1: *(.+)',
   839         'registrant_address2':            'Registrant Address2:\s*(.+)',
   852         'registrant_address2':            'Registrant Address2: *(.+)',
   840         'registrant_city':                'Registrant City:\s*(.+)',
   853         'registrant_city':                'Registrant City: *(.+)',
   841         'registrant_state_province':      'Registrant State/Province:\s*(.+)',
   854         'registrant_state_province':      'Registrant State/Province: *(.+)',
   842         'registrant_postal_code':         'Registrant Postal Code:\s*(.+)',
   855         'registrant_postal_code':         'Registrant Postal Code: *(.+)',
   843         'registrant_country':             'Registrant Country:\s*(.+)',
   856         'registrant_country':             'Registrant Country: *(.+)',
   844         'registrant_country_code':        'Registrant Country Code:\s*(.+)',
   857         'registrant_country_code':        'Registrant Country Code: *(.+)',
   845         'registrant_phone_number':        'Registrant Phone Number:\s*(.+)',
   858         'registrant_phone_number':        'Registrant Phone Number: *(.+)',
   846         'registrant_email':               'Registrant Email:\s*(.+)',
   859         'registrant_email':               'Registrant Email: *(.+)',
   847         'registrant_application_purpose': 'Registrant Application Purpose:\s*(.+)',
   860         'registrant_application_purpose': 'Registrant Application Purpose: *(.+)',
   848         'registrant_nexus_category':      'Registrant Nexus Category:\s*(.+)',
   861         'registrant_nexus_category':      'Registrant Nexus Category: *(.+)',
   849         'admin_id':                       'Administrative Contact ID:\s*(.+)',
   862         'admin_id':                       'Administrative Contact ID: *(.+)',
   850         'admin_name':                     'Administrative Contact Name:\s*(.+)',
   863         'admin_name':                     'Administrative Contact Name: *(.+)',
   851         'admin_address1':                 'Administrative Contact Address1:\s*(.+)',
   864         'admin_address1':                 'Administrative Contact Address1: *(.+)',
   852         'admin_address2':                 'Administrative Contact Address2:\s*(.+)',
   865         'admin_address2':                 'Administrative Contact Address2: *(.+)',
   853         'admin_city':                     'Administrative Contact City:\s*(.+)',
   866         'admin_city':                     'Administrative Contact City: *(.+)',
   854         'admin_state_province':           'Administrative Contact State/Province:\s*(.+)',
   867         'admin_state_province':           'Administrative Contact State/Province: *(.+)',
   855         'admin_postal_code':              'Administrative Contact Postal Code:\s*(.+)',
   868         'admin_postal_code':              'Administrative Contact Postal Code: *(.+)',
   856         'admin_country':                  'Administrative Contact Country:\s*(.+)',
   869         'admin_country':                  'Administrative Contact Country: *(.+)',
   857         'admin_country_code':             'Administrative Contact Country Code:\s*(.+)',
   870         'admin_country_code':             'Administrative Contact Country Code: *(.+)',
   858         'admin_phone_number':             'Administrative Contact Phone Number:\s*(.+)',
   871         'admin_phone_number':             'Administrative Contact Phone Number: *(.+)',
   859         'admin_email':                    'Administrative Contact Email:\s*(.+)',
   872         'admin_email':                    'Administrative Contact Email: *(.+)',
   860         'admin_application_purpose':      'Administrative Application Purpose:\s*(.+)',
   873         'admin_application_purpose':      'Administrative Application Purpose: *(.+)',
   861         'admin_nexus_category':           'Administrative Nexus Category:\s*(.+)',
   874         'admin_nexus_category':           'Administrative Nexus Category: *(.+)',
   862         'billing_id':                     'Billing Contact ID:\s*(.+)',
   875         'billing_id':                     'Billing Contact ID: *(.+)',
   863         'billing_name':                   'Billing Contact Name:\s*(.+)',
   876         'billing_name':                   'Billing Contact Name: *(.+)',
   864         'billing_address1':               'Billing Contact Address1:\s*(.+)',
   877         'billing_address1':               'Billing Contact Address1: *(.+)',
   865         'billing_address2':               'Billing Contact Address2:\s*(.+)',
   878         'billing_address2':               'Billing Contact Address2: *(.+)',
   866         'billing_city':                   'Billing Contact City:\s*(.+)',
   879         'billing_city':                   'Billing Contact City: *(.+)',
   867         'billing_state_province':         'Billing Contact State/Province:\s*(.+)',
   880         'billing_state_province':         'Billing Contact State/Province: *(.+)',
   868         'billing_postal_code':            'Billing Contact Postal Code:\s*(.+)',
   881         'billing_postal_code':            'Billing Contact Postal Code: *(.+)',
   869         'billing_country':                'Billing Contact Country:\s*(.+)',
   882         'billing_country':                'Billing Contact Country: *(.+)',
   870         'billing_country_code':           'Billing Contact Country Code:\s*(.+)',
   883         'billing_country_code':           'Billing Contact Country Code: *(.+)',
   871         'billing_phone_number':           'Billing Contact Phone Number:\s*(.+)',
   884         'billing_phone_number':           'Billing Contact Phone Number: *(.+)',
   872         'billing_email':                  'Billing Contact Email:\s*(.+)',
   885         'billing_email':                  'Billing Contact Email: *(.+)',
   873         'billing_application_purpose':    'Billing Application Purpose:\s*(.+)',
   886         'billing_application_purpose':    'Billing Application Purpose: *(.+)',
   874         'billing_nexus_category':         'Billing Nexus Category:\s*(.+)',
   887         'billing_nexus_category':         'Billing Nexus Category: *(.+)',
   875         'tech_id':                        'Technical Contact ID:\s*(.+)',
   888         'tech_id':                        'Technical Contact ID: *(.+)',
   876         'tech_name':                      'Technical Contact Name:\s*(.+)',
   889         'tech_name':                      'Technical Contact Name: *(.+)',
   877         'tech_address1':                  'Technical Contact Address1:\s*(.+)',
   890         'tech_address1':                  'Technical Contact Address1: *(.+)',
   878         'tech_address2':                  'Technical Contact Address2:\s*(.+)',
   891         'tech_address2':                  'Technical Contact Address2: *(.+)',
   879         'tech_city':                      'Technical Contact City:\s*(.+)',
   892         'tech_city':                      'Technical Contact City: *(.+)',
   880         'tech_state_province':            'Technical Contact State/Province:\s*(.+)',
   893         'tech_state_province':            'Technical Contact State/Province: *(.+)',
   881         'tech_postal_code':               'Technical Contact Postal Code:\s*(.+)',
   894         'tech_postal_code':               'Technical Contact Postal Code: *(.+)',
   882         'tech_country':                   'Technical Contact Country:\s*(.+)',
   895         'tech_country':                   'Technical Contact Country: *(.+)',
   883         'tech_country_code':              'Technical Contact Country Code:\s*(.+)',
   896         'tech_country_code':              'Technical Contact Country Code: *(.+)',
   884         'tech_phone_number':              'Technical Contact Phone Number:\s*(.+)',
   897         'tech_phone_number':              'Technical Contact Phone Number: *(.+)',
   885         'tech_email':                     'Technical Contact Email:\s*(.+)',
   898         'tech_email':                     'Technical Contact Email: *(.+)',
   886         'tech_application_purpose':       'Technical Application Purpose:\s*(.+)',
   899         'tech_application_purpose':       'Technical Application Purpose: *(.+)',
   887         'tech_nexus_category':            'Technical Nexus Category:\s*(.+)',
   900         'tech_nexus_category':            'Technical Nexus Category: *(.+)',
   888         # list of name servers
   901         # list of name servers
   889         'name_servers':                   'Name Server:\s*(.+)',
   902         'name_servers':                   'Name Server: *(.+)',
   890         'created_by_registrar':           'Created by Registrar:\s*(.+)',
   903         'created_by_registrar':           'Created by Registrar: *(.+)',
   891         'last_updated_by_registrar':      'Last Updated by Registrar:\s*(.+)',
   904         'last_updated_by_registrar':      'Last Updated by Registrar: *(.+)',
   892         'creation_date':                  'Domain Registration Date:\s*(.+)',
   905         'creation_date':                  'Domain Registration Date: *(.+)',
   893         'expiration_date':                'Domain Expiration Date:\s*(.+)',
   906         'expiration_date':                'Domain Expiration Date: *(.+)',
   894         'updated_date':                   'Domain Last Updated Date:\s*(.+)',
   907         'updated_date':                   'Domain Last Updated Date: *(.+)',
   895     }
   908     }
   896 
   909 
   897     def __init__(self, domain, text):
   910     def __init__(self, domain, text):
   898         if 'Not found:' in text:
   911         if 'Not found:' in text:
   899             raise PywhoisError(text)
   912             raise PywhoisError(text)
   903 
   916 
   904 class WhoisIo(WhoisEntry):
   917 class WhoisIo(WhoisEntry):
   905     """Whois parser for .io domains
   918     """Whois parser for .io domains
   906     """
   919     """
   907     regex = {
   920     regex = {
   908         'status':           'Status\s*:\s*(.+)',
   921         'status':           'Status\s*: *(.+)',
   909         'name_servers':     'NS \d?\s*:\s*(.+)',
   922         'name_servers':     'NS \d?\s*: *(.+)',
   910         'owner':            'Owner\s*:\s*(.+)',
   923         'owner':            'Owner\s*: *(.+)',
   911         'expiration_date':  'Expiry\s*:\s*(.+)',
   924         'expiration_date':  'Expiry\s*: *(.+)',
   912         'domain_name':      'Domain\s*:\s*(.+)',
   925         'domain_name':      'Domain\s*: *(.+)',
   913         'registrar':        r'Check for \'[\w\.]*\' --- (.+)',
   926         'registrar':        r'Check for \'[\w\.]*\' --- (.+)',
   914     }
   927     }
   915 
   928 
   916     def __init__(self, domain, text):
   929     def __init__(self, domain, text):
   917         if 'is available for purchase' in text:
   930         if 'is available for purchase' in text:
   938     """Whois parser for .kg domains
   951     """Whois parser for .kg domains
   939     """
   952     """
   940     regex = {
   953     regex = {
   941         'domain_name':                    'Domain\s*([\w]+\.[\w]{2,5})',
   954         'domain_name':                    'Domain\s*([\w]+\.[\w]{2,5})',
   942         'registrar':                      'Domain support: \s*(.+)',
   955         'registrar':                      'Domain support: \s*(.+)',
   943         'registrant_name':                'Name:\s*(.+)',
   956         'registrant_name':                'Name: *(.+)',
   944         'registrant_address1':            'Address:\s*(.+)',
   957         'registrant_address1':            'Address: *(.+)',
   945         'registrant_phone_number':        'phone:\s*(.+)',
   958         'registrant_phone_number':        'phone: *(.+)',
   946         'registrant_email':               'Email:\s*(.+)',
   959         'registrant_email':               'Email: *(.+)',
   947         # # list of name servers
   960         # # list of name servers
   948         'name_servers':                   'Name servers in the listed order:\s*([\d\w\.\s]+)',
   961         'name_servers':                   'Name servers in the listed order: *([\d\w\.\s]+)',
   949         # 'name_servers':      r'([\w]+\.[\w]+\.[\w]{2,5}\s*\d{1,3}\.\d]{1,3}\.[\d]{1-3}\.[\d]{1-3})',
   962         # 'name_servers':      r'([\w]+\.[\w]+\.[\w]{2,5}\s*\d{1,3}\.\d]{1,3}\.[\d]{1-3}\.[\d]{1-3})',
   950         'creation_date':                  'Record created:\s*(.+)',
   963         'creation_date':                  'Record created: *(.+)',
   951         'expiration_date':                'Record expires on \s*(.+)',
   964         'expiration_date':                'Record expires on \s*(.+)',
   952         'updated_date':                   'Record last updated on\s*(.+)',
   965         'updated_date':                   'Record last updated on\s*(.+)',
   953 
   966 
   954     }
   967     }
   955     def __init__(self, domain, text):
   968     def __init__(self, domain, text):