package install_interactive; # $Id$ use diagnostics; use strict; use common; use partition_table qw(:types); use partition_table::raw; use detect_devices; use install_steps; use install_any; use devices; use fsedit; use log; use fs; sub tellAboutProprietaryModules { my ($o) = @_; my @l = grep { $_ } map { $_->{driver} =~ /^Bad:(.*)/ && $1 } detect_devices::probeall(); $o->ask_warn('', formatAlaTeX( N("Some hardware on your computer needs ``proprietary'' drivers to work. You can find some information about them at: %s", join(", ", @l)))) if @l; } #- unit of $mb is mega bytes, min and max are in sectors, this #- function is used to convert back to sectors count the size of #- a partition ($mb) given from the interface (on Resize or Create). #- modified to take into account a true bounding with min and max. sub from_Mb { my ($mb, $min, $max) = @_; $mb <= $min >> 11 and return $min; $mb >= $max >> 11 and return $max; $mb * 2048; } sub partition_with_diskdrake { my ($o, $all_hds, $nowizard) = @_; my $ok; do { $ok = 1; my $do_force_reload = sub { $o->{all_hds} = fsedit::empty_all_hds(); install_any::getHds($o, $o); $all_hds = $o->{all_hds}; $o->{all_hds}; }; require diskdrake::interactive; { local $::expert = $::expert; diskdrake::interactive::main($o, $all_hds, $nowizard, $do_force_reload, $o->interactive_help_sub_display_id('partition_with_diskdrake')); } if (delete $o->{wizard}) { partitionWizard($o, 'nodiskdrake') or redo; return 1; } my @fstab = fsedit::get_all_fstab($all_hds); unless (fsedit::get_root_(\@fstab)) { $ok = 0; $o->ask_okcancel('', N("You must have a root partition. For this, create a partition (or click on an existing one). Then choose action ``Mount point'' and set it to `/'"), 1) or return; } if (!any { isSwap($_) } @fstab) { $ok &&= $o->ask_okcancel('', N("You don't have a swap partition.\n\nContinue anyway?")); } if (arch() =~ /ia64/ && !fsedit::has_mntpoint("/boot/efi", $all_hds)) { $o->ask_warn('', N("You must have a FAT partition mounted in /boot/efi")); $ok = ''; } } until $ok; 1; } sub partitionWizardSolutions { my ($o, $all_hds) = @_; my $hds = $all_hds->{hds}; my $fstab = [ fsedit::get_all_fstab($all_hds) ]; my @wizlog; my (%solutions); my $min_linux = 400 << 11; my $max_linux = 3000 << 11; my $min_swap = 50 << 11; my $max_swap = 300 << 11; my $min_freewin = 100 << 11; # each solution is a [ score, text, function ], where the function retunrs true if succeeded my @hds_rw = grep { !$_->{readonly} } @$hds; my @hds_can_add = grep { $_->can_raw_add } @hds_rw; if (fsedit::free_space(@hds_can_add) > $min_linux) { $solutions{free_space} = [ 20, N("Use free space"), sub { fsedit::auto_allocate($all_hds); 1 } ] } else { push @wizlog, N("Not enough free space to allocate new partitions") . ": " . (@hds_can_add ? fsedit::free_space(@hds_can_add) . " < $min_linux" : "no harddrive on which partitions can be added"); } if (my @truefs = grep { isTrueFS($_) } @$fstab) { #- value twice the ext2 partitions $solutions{existing_part} = [ 6 + @truefs + @$fstab, N("Use existing partitions"), sub { $o->ask_mntpoint_s($fstab) } ] } else { push @wizlog, N("There is no existing partition to use"); } my @fats = grep { isFat($_) } @$fstab; fs::df($_) foreach @fats; if (my @ok_forloopback = sort { $b->{free} <=> $a->{free} } grep { $_->{free} > $min_linux + $min_swap + $min_freewin } @fats) { $solutions{loopback} = [ -10 - @fats, N("Use the Windows partition for loopback"), sub { my ($s_root, $s_swap); my $part = $o->ask_from_listf('', N("Which partition do you want to use for Linux4Win?"), \&partition_table::description, \@ok_forloopback) or return; $max_swap = $min_swap + 1 if $part->{free} - $max_swap < $min_linux; $o->ask_from('', N("Choose the sizes"), [ { label => N("Root partition size in MB: "), val => \$s_root, min => $min_linux >> 11, max => min($part->{free} - $max_swap, $max_linux) >> 11, type => 'range' }, { label => N("Swap partition size in MB: "), val => \$s_swap, min => $min_swap >> 11, max => $max_swap >> 11, type => 'range' }, ]) or return; push @{$part->{loopback}}, { type =>0x483, loopback_file => '/lnx4win/linuxsys.img', mntpoint => '/', size => $s_root << 11, loopback_device => $part, notFormatted => 1 }, { type => 0x82, loopback_file => '/lnx4win/swapfile', mntpoint => 'swap', size => $s_swap << 11, loopback_device => $part, notFormatted => 1 }; fsedit::recompute_loopbacks($all_hds); 1; } ]; } else { push @wizlog, N("There is no FAT partition to use as loopback (or not enough space left)") . (@fats ? "\nFAT partitions:" . join('', map { "\n $_->{device} $_->{free} (" . ($min_linux + $min_swap + $min_freewin) . ")" } @fats) : ''); } if (my @ok_for_resize_fat = grep { isFat_or_NTFS($_) && !fsedit::part2hd($_, $all_hds)->{readonly} } @$fstab) { $solutions{resize_fat} = [ 6 - @ok_for_resize_fat, N("Use the free space on the Windows partition"), sub { my $part = $o->ask_from_listf_raw({ messages => N("Which partition do you want to resize?"), interactive_help_id => 'resizeFATChoose', }, \&partition_table::description, \@ok_for_resize_fat) or return; my $hd = fsedit::part2hd($part, $all_hds); my $resize_fat = eval { my $pkg = isFat($part) ? do { require resize_fat::main; 'resize_fat::main'; } : do { require diskdrake::resize_ntfs; 'diskdrake::resize_ntfs'; }; $pkg->new($part->{device}, devices::make($part->{device})); }; $@ and die \N("The FAT resizer is unable to handle your partition, the following error occured: %s", formatError($@)); my $min_win = do { my $_w = $o->wait_message(N("Resizing"), N("Computing the size of the Windows partition")); $resize_fat->min_size; }; #- make sure that even after normalizing the size to cylinder boundaries, the minimun will be saved, #- this save at least a cylinder (less than 8Mb). $min_win += partition_table::raw::cylinder_size($hd); $part->{size} > $min_linux + $min_swap + $min_freewin + $min_win or die \N("Your Windows partition is too fragmented. Please reboot your computer under Windows, run the ``defrag'' utility, then restart the Mandrake Linux installation."); $o->ask_okcancel('', N("WARNING! DrakX will now resize your Windows partition. Be careful: this operation is dangerous. If you have not already done so, you should first exit the installation, run scandisk under Windows (and optionally run defrag), then restart the installation. You should also backup your data. When sure, press Ok.")) or return; my $mb_size = $part->{size} >> 11; $o->ask_from('', N("Which size do you want to keep for Windows on"), [ { label => N("partition %s", partition_table::description($part)), val => \$mb_size, min => $min_win >> 11, max => ($part->{size} - $min_linux - $min_swap) >> 11, type => 'range' }, ]) or return; my $oldsize = $part->{size}; $part->{size} = from_Mb($mb_size, $min_win, $part->{size}); $hd->adjustEnd($part); eval { my $_w = $o->wait_message(N("Resizing"), N("Resizing Windows partition")); $resize_fat->resize($part->{size}); }; if (my $err = $@) { $part->{size} = $oldsize; die \N("FAT resizing failed: %s", formatError($err)); } $part->{isFormatted} = 1; $hd->{isDirty} = $hd->{needKernelReread} = 1; partition_table::adjust_local_extended($hd, $part); partition_table::adjust_main_extended($hd); fsedit::auto_allocate($all_hds); 1; } ]; } else { push @wizlog, N("There is no FAT partition to resize (or not enough space left)"); } if (@$fstab && @hds_rw) { $solutions{wipe_drive} = [ 10, fsedit::is_one_big_fat_or_NT($hds) ? N("Remove Windows(TM)") : N("Erase entire disk"), sub { my $hd = $o->ask_from_listf_raw({ messages => N("You have more than one hard drive, which one do you install linux on?"), interactive_help_id => 'takeOverHdChoose', }, \&partition_table::description, \@hds_rw) or return; $o->ask_okcancel_({ messages => N("ALL existing partitions and their data will be lost on drive %s", partition_table::description($hd)), interactive_help_id => 'takeOverHdConfirm' }) or return; partition_table::raw::zero_MBR($hd); fsedit::auto_allocate($all_hds); 1; } ]; } if (@hds_rw) { $solutions{diskdrake} = [ 0, N("Custom disk partitioning"), sub { partition_with_diskdrake($o, $all_hds, 'nowizard') } ]; } $solutions{fdisk} = [ -10, N("Use fdisk"), sub { $o->enter_console; foreach (@$hds) { print "\n" x 10, N("You can now partition %s. When you are done, don't forget to save using `w'", partition_table::description($_)); print "\n\n"; my $pid = 0; if (arch() =~ /ppc/) { $pid = fork() or exec "pdisk", devices::make($_->{device}); } else { $pid = fork() or exec "fdisk", devices::make($_->{device}); } waitpid($pid, 0); } $o->leave_console; 0; } ] if $o->{partitioning}{fdisk}; log::l("partitioning wizard log:\n", (map { ">>wizlog>>$_\n" } @wizlog)); %solutions; } sub partitionWizard { my ($o, $nodiskdrake) = @_; my %solutions = partitionWizardSolutions($o, $o->{all_hds}); delete $solutions{diskdrake} if $nodiskdrake; my @solutions = sort { $b->[0] <=> $a->[0] } values %solutions; my $level = $::expert ? -9999 : 0; my @sol = grep { $_->[0] >= $level } @solutions; log::l('' . "solutions found: " . join('', map { $_->[1] } @sol) . " (all solutions found: " . join('', map { $_->[1] } @solutions) . ")"); @solutions = @sol if @sol > 1; log::l("solutions: ", int @solutions); @solutions or $o->ask_warn('', N("I can't find any room for installing")), die 'already displayed'; log::l('HERE: ', join(',', map { $_->[1] } @solutions)); my $sol; $o->ask_from_({ messages => N("The DrakX Partitioning wizard found the following solutions:"), interactive_help_id => 'doPartitionDisks', }, [ { val => \$sol, list => \@solutions, format => sub { $_[0][1] }, type => 'list' } ]); log::l("partitionWizard calling solution $sol->[1]"); my $ok = eval { $sol->[2]->() }; $@ and $o->ask_warn('', N("Partitioning failed: %s", formatError($@))); $ok or goto &partitionWizard; 1; } sub upNetwork { my ($o, $pppAvoided) = @_; my $_w = $o->wait_message('', N("Bringing up the network")); install_steps::upNetwork($o, $pppAvoided); } sub downNetwork { my ($o, $pppOnly) = @_; my $_w = $o->wait_message('', N("Bringing down the network")); install_steps::downNetwork($o, $pppOnly); } 1; n243'>243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
<?php
/**
*
* This file is part of the phpBB Forum Software package.
*
* @copyright (c) phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
* For full copyright and license information, please see
* the docs/CREDITS.txt file.
*
*/

