"""Read a deployment connection string from stdin; emit five fields plus a sentinel. This is a single-host dump contract, not a replacement for MySqlConnector. Reject ambiguous aliases/control characters instead of choosing a different backup target. Never include input values in errors or pass the connection string as an argument. """ import re import sys ALIASES = { **dict.fromkeys(['server', 'host', 'data source', 'datasource', 'address', 'addr', 'network address'], 'host'), **dict.fromkeys(['user id', 'userid', 'username', 'uid', 'user name', 'user'], 'user'), **dict.fromkeys(['password', 'pwd'], 'password'), **dict.fromkeys(['database', 'initial catalog'], 'database'), 'port': 'port', } PAIR = re.compile(r'''\s*([^=;]+?)\s*=\s*(?:"((?:""|[^"])*)"|'((?:''|[^'])*)'|([^;]*))\s*(?:;|$)''') def parse(connection): if any(ord(c) < 32 or ord(c) == 127 for c in connection): raise ValueError('Control characters are unsupported in backup connection settings.') fields = {} position = 0 while position < len(connection): if connection[position] == ';' or connection[position].isspace(): position += 1 continue match = PAIR.match(connection, position) if not match: raise ValueError('Malformed backup connection settings.') key, double, single, plain = match.groups() if double is not None: value = double.replace('""', '"') elif single is not None: value = single.replace("''", "'") else: value = plain.strip() if value.startswith(('"', "'")): raise ValueError('Malformed quoted backup connection value.') canonical = ALIASES.get(key.strip().lower()) if canonical: if canonical in fields: raise ValueError('Duplicate backup connection setting or alias.') fields[canonical] = value position = match.end() if any(not fields.get(key, '').strip() for key in ['host', 'database', 'user']): raise ValueError('Backup connection requires host, database and user.') port = fields.get('port', '3306') if not re.fullmatch(r'[0-9]+', port) or not 1 <= int(port) <= 65535: raise ValueError('Backup connection port must be between 1 and 65535.') if any(c in fields['host'] for c in ',/\\'): raise ValueError('Backup requires one TCP host, not a host list or socket path.') if any(c in fields['database'] for c in '/\\'): raise ValueError('Database name cannot contain backup path separators.') return [fields['host'], str(int(port)), fields['database'], fields['user'], fields.get('password', '')] if __name__ == '__main__': try: values = parse(sys.stdin.buffer.read().decode('utf-8')) except (ValueError, UnicodeError): print('Invalid or unsupported backup connection settings.', file=sys.stderr) sys.exit(1) sys.stdout.reconfigure(encoding='utf-8', newline='\n') # Sentinel keeps an empty final password intact through shell command substitution. sys.stdout.write('\n'.join([*values, 'END']) + '\n')