class phpbb_passwords_helper_test extends \phpbb_test_case
{
	public function setUp()
	{
		// Prepare dependencies for drivers
		$config =  new \phpbb\config\config(array());
		$request = new phpbb_mock_request(array(), array(), array(), array(), array('password' => 'fööbar'));
		$this->driver_helper = new \phpbb\passwords\driver\helper($config);
		$phpbb_root_path = dirname(__FILE__) . '/../../phpBB/';
		$php_ext = 'php';

		$this->passwords_drivers = array(
			'passwords.driver.bcrypt_2y'	=> new \phpbb\passwords\driver\bcrypt_2y($config, $this->driver_helper, 10),
			'passwords.driver.bcrypt'	=> new \phpbb\passwords\driver\bcrypt($config, $this->driver_helper, 10),
			'passwords.driver.salted_md5'	=> new \phpbb\passwords\driver\salted_md5($config, $this->driver_helper),
			'passwords.driver.phpass'	=> new \phpbb\passwords\driver\phpass($config, $this->driver_helper),
			'passwords.driver.sha1_smf'	=> new \phpbb\passwords\driver\sha1_smf($config, $this->driver_helper),
			'passwords.driver.sha1_wcf1'	=> new \phpbb\passwords\driver\sha1_wcf1($config, $this->driver_helper),
			'passwords.driver.convert_password'=> new \phpbb\passwords\driver\convert_password($config, $this->driver_helper),
			'passwords.driver.sha1'		=> new \phpbb\passwords\driver\sha1($config, $this->driver_helper),
			'passwords.driver.md5_mybb'	=> new \phpbb\passwords\driver\md5_mybb($config, $this->driver_helper),
			'passwords.driver.md5_vb'	=> new \phpbb\passwords\driver\md5_vb($config, $this->driver_helper),
			'passwords.driver.sha_xf1'	=> new \phpbb\passwords\driver\sha_xf1($config, $this->driver_helper),
		);
		$this->passwords_drivers['passwords.driver.md5_phpbb2']	= new \phpbb\passwords\driver\md5_phpbb2($request, $this->passwords_drivers['passwords.driver.salted_md5'], $this->driver_helper, $phpbb_root_path, $php_ext);
		$this->passwords_drivers['passwords.driver.bcrypt_wcf2'] = new \phpbb\passwords\driver\bcrypt_wcf2($this->passwords_drivers['passwords.driver.bcrypt'], $this->driver_helper);
	}

	public function data_helper_encode64()
	{
		return array(
			array('foobar', 6, 'axqPW3aQ'),
			array('foobar', 7, 'axqPW3aQ..'),
			array('foobar', 5, 'axqPW34'),
		);
	}

	/**
	* @dataProvider data_helper_encode64
	*/
	public function test_helper_encode64($input, $length, $output)
	{
		$return = $this->driver_helper->hash_encode64($input, $length);
		$this->assertSame($output, $return);
	}

	public function data_get_random_salt()
	{
		return array(
			array(24, false),
			array(24, '/dev/foobar'),
		);
	}

	/**
	* @dataProvider data_get_random_salt
	*/
	public function test_get_random_salt($length, $rand_seed)
	{
		$rand_string = (empty($rand_seed)) ? $this->driver_helper->get_random_salt($length) : $this->driver_helper->get_random_salt($length, $rand_seed);
		$start = microtime(true);

		// Run each test for max. 1 second
		while ((microtime(true) - $start) < 1)
		{
			$urandom_string = (empty($rand_seed)) ? $this->driver_helper->get_random_salt($length) : $this->driver_helper->get_random_salt($length, $rand_seed);
			$this->assertSame($length, strlen($urandom_string));
			$this->assertNotSame($rand_string, $urandom_string);
		}
	}

	public function test_get_hash_settings_salted_md5()
	{
		$settings = $this->passwords_drivers['passwords.driver.salted_md5']->get_hash_settings('$H$9isfrtKXWqrz8PvztXlL3.daw4U0zI1');
		$this->assertEquals(array(
				'count'	=> pow(2, 11),
				'salt'	=> 'isfrtKXW',
				'full'	=> '$H$9isfrtKXW',
			),
			$settings
		);
		$this->assertEquals(false, $this->passwords_drivers['passwords.driver.salted_md5']->get_hash_settings(false));
	}

	public function data_hash_sha1_smf()
	{
		return array(
			array(false, 'test', array()),
			array(false, 'test', ''),
			array('6f9e2a1899e1f15708fd2e554103480eb53e8b57', 'foobar', array('login_name' => 'test')),
		);
	}

	/**
	* @dataProvider data_hash_sha1_smf
	*/
	public function test_hash_sha1_smf($expected, $password, $user_row)
	{
		$this->assertSame($expected, $this->passwords_drivers['passwords.driver.sha1_smf']->hash($password, $user_row));
	}

	public function data_get_settings()
	{
		return array(
			array(false, '6f9e2a1899e1f15708fd2e554103480eb53e8b57', 'passwords.driver.sha1_smf'),
		);
	}

	/**
	* @dataProvider data_get_settings
	*/
	public function test_get_settings_only($expected, $hash, $driver)
	{
		$this->assertSame($expected, $this->passwords_drivers[$driver]->get_settings_only($hash));
	}

	public function data_md5_phpbb2_check()
	{
		return array(
			array(false, 'foobar', 'ae2fc75e20ee25d4520766788fbc96ae'),
			array(false, 'foobar', 'ae2fc75e20ee25d4520766788fbc96aeddsf'),
			array(false, 'fööbar', 'ae2fc75e20ee25d4520766788fbc96ae'),
			array(true, 'fööbar', 'ae2fc75e20ee25d4520766788fbc96ae', utf8_decode('fööbar')),
			array(true, 'fööbar', '$H$966CepJh9RC3hFIm7aKywR6jEn0kpA0', utf8_decode('fööbar')),
			array(true, 'fööbar', '$H$9rNjgwETtmc8befO8JL1xFMrrMw8MC.', $this->utf8_to_cp1252(utf8_decode('fööbar'))),
			array(true, 'fööbar', '$H$9rNjgwETtmc8befO8JL1xFMrrMw8MC.', $this->utf8_to_cp1252('fööbar')),
		);
	}

	/**
	* @dataProvider data_md5_phpbb2_check
	*/
	public function test_md5_phpbb2_check($expected, $password, $hash, $request_password = false)
	{
		if (!$request_password)
		{
			unset($_REQUEST['password']);
		}
		else
		{
			$_REQUEST['password'] = $request_password;
		}
		$this->assertSame($expected, $this->passwords_drivers['passwords.driver.md5_phpbb2']->check($password, $hash));
	}

	public function test_md5_phpbb2_hash()
	{
		$this->assertSame(false, $this->passwords_drivers['passwords.driver.md5_phpbb2']->hash('foobar'));
	}

	public function test_convert_password_driver()
	{
		$this->assertSame(false, $this->passwords_drivers['passwords.driver.convert_password']->hash('foobar'));
	}

	public function test_sha1_driver()
	{
		$this->assertSame(false, $this->passwords_drivers['passwords.driver.sha1']->hash('foobar'));
	}

	public function data_md5_mybb_check()
	{
		return array(
			array(false, 'foobar', '083d11daea8675b1b4b502c7e55f8dbd'),
			array(false, 'foobar', '083d11daea8675b1b4b502c7e55f8dbd', array('user_passwd_salt' => 'ae2fc75e')),
			array(true, 'foobar', 'b86ee7e24008bfd2890dcfab1ed31333', array('user_passwd_salt' => 'yeOtfFO6')),
		);
	}

	/**
	* @dataProvider data_md5_mybb_check
	*/
	public function test_md5_mybb_check($expected, $password, $hash, $user_row = array())
	{
		$this->assertSame($expected, $this->passwords_drivers['passwords.driver.md5_mybb']->check($password, $hash, $user_row));
	}

	public function test_md5_mybb_driver()
	{
		$this->assertSame(false, $this->passwords_drivers['passwords.driver.md5_mybb']->hash('foobar'));
	}

	public function data_md5_vb_check()
	{
		return array(
			array(false, 'foobar', '083d11daea8675b1b4b502c7e55f8dbd'),
			array(false, 'foobar', 'b86ee7e24008bfd2890dcfab1ed31333', array('user_passwd_salt' => 'yeOtfFO6')),
			array(true, 'foobar', 'b452c54c44c588fc095d2d000935c470', array('user_passwd_salt' => '9^F')),
			array(true, 'foobar', 'f23a8241bd115d270c703213e3ef7f52', array('user_passwd_salt' => 'iaU*U%`CBl;/e~>D%do2m@Xf/,KZB0')),
			array(false, 'nope', 'f23a8241bd115d270c703213e3ef7f52', array('user_passwd_salt' => 'iaU*U%`CBl;/e~>D%do2m@Xf/,KZB0')),
		);
	}

	/**
	* @dataProvider data_md5_vb_check
	*/
	public function test_md5_vb_check($expected, $password, $hash, $user_row = array())
	{
		$this->assertSame($expected, $this->passwords_drivers['passwords.driver.md5_vb']->check($password, $hash, $user_row));
	}

	public function test_md5_vb_driver()
	{
		$this->assertSame(false, $this->passwords_drivers['passwords.driver.md5_vb']->hash('foobar'));
	}

	public function data_sha1_wcf1_check()
	{
		return array(
			array(false, 'foobar', 'fc46b9d9386167ce365ea3b891bf5dc31ddcd3ff'),
			array(false, 'foobar', 'fc46b9d9386167ce365ea3b891bf5dc31ddcd3ff', array('user_passwd_salt' => 'yeOtfFO6')),
			array(true, 'foobar', 'fc46b9d9386167ce365ea3b891bf5dc31ddcd3ff', array('user_passwd_salt' => '1a783e478d63f6422783a868db667aed3a857840')),
		);
	}

	/**
	* @dataProvider data_sha1_wcf1_check
	*/
	public function test_sha1_wcf1_check($expected, $password, $hash, $user_row = array())
	{
		$this->assertSame($expected, $this->passwords_drivers['passwords.driver.sha1_wcf1']->check($password, $hash, $user_row));
	}

	public function test_sha1_wcf1_driver()
	{
		$this->assertSame(false, $this->passwords_drivers['passwords.driver.sha1_wcf1']->hash('foobar'));
	}

	public function data_bcrypt_wcf2_check()
	{
		return array(
			array(false, 'foobar', 'fc46b9d9386167ce365ea3b891bf5dc31ddcd3ff'),
			array(true, 'foobar', '$2a$08$p8h14U0jsEiVb1Luy.s8oOTXSQ0hVWUXpcNGBoCezeYNXrQyCKHfi'),
			array(false, 'foobar', ''),
		);
	}

	/**
	* @dataProvider data_bcrypt_wcf2_check
	*/
	public function test_bcrypt_wcf2_check($expected, $password, $hash)
	{
		$this->assertSame($expected, $this->passwords_drivers['passwords.driver.bcrypt_wcf2']->check($password, $hash));
	}

	public function test_bcrypt_wcf2_driver()
	{
		$this->assertSame(false, $this->passwords_drivers['passwords.driver.bcrypt_wcf2']->hash('foobar'));
	}

	public function data_sha_xf1_check()
	{
		return array(
			array(false, 'foobar', 'fc46b9d9386167ce365ea3b891bf5dc31ddcd3ff'),
			array(false, 'foobar', 'fc46b9d9386167ce365ea3b891bf5dc31ddcd3ff', array('user_passwd_salt' => 'yeOtfFO6')),
			array(true, 'foobar', '7f65d2fa8a826d232f8134772252f8b1aaef8594b1edcabd9ab65e5b0f236ff0', array('user_passwd_salt' => '15b6c02cedbd727f563dcca607a89b085287b448966f19c0cc78cae263b1e38c')),
			array(true, 'foobar', '69962ae2079420573a3948cc4dedbabd35680051', array('user_passwd_salt' => '15b6c02cedbd727f563dcca607a89b085287b448966f19c0cc78cae263b1e38c')),
		);
	}

	/**
	* @dataProvider data_sha_xf1_check
	*/
	public function test_sha_xf1_check($expected, $password, $hash, $user_row = array())
	{
		$this->assertSame($expected, $this->passwords_drivers['passwords.driver.sha_xf1']->check($password, $hash, $user_row));
	}

	public function test_sha_xf1_driver()
	{
		$this->assertSame(false, $this->passwords_drivers['passwords.driver.sha_xf1']->hash('foobar'));
	}

	protected function utf8_to_cp1252($string)
	{
		static $transform = array(
			"\xE2\x82\xAC" => "\x80",
			"\xE2\x80\x9A" => "\x82",
			"\xC6\x92" => "\x83",
			"\xE2\x80\x9E" => "\x84",
			"\xE2\x80\xA6" => "\x85",
			"\xE2\x80\xA0" => "\x86",
			"\xE2\x80\xA1" => "\x87",
			"\xCB\x86" => "\x88",
			"\xE2\x80\xB0" => "\x89",
			"\xC5\xA0" => "\x8A",
			"\xE2\x80\xB9" => "\x8B",
			"\xC5\x92" => "\x8C",
			"\xC5\xBD" => "\x8E",
			"\xE2\x80\x98" => "\x91",
			"\xE2\x80\x99" => "\x92",
			"\xE2\x80\x9C" => "\x93",
			"\xE2\x80\x9D" => "\x94",
			"\xE2\x80\xA2" => "\x95",
			"\xE2\x80\x93" => "\x96",
			"\xE2\x80\x94" => "\x97",
			"\xCB\x9C" => "\x98",
			"\xE2\x84\xA2" => "\x99",
			"\xC5\xA1" => "\x9A",
			"\xE2\x80\xBA" => "\x9B",
			"\xC5\x93" => "\x9C",
			"\xC5\xBE" => "\x9E",
			"\xC5\xB8" => "\x9F",
			"\xC2\xA0" => "\xA0",
			"\xC2\xA1" => "\xA1",
			"\xC2\xA2" => "\xA2",
			"\xC2\xA3" => "\xA3",
			"\xC2\xA4" => "\xA4",
			"\xC2\xA5" => "\xA5",
			"\xC2\xA6" => "\xA6",
			"\xC2\xA7" => "\xA7",
			"\xC2\xA8" => "\xA8",
			"\xC2\xA9" => "\xA9",
			"\xC2\xAA" => "\xAA",
			"\xC2\xAB" => "\xAB",
			"\xC2\xAC" => "\xAC",
			"\xC2\xAD" => "\xAD",
			"\xC2\xAE" => "\xAE",
			"\xC2\xAF" => "\xAF",
			"\xC2\xB0" => "\xB0",
			"\xC2\xB1" => "\xB1",
			"\xC2\xB2" => "\xB2",
			"\xC2\xB3" => "\xB3",
			"\xC2\xB4" => "\xB4",
			"\xC2\xB5" => "\xB5",
			"\xC2\xB6" => "\xB6",
			"\xC2\xB7" => "\xB7",
			"\xC2\xB8" => "\xB8",
			"\xC2\xB9" => "\xB9",
			"\xC2\xBA" => "\xBA",
			"\xC2\xBB" => "\xBB",
			"\xC2\xBC" => "\xBC",
			"\xC2\xBD" => "\xBD",
			"\xC2\xBE" => "\xBE",
			"\xC2\xBF" => "\xBF",
			"\xC3\x80" => "\xC0",
			"\xC3\x81" => "\xC1",
			"\xC3\x82" => "\xC2",
			"\xC3\x83" => "\xC3",
			"\xC3\x84" => "\xC4",
			"\xC3\x85" => "\xC5",
			"\xC3\x86" => "\xC6",
			"\xC3\x87" => "\xC7",
			"\xC3\x88" => "\xC8",
			"\xC3\x89" => "\xC9",
			"\xC3\x8A" => "\xCA",
			"\xC3\x8B" => "\xCB",
			"\xC3\x8C" => "\xCC",
			"\xC3\x8D" => "\xCD",
			"\xC3\x8E" => "\xCE",
			"\xC3\x8F" => "\xCF",
			"\xC3\x90" => "\xD0",
			"\xC3\x91" => "\xD1",
			"\xC3\x92" => "\xD2",
			"\xC3\x93" => "\xD3",
			"\xC3\x94" => "\xD4",
			"\xC3\x95" => "\xD5",
			"\xC3\x96" => "\xD6",
			"\xC3\x97" => "\xD7",
			"\xC3\x98" => "\xD8",
			"\xC3\x99" => "\xD9",
			"\xC3\x9A" => "\xDA",
			"\xC3\x9B" => "\xDB",
			"\xC3\x9C" => "\xDC",
			"\xC3\x9D" => "\xDD",
			"\xC3\x9E" => "\xDE",
			"\xC3\x9F" => "\xDF",
			"\xC3\xA0" => "\xE0",
			"\xC3\xA1" => "\xE1",
			"\xC3\xA2" => "\xE2",
			"\xC3\xA3" => "\xE3",
			"\xC3\xA4" => "\xE4",
			"\xC3\xA5" => "\xE5",
			"\xC3\xA6" => "\xE6",
			"\xC3\xA7" => "\xE7",
			"\xC3\xA8" => "\xE8",
			"\xC3\xA9" => "\xE9",
			"\xC3\xAA" => "\xEA",
			"\xC3\xAB" => "\xEB",
			"\xC3\xAC" => "\xEC",