mirror of
				https://github.com/anticensority/runet-censorship-bypass.git
				synced 2025-11-04 09:57:50 +03:00 
			
		
		
		
	Compare commits
	
		
			333 Commits
		
	
	
		
			1.5
			...
			developmen
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					d86cf384af | ||
| 
						 | 
					e1c95a3ece | ||
| 
						 | 
					edabf14dd5 | ||
| 
						 | 
					2f5d425f34 | ||
| 
						 | 
					1c38137200 | ||
| 
						 | 
					99b68ec8d0 | ||
| 
						 | 
					0cc47d43a9 | ||
| 
						 | 
					a583e5ecb3 | ||
| 
						 | 
					884b4f0fbc | ||
| 
						 | 
					770c4ab47d | ||
| 
						 | 
					c5d9ca3254 | ||
| 
						 | 
					27ad6944e0 | ||
| 
						 | 
					a589568bf9 | ||
| 
						 | 
					b3585293a7 | ||
| 
						 | 
					fd45db7dcc | ||
| 
						 | 
					65eb930682 | ||
| 
						 | 
					d3156175d8 | ||
| 
						 | 
					c0862ecfff | ||
| 
						 | 
					75d30ba467 | ||
| 
						 | 
					a7e93d8383 | ||
| 
						 | 
					b3096c8d83 | ||
| 
						 | 
					ae930da8b3 | ||
| 
						 | 
					451691ef2a | ||
| 
						 | 
					6e8943ca1c | ||
| 
						 | 
					61f22e08be | ||
| 
						 | 
					b45a78e654 | ||
| 
						 | 
					08e1b41780 | ||
| 
						 | 
					e5fb9d11dd | ||
| 
						 | 
					689f9266f7 | ||
| 
						 | 
					6858c56534 | ||
| 
						 | 
					7f9fb4f972 | ||
| 
						 | 
					0e803bd08f | ||
| 
						 | 
					6ef5807971 | ||
| 
						 | 
					97adb7f3f4 | ||
| 
						 | 
					db8dfbfd50 | ||
| 
						 | 
					3d72acc6dd | ||
| 
						 | 
					0d7678ff50 | ||
| 
						 | 
					edf6fc8b5f | ||
| 
						 | 
					0951e83dd1 | ||
| 
						 | 
					79049bed25 | ||
| 
						 | 
					951a5e9248 | ||
| 
						 | 
					6634f72580 | ||
| 
						 | 
					aac0182738 | ||
| 
						 | 
					8f00516d44 | ||
| 
						 | 
					1265a3ad79 | ||
| 
						 | 
					93aa084de9 | ||
| 
						 | 
					cb0d991e94 | ||
| 
						 | 
					b77be60946 | ||
| 
						 | 
					1aec32de3e | ||
| 
						 | 
					86d07b79c3 | ||
| 
						 | 
					5b3e6f1f51 | ||
| 
						 | 
					ce22337928 | ||
| 
						 | 
					c0e5609d44 | ||
| 
						 | 
					c496fe1030 | ||
| 
						 | 
					034c08828f | ||
| 
						 | 
					1c479a4e47 | ||
| 
						 | 
					6e24ee0454 | ||
| 
						 | 
					e1bdf654f2 | ||
| 
						 | 
					e73766ae24 | ||
| 
						 | 
					5164559fc6 | ||
| 
						 | 
					e344b987c5 | ||
| 
						 | 
					05903e32b3 | ||
| 
						 | 
					f43eafaf8a | ||
| 
						 | 
					b5154f6453 | ||
| 
						 | 
					699bef6fea | ||
| 
						 | 
					da7701393e | ||
| 
						 | 
					235f6e4b47 | ||
| 
						 | 
					0c5dc8eb26 | ||
| 
						 | 
					57a2a8c1ac | ||
| 
						 | 
					d3caee1483 | ||
| 
						 | 
					e4a936c5f1 | ||
| 
						 | 
					414e2547f3 | ||
| 
						 | 
					04be2d6164 | ||
| 
						 | 
					e51673f268 | ||
| 
						 | 
					d56a7e7366 | ||
| 
						 | 
					0b13cb1dd0 | ||
| 
						 | 
					14f32b4d13 | ||
| 
						 | 
					9ef236d604 | ||
| 
						 | 
					7dae51db8c | ||
| 
						 | 
					796681ae5a | ||
| 
						 | 
					ccda45a57c | ||
| 
						 | 
					e95c186de1 | ||
| 
						 | 
					df3ea9e6ad | ||
| 
						 | 
					38e2bb2f3c | ||
| 
						 | 
					35668f620b | ||
| 
						 | 
					1371dc01cd | ||
| 
						 | 
					95bfd3ae9e | ||
| 
						 | 
					f93e01bb8a | ||
| 
						 | 
					b3997f48ac | ||
| 
						 | 
					1a0c168458 | ||
| 
						 | 
					c566fcecb5 | ||
| 
						 | 
					aa727d9329 | ||
| 
						 | 
					53571f6228 | ||
| 
						 | 
					eb996931ae | ||
| 
						 | 
					63f5e6fd08 | ||
| 
						 | 
					fe2e986bfe | ||
| 
						 | 
					da06ea4aac | ||
| 
						 | 
					e60b8cc96c | ||
| 
						 | 
					c8e7d7f427 | ||
| 
						 | 
					65f712d0a1 | ||
| 
						 | 
					71d1aff6cd | ||
| 
						 | 
					5287510263 | ||
| 
						 | 
					a12621635b | ||
| 
						 | 
					fb9aa707a6 | ||
| 
						 | 
					e3c595a447 | ||
| 
						 | 
					73485e8be4 | ||
| 
						 | 
					99a4ff9d24 | ||
| 
						 | 
					97ba2697da | ||
| 
						 | 
					0ffefd7cfc | ||
| 
						 | 
					747ad70497 | ||
| 
						 | 
					3f0cebebad | ||
| 
						 | 
					4985ef6526 | ||
| 
						 | 
					5278c65c90 | ||
| 
						 | 
					94aefc8ddc | ||
| 
						 | 
					fccc2611b2 | ||
| 
						 | 
					4e870d5b87 | ||
| 
						 | 
					17989f4735 | ||
| 
						 | 
					7037af70ca | ||
| 
						 | 
					8145f5f04a | ||
| 
						 | 
					ea85cf941b | ||
| 
						 | 
					f07f0d1fbb | ||
| 
						 | 
					71fe983cf2 | ||
| 
						 | 
					60db8d0752 | ||
| 
						 | 
					21ab71ad44 | ||
| 
						 | 
					ba19010545 | ||
| 
						 | 
					ed3997272e | ||
| 
						 | 
					84902d8e5a | ||
| 
						 | 
					94b4261418 | ||
| 
						 | 
					c2d3e12d63 | ||
| 
						 | 
					0036050e14 | ||
| 
						 | 
					288183ddea | ||
| 
						 | 
					4b27db6285 | ||
| 
						 | 
					ad3c390056 | ||
| 
						 | 
					0a0df56dbe | ||
| 
						 | 
					bc2ceed1dc | ||
| 
						 | 
					74233d7fa8 | ||
| 
						 | 
					97b6c2cdc3 | ||
| 
						 | 
					b6f95d5829 | ||
| 
						 | 
					7069c9a1ff | ||
| 
						 | 
					bf70de3727 | ||
| 
						 | 
					53e64ef61d | ||
| 
						 | 
					9a7b5407a2 | ||
| 
						 | 
					12db4afe55 | ||
| 
						 | 
					4e736bf403 | ||
| 
						 | 
					a6fd4d44f1 | ||
| 
						 | 
					85edbf75b2 | ||
| 
						 | 
					55b4ba385a | ||
| 
						 | 
					426793e700 | ||
| 
						 | 
					9d5921ae43 | ||
| 
						 | 
					af7a5a24bc | ||
| 
						 | 
					ad8aa3baed | ||
| 
						 | 
					05d1b6f6be | ||
| 
						 | 
					4704585c1a | ||
| 
						 | 
					faae7c6d4a | ||
| 
						 | 
					2ba422dde7 | ||
| 
						 | 
					7db0946f60 | ||
| 
						 | 
					b8d770862e | ||
| 
						 | 
					5be69e9c51 | ||
| 
						 | 
					f87a1c0178 | ||
| 
						 | 
					a20d36fdb6 | ||
| 
						 | 
					b36215f43f | ||
| 
						 | 
					08a3e17642 | ||
| 
						 | 
					7256a50804 | ||
| 
						 | 
					e9420a68c9 | ||
| 
						 | 
					7c13d38e4f | ||
| 
						 | 
					1c12ad86c8 | ||
| 
						 | 
					cbd377b33b | ||
| 
						 | 
					719d6eac9c | ||
| 
						 | 
					1eb005f085 | ||
| 
						 | 
					b33e038c2e | ||
| 
						 | 
					f37539a19c | ||
| 
						 | 
					60ebdae57f | ||
| 
						 | 
					89f394b9a5 | ||
| 
						 | 
					4beb6e9fcb | ||
| 
						 | 
					db4eb6767d | ||
| 
						 | 
					8432bc65de | ||
| 
						 | 
					186ca1bdb4 | ||
| 
						 | 
					b68151c998 | ||
| 
						 | 
					e6d5be6e56 | ||
| 
						 | 
					d6060e9e9c | ||
| 
						 | 
					817fa5abe0 | ||
| 
						 | 
					4965921c06 | ||
| 
						 | 
					26fc0c4de3 | ||
| 
						 | 
					5d64e445fc | ||
| 
						 | 
					8cf2602e61 | ||
| 
						 | 
					927fd7d043 | ||
| 
						 | 
					2ce48cbd50 | ||
| 
						 | 
					68df622d71 | ||
| 
						 | 
					a848c159a0 | ||
| 
						 | 
					3159ca910f | ||
| 
						 | 
					9e2f89a235 | ||
| 
						 | 
					9c2b99a738 | ||
| 
						 | 
					66d89a3b97 | ||
| 
						 | 
					2784e8a751 | ||
| 
						 | 
					3f8cd09c0e | ||
| 
						 | 
					1a92356e06 | ||
| 
						 | 
					496073e504 | ||
| 
						 | 
					1a25e74149 | ||
| 
						 | 
					6eaec132b3 | ||
| 
						 | 
					2a7a5a12ca | ||
| 
						 | 
					3b03be61c5 | ||
| 
						 | 
					77ac9ec63d | ||
| 
						 | 
					1f31f2bce7 | ||
| 
						 | 
					c2985fe072 | ||
| 
						 | 
					859dcc1227 | ||
| 
						 | 
					94cbef746b | ||
| 
						 | 
					31ca68a918 | ||
| 
						 | 
					6fb1765fcb | ||
| 
						 | 
					fc3226febd | ||
| 
						 | 
					71fcd38349 | ||
| 
						 | 
					03a113119b | ||
| 
						 | 
					13a2708de9 | ||
| 
						 | 
					6988d42068 | ||
| 
						 | 
					0d4366712f | ||
| 
						 | 
					a1ed5db0d1 | ||
| 
						 | 
					6fe17ced57 | ||
| 
						 | 
					4bb8f7d50e | ||
| 
						 | 
					4f4103405a | ||
| 
						 | 
					868168b752 | ||
| 
						 | 
					d6d3fb2dc5 | ||
| 
						 | 
					4b5ae6c461 | ||
| 
						 | 
					7f7f27a417 | ||
| 
						 | 
					02ac70b5f6 | ||
| 
						 | 
					ae0fbeaf8a | ||
| 
						 | 
					72faa8b8a9 | ||
| 
						 | 
					ec6450096f | ||
| 
						 | 
					0cfbc063f1 | ||
| 
						 | 
					5ed0ced254 | ||
| 
						 | 
					d3b828303c | ||
| 
						 | 
					7b98ed1ce7 | ||
| 
						 | 
					e9edd324fc | ||
| 
						 | 
					c9036f038a | ||
| 
						 | 
					ba43fa9f19 | ||
| 
						 | 
					f9f50a4314 | ||
| 
						 | 
					68d1270c54 | ||
| 
						 | 
					22deaf6720 | ||
| 
						 | 
					f489c05466 | ||
| 
						 | 
					74bd7eb685 | ||
| 
						 | 
					555d2c30bf | ||
| 
						 | 
					9ee9954a43 | ||
| 
						 | 
					f9671fb1fe | ||
| 
						 | 
					314429cb16 | ||
| 
						 | 
					8a243e16be | ||
| 
						 | 
					ea7914e115 | ||
| 
						 | 
					b866245ed0 | ||
| 
						 | 
					4d22581359 | ||
| 
						 | 
					285c393cd2 | ||
| 
						 | 
					e5c6c281de | ||
| 
						 | 
					4e7bfd7be6 | ||
| 
						 | 
					eac777406b | ||
| 
						 | 
					c53c38fd4f | ||
| 
						 | 
					1502c4f159 | ||
| 
						 | 
					dd36c63ed7 | ||
| 
						 | 
					55c1ccab2a | ||
| 
						 | 
					87072aa3c7 | ||
| 
						 | 
					ec5f3b26b7 | ||
| 
						 | 
					cd741414c6 | ||
| 
						 | 
					9596af084e | ||
| 
						 | 
					a78a844d21 | ||
| 
						 | 
					99e8c105ed | ||
| 
						 | 
					afbe1f4537 | ||
| 
						 | 
					8d749eb2ae | ||
| 
						 | 
					c59ffda2e2 | ||
| 
						 | 
					a592b4c4ae | ||
| 
						 | 
					59229bc63e | ||
| 
						 | 
					f40f371dd7 | ||
| 
						 | 
					8bd853fe57 | ||
| 
						 | 
					2576ec99ba | ||
| 
						 | 
					08da89030d | ||
| 
						 | 
					49cac9da15 | ||
| 
						 | 
					04a6f7e13a | ||
| 
						 | 
					e7fed46cc9 | ||
| 
						 | 
					220d368d89 | ||
| 
						 | 
					b45faad6cb | ||
| 
						 | 
					4e659dbec9 | ||
| 
						 | 
					bc1836b0f8 | ||
| 
						 | 
					b96e12d803 | ||
| 
						 | 
					c2a3c23e7e | ||
| 
						 | 
					a86b9f6a0e | ||
| 
						 | 
					30875cb9a1 | ||
| 
						 | 
					a07ecfeaf2 | ||
| 
						 | 
					498f35df71 | ||
| 
						 | 
					0b80a6bd18 | ||
| 
						 | 
					e5b47999b6 | ||
| 
						 | 
					73bb56e5e0 | ||
| 
						 | 
					466fe1451d | ||
| 
						 | 
					bca0135b77 | ||
| 
						 | 
					1191a5e043 | ||
| 
						 | 
					4738bcf9af | ||
| 
						 | 
					a35ab098d4 | ||
| 
						 | 
					aa21b5ad9e | ||
| 
						 | 
					609017da1a | ||
| 
						 | 
					8a9eb02d6e | ||
| 
						 | 
					688eef70db | ||
| 
						 | 
					314bf57b94 | ||
| 
						 | 
					a29cfd3c38 | ||
| 
						 | 
					3a79e9738b | ||
| 
						 | 
					b8a03d1329 | ||
| 
						 | 
					8df4e50ebb | ||
| 
						 | 
					029f80633d | ||
| 
						 | 
					13de0bf079 | ||
| 
						 | 
					46f311678c | ||
| 
						 | 
					795f21baba | ||
| 
						 | 
					d6037e6810 | ||
| 
						 | 
					676de4c854 | ||
| 
						 | 
					db52a6dbc0 | ||
| 
						 | 
					a1547c1403 | ||
| 
						 | 
					11b2a61ba5 | ||
| 
						 | 
					88a3810199 | ||
| 
						 | 
					ec692851d4 | ||
| 
						 | 
					558d67204b | ||
| 
						 | 
					9b35c413cc | ||
| 
						 | 
					8f1ce3b6ea | ||
| 
						 | 
					e4bf2292b4 | ||
| 
						 | 
					9495910261 | ||
| 
						 | 
					bc439c22f7 | ||
| 
						 | 
					b0f9cd2694 | ||
| 
						 | 
					18d3dda63e | ||
| 
						 | 
					41913e7dad | ||
| 
						 | 
					c9c7b15d9b | ||
| 
						 | 
					15e95d6e07 | ||
| 
						 | 
					b0f826dab6 | ||
| 
						 | 
					56a0daf635 | ||
| 
						 | 
					98cbd5fd02 | ||
| 
						 | 
					5f3da48cc9 | ||
| 
						 | 
					7ac474a5e7 | ||
| 
						 | 
					e37a376c53 | ||
| 
						 | 
					8ef11c4324 | ||
| 
						 | 
					5c9bfb455d | ||
| 
						 | 
					7fd0d77a95 | ||
| 
						 | 
					1d4c74b2b4 | ||
| 
						 | 
					26d9199ee0 | ||
| 
						 | 
					031e60c72f | 
							
								
								
									
										12
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								.github/FUNDING.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,12 @@
 | 
			
		|||
# These are supported funding model platforms
 | 
			
		||||
 | 
			
		||||
github: # ilyaigpetrov
 | 
			
		||||
patreon: # Replace with a single Patreon username
 | 
			
		||||
open_collective:
 | 
			
		||||
ko_fi: # Replace with a single Ko-fi username
 | 
			
		||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
 | 
			
		||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
 | 
			
		||||
liberapay: # Replace with a single Liberapay username
 | 
			
		||||
issuehunt: # Replace with a single IssueHunt username
 | 
			
		||||
otechie: # Replace with a single Otechie username
 | 
			
		||||
custom: https://github.com/anticensority/runet-censorship-bypass/wiki/Поддержать
 | 
			
		||||
							
								
								
									
										0
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										0
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										
										
										Executable file → Normal file
									
								
							
							
								
								
									
										78
									
								
								README.md
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										78
									
								
								README.md
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| 
						 | 
				
			
			@ -1,35 +1,71 @@
 | 
			
		|||
# [Maintainer Needed! Нужен разработчик!](https://github.com/anticensorship-russia/chromium-extension/issues/2)
 | 
			
		||||
If you __unstar__, please, [leave us a note](https://github.com/anticensority/runet-censorship-bypass/issues) why you do so. 
 | 
			
		||||
 | 
			
		||||
Also, if you __unstar__, please, [leave us a note](https://github.com/anticensorship-russia/chromium-extension/issues) why you do so.
 | 
			
		||||
[d1]: https://img.shields.io/badge/Поддержать-❤-green.svg
 | 
			
		||||
[d2]: https://github.com/anticensority/runet-censorship-bypass/wiki/Поддержать
 | 
			
		||||
 | 
			
		||||
# Russian Anti-Censorship on PAC-Scripts
 | 
			
		||||
[![Поддержать][d1]][d2]
 | 
			
		||||
[](#backers)
 | 
			
		||||
 [](#sponsors) 
 | 
			
		||||
 | 
			
		||||
This repo contains:
 | 
			
		||||
# Russian Anticensorship on PAC-Scripts
 | 
			
		||||
 | 
			
		||||
1. Chrome Extension to bypass censorship in Russia:
 | 
			
		||||
[WebStore](https://chrome.google.com/webstore/detail/npgcnondjocldhldegnakemclmfkngch)
 | 
			
		||||
| [Sources](https://github.com/ilyaigpetrov/anti-censorship-russia/tree/master/extensions/chromium/minimalistic-pac-setter)
 | 
			
		||||
2. Proof of concept PAC-script generator based on https://github.com/zapret-info/z-i
 | 
			
		||||
3. ~~PAC-scripts performance analyses of scripts generated~~ (doesn't take parse time into account)
 | 
			
		||||
4. Based on the research of step 3 [the final PAC-generator][pac-generator] was written as a Google App Script in JavaScript which is triggered every two hours to generate and publish PAC-script on Google Drive (don't use direct URL without extension, please, URL will be periodically changed to counter abuse).
 | 
			
		||||
This repo contains an extension for Chromium and FireFox that helps to bypass censorship in Russia: [WebStore](https://chrome.google.com/webstore/detail/npgcnondjocldhldegnakemclmfkngch)
 | 
			
		||||
| [Sources](./extensions/chromium/runet-censorship-bypass).  
 | 
			
		||||
This extension uses pac scripts, one of which (anticensority) is generated by this [pac-generator].
 | 
			
		||||
 | 
			
		||||
[pac-generator]: https://script.google.com/d/1RlqqfUmYNpEhekySfOqdzJ8L4eV1GsHYDjPD1DexxEW0RcGvuCSQlWa0/edit?usp=sharing
 | 
			
		||||
[pac-generator]: https://github.com/anticensority/pac-script-generator
 | 
			
		||||
 | 
			
		||||
## Install / Установка
 | 
			
		||||
 | 
			
		||||
1. [Chrome Web Store](https://chrome.google.com/webstore/detail/обход-блокировок-рунета/npgcnondjocldhldegnakemclmfkngch)
 | 
			
		||||
2. [Chrome Web Store (MINI)](https://chrome.google.com/webstore/detail/обход-блокировок-рунета-м/gnknjnebjldmkpmlhjipalimhjofpgho)
 | 
			
		||||
3. [Microsoft Edge Add-ons](https://microsoftedge.microsoft.com/addons/detail/обход-блокировок-рунета/ajgpnodjpffiagcfmifildjpoaeiobfh)
 | 
			
		||||
4. [Microsoft Edge Add-ons (MINI)](https://microsoftedge.microsoft.com/addons/detail/обход-блокировок-рунета-м/cjppllmpmkpjfchbaoebeneghcbmlibj)
 | 
			
		||||
5. [FireFox Add-ons](https://addons.mozilla.org/ru/firefox/addon/обход-блокировок-рунета/).
 | 
			
		||||
6. Opera: сначала [установщик расширений из WebStore](https://addons.opera.com/ru/extensions/details/install-chrome-extensions/) (от команды Opera), затем см. пункты 1 и 2 выше. 
 | 
			
		||||
7. Пакеты для автономной (offline) установки / Packages for offline installation: https://github.com/anticensority/runet-censorship-bypass/releases.
 | 
			
		||||
 | 
			
		||||
Из-за блокировок адресов Google расширение может не устанавливаться из WebStore. Подробности и способы установки см. https://github.com/anticensority/runet-censorship-bypass/wiki/Автономная-установка-расширения.
 | 
			
		||||
 | 
			
		||||
## Why I do This
 | 
			
		||||
 | 
			
		||||
I believe __information mustn't be blocked based on political or other subjective views__.  
 | 
			
		||||
See [my arguments against censorship (ru)](https://github.com/anticensority/runet-censorship-bypass/wiki/Почему-мы-это-делаем%3F-Аргументы-против-цензуры).
 | 
			
		||||
 | 
			
		||||
My maxim is _"Your freedom ends when it starts to confine the freedom of others"_.
 | 
			
		||||
 | 
			
		||||
See [my other arguments against censorship (ru)](https://gist.github.com/ilyaigpetrov/9452b93ef3d7dd3d8cc2)
 | 
			
		||||
 | 
			
		||||
Looking at how Russian government [distorts TV](https://therussianreader.wordpress.com/2015/11/22/russian-truckers-strike-dagestan/) and blocks [critics of Putin](http://www.reuters.com/article/2014/03/13/us-russia-internet-idUSBREA2C21L20140313),
 | 
			
		||||
Looking at how Russian government [distorts TV](https://therussianreader.wordpress.com/2015/11/22/russian-truckers-strike-dagestan/) and blocks [critics of Putin](https://www.reuters.com/article/us-russia-internet-idUSBREA2C21L20140313),
 | 
			
		||||
I decided to write an anti-censorship extension for Chromium before they strike me first.
 | 
			
		||||
 | 
			
		||||
## How it Works
 | 
			
		||||
 | 
			
		||||
0. PAC script is a JavaScript file, triggered on every URL request, which says browser which proxy to use if any for this particular URL.
 | 
			
		||||
1. The Chrome Extension sets PAC script in browser settings and keeps it synced with PAC script on the server (offering Antizapret (hosted on a dedicated server) or Anticensority (hosted on GitHub + CloudFlare)).
 | 
			
		||||
2. On every request PAC script checks if host is blocked or if its IP is blocked.
 | 
			
		||||
3. If address is blocked PAC script returns proxy server to the browser, both Antizapret and Anticensority use Antizapret proxy servers.
 | 
			
		||||
0. PAC script is a JavaScript file, triggered on every URL request, which tells the browser which proxy to use if any for this particular URL.
 | 
			
		||||
1. The Chrome Extension sets the PAC-script in browser settings and keeps it synced with the PAC script on the server. It offers Antizapret (hosted on a dedicated server) or Anticensority (hosted on GitHub) built-in PAC scripts for the user choice.
 | 
			
		||||
2. On every request the PAC script checks if the host is blocked or if its IP is blocked.
 | 
			
		||||
3. If an address is blocked, the PAC script returns the proxy server to the browser. The Antizapret PAC script uses its own proxy servers and the Anticensority PAC-script uses local Tor.
 | 
			
		||||
4. PAC scripts on servers are updated periodically from https://github.com/zapret-info/z-i.
 | 
			
		||||
 | 
			
		||||
## Contributors
 | 
			
		||||
 | 
			
		||||
This project exists thanks to all the people who contribute.
 | 
			
		||||
<a href="https://github.com/anticensority/runet-censorship-bypass/graphs/contributors"><img src="https://opencollective.com/anticensority/contributors.svg?width=890&button=false?force" /></a>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Backers
 | 
			
		||||
 | 
			
		||||
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/anticensority#backer)]
 | 
			
		||||
 | 
			
		||||
<a href="https://opencollective.com/anticensority#backers" target="_blank"><img src="https://opencollective.com/anticensority/backers.svg?width=890"></a>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Sponsors
 | 
			
		||||
 | 
			
		||||
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/anticensority#sponsor)]
 | 
			
		||||
 | 
			
		||||
<a href="https://opencollective.com/anticensority/sponsor/0/website" target="_blank"><img src="https://opencollective.com/anticensority/sponsor/0/avatar.svg"></a>
 | 
			
		||||
<a href="https://opencollective.com/anticensority/sponsor/1/website" target="_blank"><img src="https://opencollective.com/anticensority/sponsor/1/avatar.svg"></a>
 | 
			
		||||
<a href="https://opencollective.com/anticensority/sponsor/2/website" target="_blank"><img src="https://opencollective.com/anticensority/sponsor/2/avatar.svg"></a>
 | 
			
		||||
<a href="https://opencollective.com/anticensority/sponsor/3/website" target="_blank"><img src="https://opencollective.com/anticensority/sponsor/3/avatar.svg"></a>
 | 
			
		||||
<a href="https://opencollective.com/anticensority/sponsor/4/website" target="_blank"><img src="https://opencollective.com/anticensority/sponsor/4/avatar.svg"></a>
 | 
			
		||||
<a href="https://opencollective.com/anticensority/sponsor/5/website" target="_blank"><img src="https://opencollective.com/anticensority/sponsor/5/avatar.svg"></a>
 | 
			
		||||
<a href="https://opencollective.com/anticensority/sponsor/6/website" target="_blank"><img src="https://opencollective.com/anticensority/sponsor/6/avatar.svg"></a>
 | 
			
		||||
<a href="https://opencollective.com/anticensority/sponsor/7/website" target="_blank"><img src="https://opencollective.com/anticensority/sponsor/7/avatar.svg"></a>
 | 
			
		||||
<a href="https://opencollective.com/anticensority/sponsor/8/website" target="_blank"><img src="https://opencollective.com/anticensority/sponsor/8/avatar.svg"></a>
 | 
			
		||||
<a href="https://opencollective.com/anticensority/sponsor/9/website" target="_blank"><img src="https://opencollective.com/anticensority/sponsor/9/avatar.svg"></a>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,30 +1,33 @@
 | 
			
		|||
# Dev
 | 
			
		||||
# Install
 | 
			
		||||
 | 
			
		||||
Linting JS: `npm run lint`
 | 
			
		||||
Tested on:
 | 
			
		||||
 | 
			
		||||
# О расширении
 | 
			
		||||
NodeJS: v16.1.0.
 | 
			
		||||
NPM: 7.11.2.
 | 
			
		||||
OS: Linux Mint 20 Xfce Edition.
 | 
			
		||||
 | 
			
		||||
Обход интернет-цензуры в России пока что не является преступлением.
 | 
			
		||||
```
 | 
			
		||||
npm install
 | 
			
		||||
cd src/extension-common/pages/options/
 | 
			
		||||
npm install
 | 
			
		||||
cd -
 | 
			
		||||
 | 
			
		||||
Расширение позволяет обходить блокировки РосКомНадзора, давая вам доступ
 | 
			
		||||
к библиотекам, энциклопедиям, сайтам оппозиционеров, а также к неповинным
 | 
			
		||||
сайтам, случайно заблокированным в силу разных причин.
 | 
			
		||||
# For debugging:
 | 
			
		||||
npm start
 | 
			
		||||
# Use your build/extension-beta
 | 
			
		||||
 | 
			
		||||
Проксирует только заблокированные сайты, оставляя нетронутыми все остальные.
 | 
			
		||||
# For production:
 | 
			
		||||
npm start
 | 
			
		||||
# Use your build/extension-full or build/extension-mini
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Устанавливает PAC-скрипт, работающий через сервера anticenz.org и antizapret.prostovpn.org.
 | 
			
		||||
# For Reviewers
 | 
			
		||||
 | 
			
		||||
Обновляет PAC-скрипт каждые 4 часа, что составляет примерно 7MB трафика в сутки.
 | 
			
		||||
Также расширение постоянно потребляет ~15MB памяти для информирования о блокировках через иконку.
 | 
			
		||||
See ./src/extension-common/FOR_REVIEWERS.md.
 | 
			
		||||
 | 
			
		||||
Синяя лента – кампания фонда EFF в защиту свобод слова, прессы и союзов.
 | 
			
		||||
# Release Instructions
 | 
			
		||||
 | 
			
		||||
Если расширение не работает: https://git.io/vgDDj
 | 
			
		||||
 | 
			
		||||
Антицензура на Реддите: https://www.reddit.com/r/anticensorship_russia
 | 
			
		||||
Группа в G+: https://goo.gl/Lh0Cjh
 | 
			
		||||
История изменений: https://github.com/ilyaigpetrov/anti-censorship-russia/releases
 | 
			
		||||
 | 
			
		||||
# Дополнительно
 | 
			
		||||
 | 
			
		||||
Иконка синей ленты: http://www.iconsdb.com/icon-sets/cardboard-blue-icons/ribbon-12-icon.html
 | 
			
		||||
1. `npm run release`
 | 
			
		||||
2. `vim src/templates-data.js` and bump version.
 | 
			
		||||
3. Commit bumped version.
 | 
			
		||||
4. Merge development to production (usually after deployment and testing and many patches).
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										25
									
								
								extensions/chromium/runet-censorship-bypass/description.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								extensions/chromium/runet-censorship-bypass/description.md
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,25 @@
 | 
			
		|||
# О расширении
 | 
			
		||||
 | 
			
		||||
Обход интернет-цензуры в России пока что не является преступлением.
 | 
			
		||||
 | 
			
		||||
Расширение позволяет обходить блокировки РосКомНадзора, давая вам доступ
 | 
			
		||||
к библиотекам, энциклопедиям, сайтам оппозиционеров, а также к неповинным
 | 
			
		||||
сайтам, случайно заблокированным в силу разных причин.
 | 
			
		||||
 | 
			
		||||
Проксирует только заблокированные сайты, оставляя нетронутыми все остальные.
 | 
			
		||||
 | 
			
		||||
Устанавливает PAC-скрипт, работающий через сервера anticenz.org и antizapret.prostovpn.org.
 | 
			
		||||
 | 
			
		||||
Обновляет PAC-скрипт каждые 4 часа, что составляет примерно 7MB трафика в сутки.
 | 
			
		||||
Также расширение постоянно потребляет ~15MB памяти для информирования о блокировках через иконку.
 | 
			
		||||
 | 
			
		||||
Синяя лента – кампания фонда EFF в защиту свобод слова, прессы и союзов.
 | 
			
		||||
 | 
			
		||||
Если расширение не работает: https://git.io/vgDDj
 | 
			
		||||
 | 
			
		||||
Антицензура на Реддите: https://www.reddit.com/r/anticensorship_russia
 | 
			
		||||
История изменений: https://github.com/anticensority/runet-censorship-bypass/releases
 | 
			
		||||
 | 
			
		||||
# Дополнительно
 | 
			
		||||
 | 
			
		||||
Иконка синей ленты: http://www.iconsdb.com/icon-sets/cardboard-blue-icons/ribbon-12-icon.html
 | 
			
		||||
| 
						 | 
				
			
			@ -1 +1 @@
 | 
			
		|||
grep -r $@ ./src --exclude-dir=vendor --exclude-dir=node_modules
 | 
			
		||||
grep -r "$@" ./*.js ./src --exclude-dir=vendor --exclude-dir=node_modules --exclude-dir=dist
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@
 | 
			
		|||
const gulp = require('gulp');
 | 
			
		||||
const del = require('del');
 | 
			
		||||
const through = require('through2');
 | 
			
		||||
const PluginError = require('gulp-util').PluginError;
 | 
			
		||||
const PluginError = require('plugin-error');
 | 
			
		||||
const changed = require('gulp-changed');
 | 
			
		||||
 | 
			
		||||
const PluginName = 'Template literals';
 | 
			
		||||
| 
						 | 
				
			
			@ -29,7 +29,7 @@ const templatePlugin = (context) => through.obj(function(file, encoding, cb) {
 | 
			
		|||
 | 
			
		||||
      }, { keys: [], values: [] });
 | 
			
		||||
      try {
 | 
			
		||||
        file.contents = new Buffer(
 | 
			
		||||
        file.contents = Buffer.from(
 | 
			
		||||
          (new Function(...keys, 'return `' + String(file.contents) + '`;'))(...values)
 | 
			
		||||
        );
 | 
			
		||||
      } catch(e) {
 | 
			
		||||
| 
						 | 
				
			
			@ -43,81 +43,65 @@ const templatePlugin = (context) => through.obj(function(file, encoding, cb) {
 | 
			
		|||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
gulp.task('default', ['build']);
 | 
			
		||||
 | 
			
		||||
gulp.task('clean', function(cb) {
 | 
			
		||||
const clean = function(cb) {
 | 
			
		||||
 | 
			
		||||
  //return del.sync('./build');
 | 
			
		||||
  del.sync('./build');
 | 
			
		||||
  return cb();
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const contexts = require('./src/templates-data').contexts;
 | 
			
		||||
 | 
			
		||||
const excFolder = (name) => [`!./src/**/${name}`, `!./src/**/${name}/**/*`];
 | 
			
		||||
const excluded = [ ...excFolder('test') , ...excFolder('node_modules'), ...excFolder('src') ];
 | 
			
		||||
const commonWoTests = ['./src/extension-common/**/*', ...excluded];
 | 
			
		||||
 | 
			
		||||
const miniDst = './build/extension-mini';
 | 
			
		||||
const fullDst = './build/extension-full';
 | 
			
		||||
const betaDst = './build/extension-beta';
 | 
			
		||||
const firefoxDst = './build/extension-firefox';
 | 
			
		||||
 | 
			
		||||
gulp.task('_cp-common', ['clean'], function(cb) {
 | 
			
		||||
const commonSrc = './src/extension-common/**/*';;
 | 
			
		||||
const miniSrc = './src/extension-mini/**/*';
 | 
			
		||||
const fullSrc = './src/extension-full/**/*';
 | 
			
		||||
const firefoxSrc = './src/extension-firefox/**/*';
 | 
			
		||||
 | 
			
		||||
  let fins = 0;
 | 
			
		||||
  const intheend = () => {
 | 
			
		||||
    if (++fins === 2) {
 | 
			
		||||
      cb();
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
const joinSrc = (...args) => [...args, ...excluded];
 | 
			
		||||
 | 
			
		||||
  gulp.src(commonWoTests)
 | 
			
		||||
    //.pipe(changed(miniDst))
 | 
			
		||||
    .pipe(templatePlugin(contexts.mini))
 | 
			
		||||
    .pipe(gulp.dest(miniDst))
 | 
			
		||||
    .on('end', intheend);
 | 
			
		||||
const copyMini = function(cb) {
 | 
			
		||||
 | 
			
		||||
  gulp.src(commonWoTests)
 | 
			
		||||
    //.pipe(changed(fullDst))
 | 
			
		||||
    .pipe(templatePlugin(contexts.full))
 | 
			
		||||
    .pipe(gulp.dest(fullDst))
 | 
			
		||||
    .on('end', intheend);
 | 
			
		||||
 | 
			
		||||
  gulp.src(commonWoTests)
 | 
			
		||||
    //.pipe(changed(fullDst))
 | 
			
		||||
    .pipe(templatePlugin(contexts.beta))
 | 
			
		||||
    .pipe(gulp.dest(betaDst))
 | 
			
		||||
    .on('end', intheend);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
gulp.task('_cp-mini', ['_cp-common'], function(cb) {
 | 
			
		||||
 | 
			
		||||
  gulp.src(['./src/extension-mini/**/*', ...excluded])
 | 
			
		||||
  gulp.src(joinSrc(commonSrc, miniSrc))
 | 
			
		||||
    //.pipe(changed(miniDst))
 | 
			
		||||
    .pipe(templatePlugin(contexts.mini))
 | 
			
		||||
    .pipe(gulp.dest(miniDst))
 | 
			
		||||
    .on('end', cb);
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task('_cp-full', ['_cp-common'], function(cb) {
 | 
			
		||||
const copyFull = function(cb) {
 | 
			
		||||
 | 
			
		||||
  gulp.src(['./src/extension-full/**/*', ...excluded])
 | 
			
		||||
  gulp.src(joinSrc(commonSrc, fullSrc))
 | 
			
		||||
    //.pipe(changed(fullDst))
 | 
			
		||||
    .pipe(templatePlugin(contexts.full))
 | 
			
		||||
    .pipe(gulp.dest(fullDst))
 | 
			
		||||
    .on('end', cb);
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task('_cp-beta', ['_cp-common'], function(cb) {
 | 
			
		||||
const copyBeta = function(cb) {
 | 
			
		||||
 | 
			
		||||
  gulp.src(['./src/extension-full/**/*', ...excluded])
 | 
			
		||||
    gulp.src(joinSrc(commonSrc, fullSrc))
 | 
			
		||||
    //.pipe(changed(fullDst))
 | 
			
		||||
    .pipe(templatePlugin(contexts.beta))
 | 
			
		||||
    .pipe(gulp.dest(betaDst))
 | 
			
		||||
    .on('end', cb);
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
gulp.task('build:all', ['_cp-mini', '_cp-full', '_cp-beta']);
 | 
			
		||||
gulp.task('build', ['_cp-full']);
 | 
			
		||||
const buildAll = gulp.series(clean, gulp.parallel(copyMini, copyFull, copyBeta));
 | 
			
		||||
const buildBeta = copyBeta;
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
  default: buildAll,
 | 
			
		||||
  buildAll,
 | 
			
		||||
  buildBeta,
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										11476
									
								
								extensions/chromium/runet-censorship-bypass/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										11476
									
								
								extensions/chromium/runet-censorship-bypass/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -3,27 +3,29 @@
 | 
			
		|||
  "version": "0.0.19",
 | 
			
		||||
  "description": "Development tools for chromium extension",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "postinstall": "node --use_strict -e \"const fs = require('fs'), path = 'node_modules/_project-root'; fs.unlink(path, ()=> fs.symlinkSync('..', path, 'dir'));\"",
 | 
			
		||||
    "lint": "eslint ./src/**/*.js --ignore-pattern vendor",
 | 
			
		||||
    "gulp": "gulp",
 | 
			
		||||
    "test": "mocha --recursive ./src/**/test/*",
 | 
			
		||||
    "subpages": "cd ./src/extension-common/pages/options/ && npm run build && cd -",
 | 
			
		||||
    "start": "npm run subpages && npm run gulp",
 | 
			
		||||
    "beta": "npm run subpages && npm run gulp build:all"
 | 
			
		||||
    "subpages:dev": "cd ./src/extension-common/pages/options/ && npm run build:dev:nocomp && cd -",
 | 
			
		||||
    "start": "npm run release",
 | 
			
		||||
    "release": "npm run subpages && npx gulp -- buildAll"
 | 
			
		||||
  },
 | 
			
		||||
  "author": "Ilya Ig. Petrov",
 | 
			
		||||
  "license": "GPLv3",
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "chai": "^3.5.0",
 | 
			
		||||
    "eslint": "^3.15.0",
 | 
			
		||||
    "eslint-config-google": "^0.7.1",
 | 
			
		||||
    "gulp-changed": "^3.1.0",
 | 
			
		||||
    "mocha": "^3.3.0",
 | 
			
		||||
    "sinon-chrome": "^2.2.1"
 | 
			
		||||
    "chai": "^4.3.0",
 | 
			
		||||
    "eslint": "^7.19.0",
 | 
			
		||||
    "eslint-config-google": "^0.14.0",
 | 
			
		||||
    "gulp-changed": "^4.0.2",
 | 
			
		||||
    "mocha": "^8.2.1",
 | 
			
		||||
    "sinon-chrome": "^3.0.1",
 | 
			
		||||
    "symlink-to": "^0.0.4"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "del": "^2.2.2",
 | 
			
		||||
    "gulp": "^3.9.1",
 | 
			
		||||
    "through2": "^2.0.3"
 | 
			
		||||
    "del": "^6.0.0",
 | 
			
		||||
    "gulp": "^4.0.2",
 | 
			
		||||
    "plugin-error": "^1.0.1",
 | 
			
		||||
    "through2": "^4.0.2"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,7 @@
 | 
			
		|||
'use strict';
 | 
			
		||||
 | 
			
		||||
console.log('Extension started.');
 | 
			
		||||
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
  const IF_DEBUG = true;
 | 
			
		||||
| 
						 | 
				
			
			@ -39,15 +41,18 @@
 | 
			
		|||
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    lastError: undefined,
 | 
			
		||||
 | 
			
		||||
    checkChromeError() {
 | 
			
		||||
 | 
			
		||||
      // Chrome API calls your cb in a context different from the point of API
 | 
			
		||||
      // method invokation.
 | 
			
		||||
      const err = chrome.runtime.lastError || chrome.extension.lastError;
 | 
			
		||||
      const err = chrome.runtime.lastError || chrome.extension.lastError || self.lastError;
 | 
			
		||||
      if (!err) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      console.warn('API returned error:', err);
 | 
			
		||||
      delete self.lastError;
 | 
			
		||||
      return new Error(err.message); // Add stack.
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -71,6 +76,19 @@
 | 
			
		|||
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    getOrDie(cb = self.mandatory()) {
 | 
			
		||||
 | 
			
		||||
      return self.chromified((err, ...args) => {
 | 
			
		||||
 | 
			
		||||
        if (err) {
 | 
			
		||||
          throw err;
 | 
			
		||||
        }
 | 
			
		||||
        cb(...args);
 | 
			
		||||
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    getProp(obj, path = self.mandatory()) {
 | 
			
		||||
 | 
			
		||||
      const props = path.split('.');
 | 
			
		||||
| 
						 | 
				
			
			@ -140,6 +158,32 @@
 | 
			
		|||
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    promisedLocalStorage: {
 | 
			
		||||
      get(key) {
 | 
			
		||||
        return new Promise((resolve) => (
 | 
			
		||||
          chrome.storage.local.get(
 | 
			
		||||
            key,
 | 
			
		||||
            window.utils.getOrDie((storage) => resolve(key ? storage[key] : storage)),
 | 
			
		||||
          )
 | 
			
		||||
        ));
 | 
			
		||||
      },
 | 
			
		||||
      set(items) {
 | 
			
		||||
        return new Promise((resolve) => (
 | 
			
		||||
          chrome.storage.local.set(items, resolve)
 | 
			
		||||
        ));
 | 
			
		||||
      },
 | 
			
		||||
      remove(keys) {
 | 
			
		||||
        return new Promise((resolve) => (
 | 
			
		||||
          chrome.storage.local.remove(keys, resolve)
 | 
			
		||||
        ));
 | 
			
		||||
      },
 | 
			
		||||
      clear() {
 | 
			
		||||
        return new Promise((resolve) => (
 | 
			
		||||
          chrome.storage.local.clear(resolve)
 | 
			
		||||
        ));
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    * Possible values for levelOfControl:
 | 
			
		||||
    *
 | 
			
		||||
| 
						 | 
				
			
			@ -168,7 +212,7 @@
 | 
			
		|||
 | 
			
		||||
      searchSettingsForUrl(niddle) {
 | 
			
		||||
 | 
			
		||||
        return 'chrome://settings/search#' + (chrome.i18n.getMessage(niddle) || niddle);
 | 
			
		||||
        return 'chrome://settings/?search=' + (chrome.i18n.getMessage(niddle) || niddle);
 | 
			
		||||
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -176,19 +220,86 @@
 | 
			
		|||
 | 
			
		||||
        return chrome.i18n.getMessage('noControl') +
 | 
			
		||||
          ` <a href="${ this.searchSettingsForUrl('proxy') }">
 | 
			
		||||
            ${ chrome.i18n.getMessage('which') }
 | 
			
		||||
            ${ chrome.i18n.getMessage('WhichQ') }
 | 
			
		||||
          </a>`;
 | 
			
		||||
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    parseProxyScheme(proxyAsStringRaw) {
 | 
			
		||||
 | 
			
		||||
      const proxyAsString = proxyAsStringRaw.trim();
 | 
			
		||||
      const [type] = proxyAsString.split(/\s+/);
 | 
			
		||||
      /*
 | 
			
		||||
      if (!/^[a-zA-Z0-9]+$/.test(type)) {
 | 
			
		||||
        throw new Error(`${type} is not a proxy type!`);
 | 
			
		||||
      }
 | 
			
		||||
        JS has no code blocks in RE, seems safe to omit this check.
 | 
			
		||||
      */
 | 
			
		||||
      const typeRe = new RegExp(`^${type}\\s+`, 'g');
 | 
			
		||||
      const crededAddr = proxyAsString.replace(typeRe, '');
 | 
			
		||||
 | 
			
		||||
      let parts;
 | 
			
		||||
      parts = crededAddr.split('@');
 | 
			
		||||
      let [creds, addr] = [parts.slice(0, -1).join('@'), parts[parts.length - 1]];
 | 
			
		||||
 | 
			
		||||
      const [hostname, port] = addr.split(':');
 | 
			
		||||
 | 
			
		||||
      parts = creds.split(':')
 | 
			
		||||
      const username = parts[0];
 | 
			
		||||
      const password = parts.slice(1).join(':');
 | 
			
		||||
 | 
			
		||||
      return {
 | 
			
		||||
        type,
 | 
			
		||||
        username,
 | 
			
		||||
        password,
 | 
			
		||||
        hostname,
 | 
			
		||||
        port,
 | 
			
		||||
        creds,
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    openAndFocus(url) {
 | 
			
		||||
 | 
			
		||||
      chrome.tabs.create(
 | 
			
		||||
        {url: url},
 | 
			
		||||
        (tab) => chrome.windows.update(tab.windowId, {focused: true})
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const max = 2**16;
 | 
			
		||||
  const versionToArray = (v) => [ ...v.split('.'), 0, 0, 0].slice(0,4);
 | 
			
		||||
  const versionToInt = (v) => versionToArray(v)
 | 
			
		||||
    .reverse()
 | 
			
		||||
    .reduce((acc, vv, i) => acc + parseInt(vv)*(max**i), 0);
 | 
			
		||||
 | 
			
		||||
  const compareVersions = (a, b) => versionToInt(a) - versionToInt(b);
 | 
			
		||||
 | 
			
		||||
  const ifFirefox = navigator.userAgent.toLowerCase().includes('firefox');
 | 
			
		||||
 | 
			
		||||
  let give;
 | 
			
		||||
  const promise = !ifFirefox ? Promise.resolve() : new Promise((resolve) => {
 | 
			
		||||
    give = resolve;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  window.apis = {
 | 
			
		||||
    consent: {
 | 
			
		||||
      promise,
 | 
			
		||||
      give,
 | 
			
		||||
    },
 | 
			
		||||
    platform: {
 | 
			
		||||
      ifFirefox,
 | 
			
		||||
    },
 | 
			
		||||
    version: {
 | 
			
		||||
      ifMini: false,
 | 
			
		||||
      build: chrome.runtime.getManifest().version.replace(/\d+\.\d+\./g, ''),
 | 
			
		||||
 | 
			
		||||
      isLeq: (a, b) => compareVersions(a, b) <= 0,
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,6 +8,9 @@
 | 
			
		|||
  const errorJsonReplacer = function errorJsonReplacer(key, value) {
 | 
			
		||||
 | 
			
		||||
    // fooWindow.ErrorEvent !== barWindow.ErrorEvent
 | 
			
		||||
    if (value === window) {
 | 
			
		||||
      return; // STUPID, because other window object may be passed.
 | 
			
		||||
    }
 | 
			
		||||
    if (!( value && value.constructor
 | 
			
		||||
      && ['Error', 'Event'].some(
 | 
			
		||||
        (suff) => value.constructor.name.endsWith(suff)
 | 
			
		||||
| 
						 | 
				
			
			@ -50,15 +53,6 @@
 | 
			
		|||
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const openAndFocus = function openAndFocus(url) {
 | 
			
		||||
 | 
			
		||||
    chrome.tabs.create(
 | 
			
		||||
      {url: url},
 | 
			
		||||
      (tab) => chrome.windows.update(tab.windowId, {focused: true})
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const ifPrefix = 'if-on-';
 | 
			
		||||
  const extName = chrome.runtime.getManifest().name;
 | 
			
		||||
  const extVersion = window.apis.version.build;
 | 
			
		||||
| 
						 | 
				
			
			@ -72,10 +66,12 @@
 | 
			
		|||
      const errors = err ? {[type]: err} : this.idToError;
 | 
			
		||||
      const json = JSON.stringify(errors, errorJsonReplacer, 0);
 | 
			
		||||
 | 
			
		||||
      openAndFocus(
 | 
			
		||||
        'http://rebrand.ly/ac-error/?json=' + encodeURIComponent(json) +
 | 
			
		||||
      window.utils.openAndFocus(
 | 
			
		||||
        'https://anticensority.github.io/error/?json=' + encodeURIComponent(json) +
 | 
			
		||||
          (type ? '&type=' + encodeURIComponent(type) : '') +
 | 
			
		||||
          '&version=' + chrome.runtime.getManifest().version
 | 
			
		||||
          '&version=' + chrome.runtime.getManifest().version +
 | 
			
		||||
          '&useragent=' + encodeURIComponent(navigator.userAgent) +
 | 
			
		||||
          '&platform=' + encodeURIComponent(navigator.platform),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -114,20 +110,22 @@
 | 
			
		|||
 | 
			
		||||
    isControllable(details) {
 | 
			
		||||
 | 
			
		||||
      this.ifControllable = window.utils.areSettingsControllableFor(details);
 | 
			
		||||
      if (details) {
 | 
			
		||||
        this.ifControllable = window.utils.areSettingsControllableFor(details);
 | 
			
		||||
 | 
			
		||||
      if (this.ifControllable) {
 | 
			
		||||
        this.ifControlled = window.utils.areSettingsControlledFor(details);
 | 
			
		||||
      } else {
 | 
			
		||||
        this.ifControlled = false;
 | 
			
		||||
      }
 | 
			
		||||
        if (this.ifControllable) {
 | 
			
		||||
          this.ifControlled = window.utils.areSettingsControlledFor(details);
 | 
			
		||||
        } else {
 | 
			
		||||
          this.ifControlled = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
      if (this.ifControlled) {
 | 
			
		||||
        chrome.browserAction.setIcon( {path: './icons/default-128.png'} );
 | 
			
		||||
      } else {
 | 
			
		||||
        chrome.browserAction.setIcon({
 | 
			
		||||
          path: './icons/default-grayscale-128.png',
 | 
			
		||||
        });
 | 
			
		||||
        if (this.ifControlled) {
 | 
			
		||||
          chrome.browserAction.setIcon( {path: './icons/default-128.png'} );
 | 
			
		||||
        } else {
 | 
			
		||||
          chrome.browserAction.setIcon({
 | 
			
		||||
            path: './icons/default-grayscale-128.png',
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return this.ifControllable;
 | 
			
		||||
| 
						 | 
				
			
			@ -136,7 +134,9 @@
 | 
			
		|||
 | 
			
		||||
    isControlled(details) {
 | 
			
		||||
 | 
			
		||||
      this.isControllable(details);
 | 
			
		||||
      if (details) {
 | 
			
		||||
        this.isControllable(details);
 | 
			
		||||
      }
 | 
			
		||||
      return this.ifControlled;
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -148,7 +148,9 @@
 | 
			
		|||
        timeouted(
 | 
			
		||||
          (details) => {
 | 
			
		||||
 | 
			
		||||
            this.isControllable(details);
 | 
			
		||||
            if (details) {
 | 
			
		||||
              this.isControllable(details);
 | 
			
		||||
            }
 | 
			
		||||
            cb();
 | 
			
		||||
 | 
			
		||||
          }
 | 
			
		||||
| 
						 | 
				
			
			@ -175,16 +177,15 @@
 | 
			
		|||
      const message = errOrMessage.message || errOrMessage.toString();
 | 
			
		||||
      chrome.notifications.create(
 | 
			
		||||
        id,
 | 
			
		||||
        {
 | 
			
		||||
        Object.assign({
 | 
			
		||||
          title: title,
 | 
			
		||||
          message: message,
 | 
			
		||||
          contextMessage: context,
 | 
			
		||||
          requireInteraction: ifSticky,
 | 
			
		||||
          type: 'basic',
 | 
			
		||||
          iconUrl: './icons/' + icon,
 | 
			
		||||
          appIconMaskUrl: './icons/default-mask-128.png',
 | 
			
		||||
          isClickable: true,
 | 
			
		||||
        }
 | 
			
		||||
        }, window.apis.platform.ifFirefox ? {} : { requireInteraction: ifSticky }),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -232,7 +233,7 @@
 | 
			
		|||
 | 
			
		||||
    chrome.notifications.clear(notId);
 | 
			
		||||
    if(notId === 'no-control') {
 | 
			
		||||
      return openAndFocus(
 | 
			
		||||
      return window.utils.openAndFocus(
 | 
			
		||||
        window.utils.messages.searchSettingsForUrl('proxy')
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -242,7 +243,7 @@
 | 
			
		|||
 | 
			
		||||
  handlers.installListenersOn(window, 'BG');
 | 
			
		||||
 | 
			
		||||
  chrome.proxy.onProxyError.addListener( timeouted( (details) => {
 | 
			
		||||
  (chrome.proxy.onProxyError || chrome.proxy.onError).addListener( timeouted( (details) => {
 | 
			
		||||
 | 
			
		||||
    if (!handlers.ifControlled) {
 | 
			
		||||
      return;
 | 
			
		||||
| 
						 | 
				
			
			@ -253,7 +254,11 @@
 | 
			
		|||
        error: "net::ERR_PAC_SCRIPT_FAILED",
 | 
			
		||||
        fatal: false,
 | 
			
		||||
    */
 | 
			
		||||
    const ifConFail = details.error === 'net::ERR_PROXY_CONNECTION_FAILED';
 | 
			
		||||
    const ifConFail = [
 | 
			
		||||
      'net::ERR_TUNNEL_CONNECTION_FAILED',
 | 
			
		||||
      'net::ERR_PROXY_CONNECTION_FAILED',
 | 
			
		||||
    ].includes(details.error);
 | 
			
		||||
 | 
			
		||||
    if (ifConFail) {
 | 
			
		||||
      // Happens if you return neither prixies nor "DIRECT".
 | 
			
		||||
      // Ignore it.
 | 
			
		||||
| 
						 | 
				
			
			@ -262,7 +267,7 @@
 | 
			
		|||
    console.warn('PAC ERROR', details);
 | 
			
		||||
    // TOOD: add "view pac script at this line" button.
 | 
			
		||||
    handlers.mayNotify('pac-error', 'Ошибка PAC!',
 | 
			
		||||
      details.error + '\n' + details.details,
 | 
			
		||||
      (details.error || details.message /* Firefox */) + '\n' + details.details,
 | 
			
		||||
      {icon: 'pac-error-128.png'}
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -270,14 +275,14 @@
 | 
			
		|||
 | 
			
		||||
  chrome.proxy.settings.onChange.addListener( timeouted( (details) => {
 | 
			
		||||
 | 
			
		||||
    console.log('Proxy settings changed.', details);
 | 
			
		||||
    console.log('Proxy settings changed:', details.levelOfControl);
 | 
			
		||||
    const noCon = 'no-control';
 | 
			
		||||
    const ifWasControllable = handlers.ifControllable;
 | 
			
		||||
    if ( !handlers.isControllable(details) && ifWasControllable ) {
 | 
			
		||||
      handlers.mayNotify(
 | 
			
		||||
        noCon,
 | 
			
		||||
        chrome.i18n.getMessage('noControl'),
 | 
			
		||||
        chrome.i18n.getMessage('which'),
 | 
			
		||||
        chrome.i18n.getMessage('WhichQ'),
 | 
			
		||||
        {icon: 'no-control-128.png', ifSticky: false}
 | 
			
		||||
      );
 | 
			
		||||
    } else {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -69,7 +69,7 @@
 | 
			
		|||
            );
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          console.log('GETed with success:', url, Date.now() - start);
 | 
			
		||||
          console.log('GETed with success:', url.substr(0, 100), Date.now() - start);
 | 
			
		||||
          textCb();
 | 
			
		||||
 | 
			
		||||
        },
 | 
			
		||||
| 
						 | 
				
			
			@ -78,6 +78,31 @@
 | 
			
		|||
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    head(url, cb = mandatory()) {
 | 
			
		||||
 | 
			
		||||
      const start = Date.now();
 | 
			
		||||
      fetch(url, {cache: 'no-store', method: 'HEAD'}).then(
 | 
			
		||||
        (res) => {
 | 
			
		||||
 | 
			
		||||
          const status = res.status;
 | 
			
		||||
          if ( !( status >= 200 && status < 300 || status === 304 ) ) {
 | 
			
		||||
            return cb(
 | 
			
		||||
              errorsLib.clarify(
 | 
			
		||||
                res,
 | 
			
		||||
                'Получен ответ с неудачным HTTP-кодом ' + status + '.'
 | 
			
		||||
              )
 | 
			
		||||
            );
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          console.log('HEADed with success:', url, Date.now() - start);
 | 
			
		||||
          cb();
 | 
			
		||||
 | 
			
		||||
        },
 | 
			
		||||
        errorsLib.clarifyThen(checkCon, cb)
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,64 @@
 | 
			
		|||
'use strict';
 | 
			
		||||
 | 
			
		||||
if (window.apis.platform.ifFirefox) {
 | 
			
		||||
 | 
			
		||||
  const prefix = 'firefox-only';
 | 
			
		||||
 | 
			
		||||
  const originalSet = chrome.proxy.settings.set.bind( chrome.proxy.settings );
 | 
			
		||||
  chrome.proxy.settings.set = function(details, cb) {
 | 
			
		||||
    const pac = window.utils.getProp(details, 'value.pacScript') || {};
 | 
			
		||||
    if (!(pac && pac.data)) {
 | 
			
		||||
      return originalSet(details, cb);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const blob = new Blob([pac.data], { type : 'application/x-ns-proxy-autoconfig' });
 | 
			
		||||
    const blobUrl = URL.createObjectURL(blob);
 | 
			
		||||
    originalSet({
 | 
			
		||||
      value: {
 | 
			
		||||
        proxyType: 'autoConfig',
 | 
			
		||||
        autoConfigUrl: blobUrl,
 | 
			
		||||
      },
 | 
			
		||||
    }, window.utils.chromified( async (err) => {
 | 
			
		||||
      if (err) {
 | 
			
		||||
        window.utils.lastError = err;
 | 
			
		||||
        cb();
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      await window.utils.promisedLocalStorage.set({ [`${prefix}-pac-data`]: pac.data });
 | 
			
		||||
      cb();
 | 
			
		||||
    }));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const originalGet = chrome.proxy.settings.get.bind( chrome.proxy.settings );
 | 
			
		||||
  chrome.proxy.settings.get = function(details, cb) {
 | 
			
		||||
    originalGet(details, window.utils.chromified(async (err, originalDetails) => {
 | 
			
		||||
      if (err) {
 | 
			
		||||
        window.utils.lastError = err;
 | 
			
		||||
        cb(originalDetails);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      let pacData = await window.utils.promisedLocalStorage.get(`${prefix}-pac-data`);
 | 
			
		||||
      if (!pacData || !Object.keys(pacData).length) {
 | 
			
		||||
        cb(originalDetails);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      cb(Object.assign(
 | 
			
		||||
        originalDetails,
 | 
			
		||||
        {
 | 
			
		||||
          value: {
 | 
			
		||||
            mode: 'pac_script',
 | 
			
		||||
            pacScript: {
 | 
			
		||||
              data: pacData,
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        }
 | 
			
		||||
      ));
 | 
			
		||||
    }));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const originalClear = chrome.proxy.settings.clear.bind( chrome.proxy.settings );
 | 
			
		||||
  chrome.proxy.settings.clear = async function(details, cb) {
 | 
			
		||||
    await window.utils.promisedLocalStorage.remove(`${prefix}-pac-data`);
 | 
			
		||||
    originalClear(details, cb);
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -12,6 +12,57 @@
 | 
			
		|||
  const ifIncontinence = 'if-incontinence';
 | 
			
		||||
  const modsKey = 'mods';
 | 
			
		||||
 | 
			
		||||
  let proxyHostToCredsList = {};
 | 
			
		||||
  const ifAuthSupported = chrome.webRequest && chrome.webRequest.onAuthRequired && !window.apis.version.ifMini;
 | 
			
		||||
  if (ifAuthSupported) {
 | 
			
		||||
 | 
			
		||||
    const requestIdToTries = {};
 | 
			
		||||
 | 
			
		||||
    chrome.webRequest.onAuthRequired.addListener(
 | 
			
		||||
      (details) => {
 | 
			
		||||
 | 
			
		||||
        if (!details.isProxy) {
 | 
			
		||||
          return {};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const proxyHost = `${details.challenger.host}:${details.challenger.port}`;
 | 
			
		||||
        const credsList = proxyHostToCredsList[proxyHost];
 | 
			
		||||
        if (!credsList) {
 | 
			
		||||
          return {}; // No creds found for this proxy.
 | 
			
		||||
        }
 | 
			
		||||
        const requestId = details.requestId;
 | 
			
		||||
        const tries = requestIdToTries[requestId] || 0;
 | 
			
		||||
        if (tries > credsList.length) {
 | 
			
		||||
          return {}; // All creds for this proxy were tried already.
 | 
			
		||||
        }
 | 
			
		||||
        requestIdToTries[requestId] = tries + 1;
 | 
			
		||||
        return {
 | 
			
		||||
          authCredentials: credsList[tries],
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
      },
 | 
			
		||||
      {urls: ['<all_urls>']},
 | 
			
		||||
      ['blocking'],
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const forgetRequestId = (details) => {
 | 
			
		||||
 | 
			
		||||
      delete requestIdToTries[details.requestId];
 | 
			
		||||
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    chrome.webRequest.onCompleted.addListener(
 | 
			
		||||
      forgetRequestId,
 | 
			
		||||
      {urls: ['<all_urls>']},
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    chrome.webRequest.onErrorOccurred.addListener(
 | 
			
		||||
      forgetRequestId,
 | 
			
		||||
      {urls: ['<all_urls>']},
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const getDefaultConfigs = () => ({// Configs user may mutate them and we don't care!
 | 
			
		||||
 | 
			
		||||
    ifProxyHttpsUrlsOnly: {
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +79,7 @@
 | 
			
		|||
    },
 | 
			
		||||
    ifProhibitDns: {
 | 
			
		||||
      dflt: false,
 | 
			
		||||
      label: 'запретить опредление по IP/DNS',
 | 
			
		||||
      label: 'запретить определение по IP/DNS',
 | 
			
		||||
      desc: 'Пытается запретить скрипту использовать DNS, без которого определение блокировки по IP работать не будет (т.е. будет разблокироваться меньше сайтов). Используйте, чтобы получить прирост в производительности или если вам кажется, что мы проксируем слишком много сайтов. Запрет действует только для скрипта, браузер и др.программы продолжат использование DNS.',
 | 
			
		||||
      order: 2,
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -50,12 +101,19 @@
 | 
			
		|||
      dflt: false,
 | 
			
		||||
      category: 'ownProxies',
 | 
			
		||||
      label: 'использовать СВОЙ локальный Tor',
 | 
			
		||||
      desc: 'Установите <a href="https://rebrand.ly/ac-tor">Tor</a> на свой компьютер и используйте его как прокси-сервер. <a href="https://rebrand.ly/ac-tor">ВАЖНО</a>',
 | 
			
		||||
      desc: 'Установите <a href="https://github.com/anticensority/runet-censorship-bypass/wiki/Расширение-и-TOR">Tor</a> на свой компьютер и используйте его как прокси-сервер. <a href="https://github.com/anticensority/runet-censorship-bypass/wiki/Расширение-и-TOR#важно">ВАЖНО</a>.',
 | 
			
		||||
      order: 5,
 | 
			
		||||
    },
 | 
			
		||||
    ifUseLocalWarp: {
 | 
			
		||||
      dflt: false,
 | 
			
		||||
      category: 'ownProxies',
 | 
			
		||||
      label: 'использовать WARP как прокси',
 | 
			
		||||
      desc: 'Использовать СВОЙ локальный CloudFlare WARP (<a href="https://one.one.one.one">https://one.one.one.one</a>) в качестве прокси.',
 | 
			
		||||
      order: 5.5,
 | 
			
		||||
    },
 | 
			
		||||
    exceptions: {
 | 
			
		||||
      category: 'exceptions',
 | 
			
		||||
      dflt: null,
 | 
			
		||||
      category: 'exceptions',
 | 
			
		||||
    },
 | 
			
		||||
    ifMindExceptions: {
 | 
			
		||||
      dflt: true,
 | 
			
		||||
| 
						 | 
				
			
			@ -64,13 +122,31 @@
 | 
			
		|||
      desc: 'Учитывать сайты, добавленные вручную. Только для своих прокси-серверов! Без своих прокси работать не будет.',
 | 
			
		||||
      order: 6,
 | 
			
		||||
    },
 | 
			
		||||
    whitelist: {
 | 
			
		||||
      dflt: [],
 | 
			
		||||
      category: 'exceptions',
 | 
			
		||||
    },
 | 
			
		||||
    ifMindWhitelist: {
 | 
			
		||||
      dflt: false,
 | 
			
		||||
      category: 'exceptions',
 | 
			
		||||
      label: 'Ограничиться только <a href="../exceptions/index.html">белым списком</a>',
 | 
			
		||||
      desc: 'Разрешить расширению работать только с адресами из белого списка.',
 | 
			
		||||
      order: 6.5,
 | 
			
		||||
    },
 | 
			
		||||
    customProxyStringRaw: {
 | 
			
		||||
      dflt: '',
 | 
			
		||||
      category: 'ownProxies',
 | 
			
		||||
      label: 'использовать СВОИ прокси',
 | 
			
		||||
      url: 'https://rebrand.ly/ac-own-proxy',
 | 
			
		||||
      url: 'https://github.com/anticensority/runet-censorship-bypass/wiki/Свои-прокси-в-расширении',
 | 
			
		||||
      order: 7,
 | 
			
		||||
    },
 | 
			
		||||
    ifUseOwnProxiesOnlyForOwnSites: {
 | 
			
		||||
      dflt: false,
 | 
			
		||||
      category: 'ownProxies',
 | 
			
		||||
      label: 'СВОИ прокси только для СВОИХ сайтов',
 | 
			
		||||
      desc: 'Не использовать СВОИ прокси для всех сайтов из PAC-скрипта, а только для добавленных вручную исключений.',
 | 
			
		||||
      order: 7.5,
 | 
			
		||||
    },
 | 
			
		||||
    ifProxyMoreDomains: {
 | 
			
		||||
      ifDisabled: true,
 | 
			
		||||
      dflt: false,
 | 
			
		||||
| 
						 | 
				
			
			@ -79,6 +155,14 @@
 | 
			
		|||
      desc: 'Проксировать особые домены. Необходима поддержка со стороны СВОИХ прокси.',
 | 
			
		||||
      order: 8,
 | 
			
		||||
    },
 | 
			
		||||
    replaceDirectWith: {
 | 
			
		||||
      ifDisabled: true,
 | 
			
		||||
      dflt: false,
 | 
			
		||||
      category: 'ownProxies',
 | 
			
		||||
      label: 'подменять DIRECT на',
 | 
			
		||||
      desc: 'Использовать в PAC-скрипте указанную строку для запросов напрямую (вместо директивы DIRECT). Данная строка не проверяется на требования к шифрованию связи до прокси! Строка должна соответствовать формату возвращаемого значения PAC-скрипта, который подобен <a href="https://github.com/anticensority/runet-censorship-bypass/wiki/Свои-прокси-в-расширении#формат">формату своих прокси</a>.',
 | 
			
		||||
      order: 9,
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -91,16 +175,15 @@
 | 
			
		|||
      return acc;
 | 
			
		||||
 | 
			
		||||
    }, {});
 | 
			
		||||
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const getCurrentConfigs = function getCurrentConfigs() {
 | 
			
		||||
  const getCurrentConfigs = function getCurrentConfigs(ifRaw = false) {
 | 
			
		||||
 | 
			
		||||
    const oldMods = kitchenState(modsKey);
 | 
			
		||||
    /*if (oldMods) {
 | 
			
		||||
    if (ifRaw) {
 | 
			
		||||
      // No migration!
 | 
			
		||||
      return oldMods;
 | 
			
		||||
    }*/
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Client may expect mods.included and mods.excluded!
 | 
			
		||||
    // On first install they are not defined.
 | 
			
		||||
| 
						 | 
				
			
			@ -143,34 +226,73 @@
 | 
			
		|||
      .every((dProp) => {
 | 
			
		||||
 | 
			
		||||
        const ifDflt = (
 | 
			
		||||
          !(dProp in mods) ||
 | 
			
		||||
          Boolean(configs[dProp].dflt) === Boolean(mods[dProp])
 | 
			
		||||
          !(
 | 
			
		||||
            dProp in mods &&
 | 
			
		||||
            Boolean(configs[dProp].dflt) !== Boolean(mods[dProp])
 | 
			
		||||
          )
 | 
			
		||||
        );
 | 
			
		||||
        const ifMods = configs[dProp].ifDfltMods;
 | 
			
		||||
        const ifMods = configs[dProp].ifDfltMods; // If default value implies PAC-script modification.
 | 
			
		||||
        return ifDflt ? !ifMods : ifMods;
 | 
			
		||||
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    const self = {};
 | 
			
		||||
    Object.assign(self, getDefaults(), mods);
 | 
			
		||||
    const gdft = getDefaults();
 | 
			
		||||
    Object.assign(self, gdft, mods);
 | 
			
		||||
    self.ifNoMods = ifNoMods;
 | 
			
		||||
 | 
			
		||||
    let customProxyArray = [];
 | 
			
		||||
    if (self.customProxyStringRaw) {
 | 
			
		||||
      customProxyArray = self.customProxyStringRaw
 | 
			
		||||
        .replace(/#.*$/mg, '') // Strip comments.
 | 
			
		||||
        .split( /(?:[^\S\r\n]*(?:;|\r?\n)+[^\S\r\n]*)+/g )
 | 
			
		||||
        .split( /(?:\s*(?:;\r?\n)+\s*)+/g )
 | 
			
		||||
        .map( (p) => p.trim() )
 | 
			
		||||
        .filter( (p) => p && /\s+/g.test(p) );
 | 
			
		||||
        .filter( (p) => p && /\s+/g.test(p) ); // At least one space is required.
 | 
			
		||||
      if (self.ifUseSecureProxiesOnly) {
 | 
			
		||||
        customProxyArray = customProxyArray.filter( (pStr) => /^HTTPS\s/.test(pStr) );
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (self.ifUseLocalWarp) {
 | 
			
		||||
      self.warpPoints = ['SOCKS5 localhost:40000', 'HTTPS localhost:40000'];
 | 
			
		||||
      customProxyArray.push(...self.warpPoints);
 | 
			
		||||
    }
 | 
			
		||||
    if (self.ifUseLocalTor) {
 | 
			
		||||
      self.torPoints = ['SOCKS5 localhost:9150', 'SOCKS5 localhost:9050'];
 | 
			
		||||
      customProxyArray.push(...self.torPoints);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Hanlde protected proxies in customProxyArray.
 | 
			
		||||
    const protectedProxies = [];
 | 
			
		||||
    customProxyArray = customProxyArray.map((proxyScheme) => {
 | 
			
		||||
 | 
			
		||||
      if (proxyScheme.includes('@')) {
 | 
			
		||||
 | 
			
		||||
        const proxy = window.utils.parseProxyScheme(proxyScheme);
 | 
			
		||||
        protectedProxies.push(proxy);
 | 
			
		||||
        return `${proxy.type} ${proxy.hostname}:${proxy.port}`;
 | 
			
		||||
 | 
			
		||||
      }
 | 
			
		||||
      return proxyScheme;
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (!ifAuthSupported && protectedProxies.length) {
 | 
			
		||||
      return [new Error('Запароленные прокси не поддерживаются в данной версии/платформе!')];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    proxyHostToCredsList = {};
 | 
			
		||||
    protectedProxies.forEach(({ hostname, port, username, password }) => {
 | 
			
		||||
 | 
			
		||||
      proxyHostToCredsList[`${hostname}:${port}`] =
 | 
			
		||||
        proxyHostToCredsList[`${hostname}:${port}`] || [];
 | 
			
		||||
      const tries = proxyHostToCredsList[`${hostname}:${port}`];
 | 
			
		||||
      tries.push({
 | 
			
		||||
        username: username || '',
 | 
			
		||||
        password: password || '',
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    self.filteredCustomsString = '';
 | 
			
		||||
    if (customProxyArray.length) {
 | 
			
		||||
      self.customProxyArray = customProxyArray;
 | 
			
		||||
| 
						 | 
				
			
			@ -198,7 +320,8 @@
 | 
			
		|||
      self.included = [];
 | 
			
		||||
      self.excluded = [];
 | 
			
		||||
      for(const host of Object.keys(self.exceptions)) {
 | 
			
		||||
        if (self.exceptions[host]) {
 | 
			
		||||
        const ifProxy = self.exceptions[host] || false;
 | 
			
		||||
        if (ifProxy) {
 | 
			
		||||
          self.included.push(host);
 | 
			
		||||
        } else {
 | 
			
		||||
          self.excluded.push(host);
 | 
			
		||||
| 
						 | 
				
			
			@ -207,9 +330,9 @@
 | 
			
		|||
      ['included', 'excluded'].forEach((who) => {
 | 
			
		||||
 | 
			
		||||
        self[who] = self[who]
 | 
			
		||||
          .map( (s) => s.split('').reverse() )
 | 
			
		||||
          .map( (domain) => domain.split('').reverse() )
 | 
			
		||||
          .sort()
 | 
			
		||||
          .map( (a) => a.reverse().join('') );
 | 
			
		||||
          .map( (rDomain) => rDomain.reverse().join('') );
 | 
			
		||||
 | 
			
		||||
      });
 | 
			
		||||
      if (self.included.length && !self.filteredCustomsString) {
 | 
			
		||||
| 
						 | 
				
			
			@ -225,29 +348,50 @@
 | 
			
		|||
  window.apis.pacKitchen = {
 | 
			
		||||
 | 
			
		||||
    getPacMods: getCurrentConfigs,
 | 
			
		||||
    getPacModsRaw: () => getCurrentConfigs(true),
 | 
			
		||||
    getOrderedConfigs: getOrderedConfigsForUser,
 | 
			
		||||
 | 
			
		||||
    cook(pacData, pacMods = mandatory()) {
 | 
			
		||||
 | 
			
		||||
      pacData = pacData.replace(
 | 
			
		||||
        new RegExp(kitchenStartsMark + '[\\s\\S]*$', 'g'),
 | 
			
		||||
        ''
 | 
			
		||||
      );
 | 
			
		||||
      /a/.test('a'); // GC RegExp.input and friends.
 | 
			
		||||
 | 
			
		||||
      return pacMods.ifNoMods ? pacData : pacData + `${ kitchenStartsMark }
 | 
			
		||||
/******/
 | 
			
		||||
/******/;+function(global) {
 | 
			
		||||
/******/;(function(global) {
 | 
			
		||||
/******/  "use strict";
 | 
			
		||||
/******/
 | 
			
		||||
/******/  const originalFindProxyForURL = FindProxyForURL;
 | 
			
		||||
/******/  global.FindProxyForURL = function(url, host) {
 | 
			
		||||
/******/
 | 
			
		||||
/******/  let tmp = function(url, host) {
 | 
			
		||||
/******/    const dotHost = '.' + host;
 | 
			
		||||
    ${
 | 
			
		||||
      function() {
 | 
			
		||||
        let generatedPac = `
 | 
			
		||||
/******/  if (${pacMods.ifMindWhitelist && pacMods.whitelist.length}) {
 | 
			
		||||
/******/    const ifWhitelisted =
 | 
			
		||||
/******/      ${JSON.stringify(pacMods.whitelist)}.some((whiteHost) => {
 | 
			
		||||
/******/        const ifWild = whiteHost.startsWith('*');
 | 
			
		||||
/******/          if (ifWild) {
 | 
			
		||||
/******/            return dotHost.endsWith(whiteHost.substr(1));
 | 
			
		||||
/******/          }
 | 
			
		||||
/******/          return host === whiteHost;
 | 
			
		||||
/******/      })
 | 
			
		||||
/******/    if (!ifWhitelisted) {
 | 
			
		||||
/******/      return 'DIRECT';
 | 
			
		||||
/******/    }
 | 
			
		||||
/******/  }`;
 | 
			
		||||
 | 
			
		||||
        let res = pacMods.ifProhibitDns ? `
 | 
			
		||||
        generatedPac += pacMods.ifProhibitDns ? `
 | 
			
		||||
/******/
 | 
			
		||||
/******/    global.dnsResolve = function(host) { return null; };
 | 
			
		||||
/******/
 | 
			
		||||
/******/` : '';
 | 
			
		||||
        if (pacMods.ifProxyHttpsUrlsOnly) {
 | 
			
		||||
 | 
			
		||||
          res += `
 | 
			
		||||
          generatedPac += `
 | 
			
		||||
/******/
 | 
			
		||||
/******/    if (!url.startsWith("https")) {
 | 
			
		||||
/******/      return "DIRECT";
 | 
			
		||||
| 
						 | 
				
			
			@ -257,7 +401,7 @@
 | 
			
		|||
        }
 | 
			
		||||
        if (pacMods.ifUseLocalTor) {
 | 
			
		||||
 | 
			
		||||
          res += `
 | 
			
		||||
          generatedPac += `
 | 
			
		||||
/******/
 | 
			
		||||
/******/    if (host.endsWith(".onion")) {
 | 
			
		||||
/******/      return "${pacMods.torPoints.join('; ')}";
 | 
			
		||||
| 
						 | 
				
			
			@ -265,25 +409,25 @@
 | 
			
		|||
/******/
 | 
			
		||||
/******/  `;
 | 
			
		||||
        }
 | 
			
		||||
        res += `
 | 
			
		||||
        generatedPac += `
 | 
			
		||||
/******/
 | 
			
		||||
/******/    const directIfAllowed = ${pacMods.ifProxyOrDie ? '""/* Not allowed. */' : '"; DIRECT"'};
 | 
			
		||||
/******/    const directIfAllowed = ${pacMods.ifProxyOrDie ? '""/* Not allowed. */' : '"DIRECT"'};
 | 
			
		||||
/******/`;
 | 
			
		||||
        if (pacMods.filteredCustomsString) {
 | 
			
		||||
          res += `
 | 
			
		||||
          generatedPac += `
 | 
			
		||||
/******/
 | 
			
		||||
/******/    const filteredCustomProxies = "; ${pacMods.filteredCustomsString}";
 | 
			
		||||
/******/    const filteredCustomProxies = "${pacMods.filteredCustomsString}";
 | 
			
		||||
/******/`;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const ifIncluded = pacMods.included && pacMods.included.length;
 | 
			
		||||
        const ifExcluded = pacMods.excluded && pacMods.excluded.length;
 | 
			
		||||
        const ifManualExceptions = ifIncluded || ifExcluded;
 | 
			
		||||
        const finalExceptions = {};
 | 
			
		||||
        let finalExceptions = {};
 | 
			
		||||
        if (pacMods.ifProxyMoreDomains) {
 | 
			
		||||
          pacMods.moreDomains.reduce((acc, tld) => {
 | 
			
		||||
          finalExceptions = pacMods.moreDomains.reduce((acc, tld) => {
 | 
			
		||||
 | 
			
		||||
            acc[tld] = true;
 | 
			
		||||
            acc['*.' + tld] = true;
 | 
			
		||||
            return acc;
 | 
			
		||||
 | 
			
		||||
          }, finalExceptions);
 | 
			
		||||
| 
						 | 
				
			
			@ -294,21 +438,33 @@
 | 
			
		|||
        const ifExceptions = Object.keys(finalExceptions).length;
 | 
			
		||||
 | 
			
		||||
        if (ifExceptions) {
 | 
			
		||||
          res += `
 | 
			
		||||
          generatedPac += `
 | 
			
		||||
/******/
 | 
			
		||||
/******/    /* EXCEPTIONS START */
 | 
			
		||||
/******/    const dotHost = '.' + host;
 | 
			
		||||
/******/    const isHostInDomain = (domain) => dotHost.endsWith('.' + domain);
 | 
			
		||||
/******/    const domainReducer = (maxWeight, [domain, ifIncluded]) => {
 | 
			
		||||
            // TODO: handle wildcards.
 | 
			
		||||
/******/    const isHostInDomain = (domain, ifWild) => {
 | 
			
		||||
              if (ifWild) {
 | 
			
		||||
                return dotHost.endsWith(domain.substr(1));
 | 
			
		||||
              }
 | 
			
		||||
              return domain === host;
 | 
			
		||||
            }
 | 
			
		||||
/******/    const domainReducer = (maxWeight, [domain, ifProxy]) => {
 | 
			
		||||
/******/
 | 
			
		||||
/******/      if (!isHostInDomain(domain)) {
 | 
			
		||||
              const ifWild = domain.startsWith('*.');
 | 
			
		||||
/******/      if (!isHostInDomain(domain, ifWild)) {
 | 
			
		||||
/******/        return maxWeight;
 | 
			
		||||
/******/      }
 | 
			
		||||
/******/      const newWeightAbs = domain.length;
 | 
			
		||||
              let len = domain.length;
 | 
			
		||||
              if (ifWild) {
 | 
			
		||||
                len = len === 0 ? len : (len - 2)*2 - 1;
 | 
			
		||||
              } else {
 | 
			
		||||
                len = len*2;
 | 
			
		||||
              }
 | 
			
		||||
/******/      const newWeightAbs = len;
 | 
			
		||||
/******/      if (newWeightAbs < Math.abs(maxWeight)) {
 | 
			
		||||
/******/        return maxWeight;
 | 
			
		||||
/******/      }
 | 
			
		||||
/******/      return newWeightAbs*(ifIncluded ? 1 : -1);
 | 
			
		||||
/******/      return newWeightAbs*(ifProxy ? 1 : -1);
 | 
			
		||||
/******/
 | 
			
		||||
/******/    };
 | 
			
		||||
/******/
 | 
			
		||||
| 
						 | 
				
			
			@ -320,27 +476,30 @@
 | 
			
		|||
/******/      }
 | 
			
		||||
/******/      // Always proxy it!
 | 
			
		||||
${        pacMods.filteredCustomsString
 | 
			
		||||
            ? `/******/      return filteredCustomProxies + directIfAllowed;`
 | 
			
		||||
            ? `/******/      return filteredCustomProxies + "; " + directIfAllowed;`
 | 
			
		||||
            : '/******/      /* No custom proxies -- continue. */'
 | 
			
		||||
}
 | 
			
		||||
/******/    }
 | 
			
		||||
/******/    /* EXCEPTIONS END */
 | 
			
		||||
`;
 | 
			
		||||
        }
 | 
			
		||||
        res += `
 | 
			
		||||
        generatedPac += `
 | 
			
		||||
/******/    const pacScriptProxies = originalFindProxyForURL(url, host)${
 | 
			
		||||
/******/          pacMods.ifProxyOrDie ? '.replace(/DIRECT/g, "")' : ' + directIfAllowed'
 | 
			
		||||
/******/          pacMods.ifProxyOrDie
 | 
			
		||||
                    ? '.replace(/DIRECT/g, "")'
 | 
			
		||||
                    : ' + "; " + directIfAllowed'
 | 
			
		||||
        };`;
 | 
			
		||||
        if(
 | 
			
		||||
          !pacMods.ifUseSecureProxiesOnly &&
 | 
			
		||||
          !pacMods.filteredCustomsString &&
 | 
			
		||||
           pacMods.ifUsePacScriptProxies
 | 
			
		||||
        ) {
 | 
			
		||||
          return res + `
 | 
			
		||||
/******/    return pacScriptProxies + directIfAllowed;`;
 | 
			
		||||
          return generatedPac + `
 | 
			
		||||
/******/    return [pacScriptProxies, directIfAllowed]
 | 
			
		||||
              .filter((p) => p).join("; ") || "DIRECT";`;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return res + `
 | 
			
		||||
        return generatedPac + `
 | 
			
		||||
/******/    let pacProxyArray = pacScriptProxies.split(/(?:\\s*;\\s*)+/g).filter( (p) => p );
 | 
			
		||||
/******/    const ifNoProxies = pacProxyArray${pacMods.ifProxyOrDie ? '.length === 0' : '.every( (p) => /^DIRECT$/i.test(p) )'};
 | 
			
		||||
/******/    if (ifNoProxies) {
 | 
			
		||||
| 
						 | 
				
			
			@ -348,6 +507,10 @@ ${        pacMods.filteredCustomsString
 | 
			
		|||
/******/      return "DIRECT";
 | 
			
		||||
/******/    }
 | 
			
		||||
/******/    return ` +
 | 
			
		||||
        ((pacMods.filteredCustomsString && !pacMods.ifUseOwnProxiesOnlyForOwnSites)
 | 
			
		||||
          ? 'filteredCustomProxies + "; " + '
 | 
			
		||||
          : ''
 | 
			
		||||
        ) +
 | 
			
		||||
        function() {
 | 
			
		||||
 | 
			
		||||
          if (!pacMods.ifUsePacScriptProxies) {
 | 
			
		||||
| 
						 | 
				
			
			@ -358,16 +521,57 @@ ${        pacMods.filteredCustomsString
 | 
			
		|||
            filteredPacExp =
 | 
			
		||||
              'pacProxyArray.filter( (pStr) => /^HTTPS\\s/.test(pStr) ).join("; ")';
 | 
			
		||||
          }
 | 
			
		||||
          return filteredPacExp + ' + ';
 | 
			
		||||
          return filteredPacExp + ' + "; " + ';
 | 
			
		||||
 | 
			
		||||
        }() + `${pacMods.filteredCustomsString ? 'filteredCustomProxies + ' : ''}directIfAllowed;`; // Without DIRECT you will get 'PROXY CONN FAILED' pac-error.
 | 
			
		||||
        }() + 'directIfAllowed;'; // Without DIRECT you will get 'PROXY CONN FAILED' pac-error.
 | 
			
		||||
 | 
			
		||||
      }()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  };
 | 
			
		||||
/******/  };
 | 
			
		||||
${
 | 
			
		||||
      !pacMods.replaceDirectWith
 | 
			
		||||
        ? ''
 | 
			
		||||
        : `
 | 
			
		||||
/******/  const oldTmp = tmp;
 | 
			
		||||
/******/  tmp = function(url, host) {
 | 
			
		||||
/******/    const ip = dnsResolve(host);
 | 
			
		||||
/******/    if (ip) {
 | 
			
		||||
/******/      const ipInt = convert_addr(ip);
 | 
			
		||||
/******/      if([
 | 
			
		||||
/******/          /* Reserved networks: https://en.wikipedia.org/wiki/Reserved_IP_addresses#IPv4 */
 | 
			
		||||
/******/          [-16777216,  0          ], // ['0.0.0.0'    , '255.0.0.0'  ],
 | 
			
		||||
/******/          [-16777216,  167772160  ], // ['10.0.0.0'   , '255.0.0.0'  ],
 | 
			
		||||
/******/          [-4194304,   1681915904 ], // ['100.64.0.0' , '255.192.0.0'],
 | 
			
		||||
/******/          [-16777216,  2130706432 ], // ['127.0.0.0'  , '255.0.0.0'  ],
 | 
			
		||||
/******/          [-65536,     -1442971648], // ['169.254.0.0', '255.255.0.0'],
 | 
			
		||||
/******/          [-1048576,   -1408237568], // ['172.16.0.0', '255.240.0.0'],
 | 
			
		||||
/******/          [-256,       -1073741824], // ['192.0.0.0'  , '255.255.255.0'],
 | 
			
		||||
/******/          [-256,       -1073741312], // ['192.0.2.0'  , '255.255.255.0'],
 | 
			
		||||
/******/          [-256,       -1067949312], // ['192.88.99.0'  , '255.255.255.0'],
 | 
			
		||||
/******/          [-65536,     -1062731776], // ['192.168.0.0', '255.255.0.0'],
 | 
			
		||||
/******/          [-131072,    -971898880 ], // ['198.18.0.0', '255.254.0.0'],
 | 
			
		||||
/******/          [-256,       -969710592 ], // ['198.51.100.0', '255.255.255.0'],
 | 
			
		||||
/******/          [-256,       -889163520 ], // ['203.0.113.0', '255.255.255.0'],
 | 
			
		||||
/******/          [-268435456, -536870912 ], // ['224.0.0.0', '240.0.0.0'],
 | 
			
		||||
/******/          [-268435456, -268435456 ], // ['240.0.0.0', '240.0.0.0'],
 | 
			
		||||
/******/          [-1,         -1         ], // ['255.255.255.255' , '255.255.255.255'],
 | 
			
		||||
/******/        ].some(([netMask, maskedNet]) => (ipInt & netMask) === maskedNet)
 | 
			
		||||
/******/      ) {
 | 
			
		||||
/******/        return "DIRECT";
 | 
			
		||||
/******/      }
 | 
			
		||||
/******/    }
 | 
			
		||||
/******/    return oldTmp.call(this, url, host).replace(/(;|^)\\s*DIRECT\\s*(?=;|$)/g, "$1${pacMods.replaceDirectWith}");
 | 
			
		||||
/******/  };
 | 
			
		||||
          `
 | 
			
		||||
}
 | 
			
		||||
/******/  if (global) {
 | 
			
		||||
/******/    global.FindProxyForURL = tmp;
 | 
			
		||||
/******/  } else {
 | 
			
		||||
/******/    FindProxyForURL = tmp;
 | 
			
		||||
/******/  }
 | 
			
		||||
 | 
			
		||||
}(this);`;
 | 
			
		||||
/*****/})(this);`;
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -382,21 +586,15 @@ ${        pacMods.filteredCustomsString
 | 
			
		|||
 | 
			
		||||
        details
 | 
			
		||||
          ? resolve(details)
 | 
			
		||||
          : chrome.proxy.settings.get({}, timeouted(resolve) )
 | 
			
		||||
          : chrome.proxy.settings.get({}, timeouted(resolve) ),
 | 
			
		||||
 | 
			
		||||
      ).then( (details) => {
 | 
			
		||||
      ).then((details) => {
 | 
			
		||||
 | 
			
		||||
        if (
 | 
			
		||||
          details.levelOfControl === 'controlled_by_this_extension'
 | 
			
		||||
          details && details.levelOfControl === 'controlled_by_this_extension'
 | 
			
		||||
        ) {
 | 
			
		||||
          const pac = window.utils.getProp(details, 'value.pacScript');
 | 
			
		||||
          if (pac && pac.data) {
 | 
			
		||||
            // Delete old kitchen modifications.
 | 
			
		||||
            pac.data = pac.data.replace(
 | 
			
		||||
              new RegExp(kitchenStartsMark + '[\\s\\S]*$', 'g'),
 | 
			
		||||
              ''
 | 
			
		||||
            );
 | 
			
		||||
            /a/.test('a'); // GC RegExp.input and friends.
 | 
			
		||||
            return chrome.proxy.settings.set(details, chromified(cb));
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -413,7 +611,7 @@ ${        pacMods.filteredCustomsString
 | 
			
		|||
    checkIncontinence(details) {
 | 
			
		||||
 | 
			
		||||
      if ( kitchenState(ifIncontinence) ) {
 | 
			
		||||
        this.setNowAsync(details, () => {/* Swallow. */});
 | 
			
		||||
        this.setNowAsync(details, (err) => { if (err) { throw err; } }); // TODO: suppress?
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -448,9 +646,14 @@ ${        pacMods.filteredCustomsString
 | 
			
		|||
            return cb(null, res, ...accWarns);
 | 
			
		||||
          }
 | 
			
		||||
          const newHosts = (pacMods.customProxyArray || []).map( (ps) => ps.split(/\s+/)[1] );
 | 
			
		||||
          window.utils.fireRequest('ip-to-host-replace-all', newHosts, (err, res, ...moreWarns) => cb( err, res, ...accWarns.concat(moreWarns) ));
 | 
			
		||||
          window.utils.fireRequest(
 | 
			
		||||
            'ip-to-host-replace-all',
 | 
			
		||||
            newHosts,
 | 
			
		||||
            (err, res, ...moreWarns) =>
 | 
			
		||||
              cb(err, res, ...accWarns, ...moreWarns),
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        },
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -470,27 +673,21 @@ ${        pacMods.filteredCustomsString
 | 
			
		|||
  const originalSet = chrome.proxy.settings.set.bind( chrome.proxy.settings );
 | 
			
		||||
 | 
			
		||||
  chrome.proxy.settings.set = function(details, cb) {
 | 
			
		||||
 | 
			
		||||
    const pac = window.utils.getProp(details, 'value.pacScript');
 | 
			
		||||
    if (!(pac && pac.data)) {
 | 
			
		||||
      return originalSet(details, cb);
 | 
			
		||||
      return originalSet(details, window.utils.timeouted(cb));
 | 
			
		||||
    }
 | 
			
		||||
    const pacMods = getCurrentConfigs();
 | 
			
		||||
    pac.data = pacKitchen.cook( pac.data, pacMods );
 | 
			
		||||
    originalSet({value: details.value}, (/* No args. */) => {
 | 
			
		||||
    originalSet({value: details.value}, window.utils.chromified((err) => {
 | 
			
		||||
 | 
			
		||||
      kitchenState(ifIncontinence, null);
 | 
			
		||||
      if (!err) {
 | 
			
		||||
        kitchenState(ifIncontinence, null);
 | 
			
		||||
      }
 | 
			
		||||
      window.utils.lastError = err;
 | 
			
		||||
      cb && cb();
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    }));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  pacKitchen.checkIncontinence();
 | 
			
		||||
  chrome.proxy.settings.onChange.addListener(
 | 
			
		||||
    timeouted(
 | 
			
		||||
      pacKitchen.checkIncontinence.bind(pacKitchen)
 | 
			
		||||
    )
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
} // Private namespace ends.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,14 +19,25 @@
 | 
			
		|||
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
  Due to History
 | 
			
		||||
 | 
			
		||||
  - *Async suffix usually means that function requires a cb.
 | 
			
		||||
    It may not be related to async (returning a Promise).
 | 
			
		||||
    This naming is confusing and should be reconsidered.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
{ // Private namespace starts.
 | 
			
		||||
 | 
			
		||||
  const ifRu = chrome.i18n.getMessage('@@ui_locale').startsWith('ru');
 | 
			
		||||
  console.log('Russian?', ifRu);
 | 
			
		||||
  const mandatory = window.utils.mandatory;
 | 
			
		||||
  const throwIfError = window.utils.throwIfError;
 | 
			
		||||
  const chromified = window.utils.chromified;
 | 
			
		||||
  const timeouted = window.utils.timeouted;
 | 
			
		||||
 | 
			
		||||
  const clarifyThen = window.apis.errorsLib.clarifyThen;
 | 
			
		||||
  const clarify = window.apis.errorsLib.clarify;
 | 
			
		||||
  const Warning = window.apis.errorsLib.Warning;
 | 
			
		||||
 | 
			
		||||
  const httpLib = window.apis.httpLib;
 | 
			
		||||
| 
						 | 
				
			
			@ -49,8 +60,70 @@
 | 
			
		|||
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const tryPromiseSeveralTimesAsync = (createPromise, timeoutsInSec) =>
 | 
			
		||||
    createPromise().then(
 | 
			
		||||
      (res) => Promise.resolve(res),
 | 
			
		||||
      (err) => {
 | 
			
		||||
        console.log('Promise failed, are there any retries?');
 | 
			
		||||
        const outSec = timeoutsInSec.shift();
 | 
			
		||||
        if (outSec === undefined) {
 | 
			
		||||
          console.log('No retries left.');
 | 
			
		||||
          return Promise.reject(err);
 | 
			
		||||
        }
 | 
			
		||||
        console.log('Retrying in', outSec, 'sec');
 | 
			
		||||
        /*
 | 
			
		||||
        const alarmName = 'try-promise=several-times-async';
 | 
			
		||||
        const res = new Promise((resolve) => {
 | 
			
		||||
          chrome.alarms.onAlarm.addListener((alarmInfo) => {
 | 
			
		||||
            if (alarmInfo.name === alarmName) {
 | 
			
		||||
              console.log('Time to retry.');
 | 
			
		||||
              resolve(tryPromiseSeveralTimesAsync(createPromise, timeoutsInSec));
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
        });
 | 
			
		||||
        chrome.alarms.create(alarmName, { delayInMinutes: outSec/60 });
 | 
			
		||||
        return res;
 | 
			
		||||
        */
 | 
			
		||||
        return new Promise((resolve) =>
 | 
			
		||||
          window.setTimeout(() => resolve(tryPromiseSeveralTimesAsync(createPromise, timeoutsInSec)), outSec*1000),
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const doWithoutProxyAsync = (createPromise) => new Promise((resolve, reject) => {
 | 
			
		||||
    console.log('Doing without proxy...');
 | 
			
		||||
    chrome.proxy.settings.get({}, chromified((getErr, settings) => {
 | 
			
		||||
      if (getErr) {
 | 
			
		||||
        reject(getErr);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      const ifWeAreInControl = window.utils.areSettingsControlledFor(settings);
 | 
			
		||||
      if (!ifWeAreInControl) {
 | 
			
		||||
        resolve(createPromise());
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      delete settings.levelOfControl;
 | 
			
		||||
      const setProxyAsync = () => new Promise((setResolve, setReject) => {
 | 
			
		||||
 | 
			
		||||
        console.log('Restoring chrome proxy settings...');
 | 
			
		||||
        chrome.proxy.settings.set(
 | 
			
		||||
          settings,
 | 
			
		||||
          chromified((err) => err ? setReject(err) : setResolve()),
 | 
			
		||||
        );
 | 
			
		||||
      });
 | 
			
		||||
      console.log('Clearing chrome proxy settings...');
 | 
			
		||||
      chrome.proxy.settings.clear({}, chromified((clearErr) => {
 | 
			
		||||
        if (clearErr) {
 | 
			
		||||
          reject(clearErr);
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        createPromise().then((actionResult) => setProxyAsync().then(() => resolve(actionResult)), reject);
 | 
			
		||||
      }));
 | 
			
		||||
    }));
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const setPacAsync = function setPacAsync(
 | 
			
		||||
    pacData = mandatory(), cb = throwIfError
 | 
			
		||||
    pacData = mandatory(), cb = throwIfError,
 | 
			
		||||
  ) {
 | 
			
		||||
 | 
			
		||||
    const config = {
 | 
			
		||||
| 
						 | 
				
			
			@ -61,9 +134,16 @@
 | 
			
		|||
      },
 | 
			
		||||
    };
 | 
			
		||||
    console.log('Setting chrome proxy settings...');
 | 
			
		||||
    chrome.proxy.settings.set( {value: config}, chromified((err) => {
 | 
			
		||||
    chrome.proxy.settings.set( { value: config }, chromified((err) => {
 | 
			
		||||
 | 
			
		||||
      if (err) {
 | 
			
		||||
        if (err.message === 'proxy.settings requires private browsing permission.') {
 | 
			
		||||
          clarifyThen(
 | 
			
		||||
            chrome.i18n.getMessage('AllowExtensionToRunInPrivateWindows'),
 | 
			
		||||
            cb,
 | 
			
		||||
          )(err);
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        return cb(err);
 | 
			
		||||
      }
 | 
			
		||||
      handlers.updateControlState( () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -72,17 +152,16 @@
 | 
			
		|||
 | 
			
		||||
          console.warn('Failed, other extension is in control.');
 | 
			
		||||
          return cb(
 | 
			
		||||
            new Error( window.utils.messages.whichExtensionHtml() )
 | 
			
		||||
            new Error( window.utils.messages.whichExtensionHtml() ),
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        console.log('Successfuly set PAC in proxy settings..');
 | 
			
		||||
        console.log('Successfuly set PAC in proxy settings.');
 | 
			
		||||
        cb();
 | 
			
		||||
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    }));
 | 
			
		||||
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const updatePacProxyIps = function updatePacProxyIps(
 | 
			
		||||
| 
						 | 
				
			
			@ -91,75 +170,86 @@
 | 
			
		|||
 | 
			
		||||
    cb = asyncLogGroup(
 | 
			
		||||
      'Getting IPs for PAC hosts...',
 | 
			
		||||
      cb
 | 
			
		||||
      cb,
 | 
			
		||||
    );
 | 
			
		||||
    window.utils.fireRequest('ip-to-host-update-all', cb);
 | 
			
		||||
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const setPacScriptFromProviderAsync = function setPacScriptFromProviderAsync(
 | 
			
		||||
    provider, lastModifiedStr = mandatory(), cb = throwIfError
 | 
			
		||||
    provider, lastModifiedStr, ifUnattended = mandatory(), cb = throwIfError,
 | 
			
		||||
  ) {
 | 
			
		||||
 | 
			
		||||
    const pacUrl = provider.pacUrls[0];
 | 
			
		||||
    cb = asyncLogGroup(
 | 
			
		||||
      'Getting PAC script from provider...', pacUrl,
 | 
			
		||||
      cb
 | 
			
		||||
      cb,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    httpLib.ifModifiedSince(pacUrl, lastModifiedStr, (err, newLastModifiedStr) => {
 | 
			
		||||
    const warnings = [];
 | 
			
		||||
    const originalCb = cb;
 | 
			
		||||
    cb = (err, res, ...warns) => originalCb(err, res, ...warns, ...warnings);
 | 
			
		||||
    const addWarning = (wText) => { warnings.push(new Warning(wText)) };
 | 
			
		||||
 | 
			
		||||
      if (!newLastModifiedStr) {
 | 
			
		||||
        const res = {lastModified: lastModifiedStr};
 | 
			
		||||
        const ifWasEverModified = lastModifiedStr !== new Date(0).toUTCString();
 | 
			
		||||
        if (ifWasEverModified) {
 | 
			
		||||
          return cb(
 | 
			
		||||
            null, res,
 | 
			
		||||
            new Warning(
 | 
			
		||||
              'Ваш PAC-скрипт не нуждается в обновлении. Его дата: ' +
 | 
			
		||||
                lastModifiedStr
 | 
			
		||||
            )
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
    if (provider.distinctKey === 'Anticensority') {
 | 
			
		||||
 | 
			
		||||
      const pacMods = window.apis.pacKitchen.getPacMods();
 | 
			
		||||
      if (!pacMods.filteredCustomsString) {
 | 
			
		||||
        addWarning(
 | 
			
		||||
          ifRu
 | 
			
		||||
            ? \`
 | 
			
		||||
              Не найдено СВОИХ прокси. Этот PAC-скрипт
 | 
			
		||||
              работает только со <a href="https://git.io/ac-own-proxy">СВОИМИ прокси</a>
 | 
			
		||||
              (по умолчанию будет использоваться локальный <a href="https://git.io/ac-tor">Tor</a>).
 | 
			
		||||
            \`
 | 
			
		||||
            : \`
 | 
			
		||||
              Couldn't find OWN proxies. This PAC-script
 | 
			
		||||
              works only with <a href="https://git.io/ac-own-proxy">OWN proxies</a>
 | 
			
		||||
              (by default local <a href="https://git.io/ac-tor">Tor</a> will be used).
 | 
			
		||||
            \`,
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    doWithoutProxyAsync(
 | 
			
		||||
      // Employ all urls, the latter are fallbacks for the former.
 | 
			
		||||
      const pacDataPromise = provider.pacUrls.reduce(
 | 
			
		||||
        (promise, url) => promise.catch(
 | 
			
		||||
          () => new Promise(
 | 
			
		||||
            (resolve, reject) => httpLib.get(
 | 
			
		||||
              url,
 | 
			
		||||
              (newErr, pacData) => newErr ? reject(newErr) : resolve(pacData)
 | 
			
		||||
            )
 | 
			
		||||
          )
 | 
			
		||||
        ),
 | 
			
		||||
        Promise.reject()
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      pacDataPromise.then(
 | 
			
		||||
 | 
			
		||||
        (pacData) => {
 | 
			
		||||
 | 
			
		||||
          setPacAsync(
 | 
			
		||||
            pacData,
 | 
			
		||||
            (err, res) => cb(
 | 
			
		||||
      () => {
 | 
			
		||||
        const tryAllUrlsAsync = () => provider.pacUrls.reduce(
 | 
			
		||||
          (promise, url) => promise.catch(
 | 
			
		||||
            () => new Promise(
 | 
			
		||||
              (resolve, reject) => httpLib.get(
 | 
			
		||||
                url,
 | 
			
		||||
                (newErr, pacData) =>
 | 
			
		||||
                  newErr ? reject(newErr) : resolve(pacData),
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
          Promise.reject(),
 | 
			
		||||
        );
 | 
			
		||||
        return (ifUnattended
 | 
			
		||||
            ? tryPromiseSeveralTimesAsync(tryAllUrlsAsync, [20, 40, 60])
 | 
			
		||||
            : tryAllUrlsAsync()
 | 
			
		||||
        ).catch(
 | 
			
		||||
          (err) => Promise.reject(clarify(
 | 
			
		||||
              err,
 | 
			
		||||
              Object.assign(res || {}, {lastModified: newLastModifiedStr})
 | 
			
		||||
            )
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        clarifyThen(
 | 
			
		||||
          'Не удалось скачать PAC-скрипт с адресов: [ '
 | 
			
		||||
          + provider.pacUrls.join(' , ') + ' ].',
 | 
			
		||||
          cb
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
              chrome.i18n.getMessage('FailedToDownloadPacScriptFromAddresses') + ': [ '
 | 
			
		||||
              + provider.pacUrls.join(' , ') + ' ].',
 | 
			
		||||
          )),
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
    ).then(
 | 
			
		||||
      (pacData) => {
 | 
			
		||||
        setPacAsync(
 | 
			
		||||
          pacData,
 | 
			
		||||
          (err, res) => cb(
 | 
			
		||||
            err,
 | 
			
		||||
            Object.assign(res || {}, {lastModified: lastModifiedStr}),
 | 
			
		||||
          ),
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
      cb,
 | 
			
		||||
    );
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  window.apis.antiCensorRu = {
 | 
			
		||||
| 
						 | 
				
			
			@ -168,20 +258,45 @@
 | 
			
		|||
 | 
			
		||||
    pacProviders: {
 | 
			
		||||
      Антизапрет: {
 | 
			
		||||
        label: 'Антизапрет',
 | 
			
		||||
        desc: \`Альтернативный PAC-скрипт от стороннего разработчика.
 | 
			
		||||
               Работает быстрее, но охватывает меньше сайтов.
 | 
			
		||||
               Блокировка определяется по доменному имени,
 | 
			
		||||
               <br/> <a href="https://antizapret.prostovpn.org">Страница проекта</a>.\`,
 | 
			
		||||
        // Distinct keys are needed if you want to check if a given
 | 
			
		||||
        // provider is this or that (distinct it from others).
 | 
			
		||||
        distinctKey: 'Antizapret',
 | 
			
		||||
        label: chrome.i18n.getMessage('Antizapret'),
 | 
			
		||||
        desc: ifRu
 | 
			
		||||
                ? \`Основной PAC-скрипт от автора проекта «Антизапрет».
 | 
			
		||||
                    Охватывет меньше сайтов.
 | 
			
		||||
                    Блокировка определяется по доменному имени и при необходимости по IP.
 | 
			
		||||
                    <br/> <a href="https://github.com/anticensority/runet-censorship-bypass/wiki/PAC-скрипты:-различия">Сравнение PAC-скриптов</a>.
 | 
			
		||||
                  \`
 | 
			
		||||
                : \`The main PAC-script from the author of project "Antizapret"\.
 | 
			
		||||
                    Covers fewer sites.
 | 
			
		||||
                    Block is detected based on a domain name and, if necessary, on an IP.
 | 
			
		||||
                    <br/> <a href="https://github.com/anticensority/runet-censorship-bypass/wiki/PAC-скрипты:-различия">Comparison of PAC-scripts (ru)</a>.
 | 
			
		||||
                  \`,
 | 
			
		||||
        order: 0,
 | 
			
		||||
        pacUrls: ['https://antizapret.prostovpn.org/proxy.pac'],
 | 
			
		||||
        pacUrls: [
 | 
			
		||||
          'https://e.cen.rodeo:8443/proxy.pac',
 | 
			
		||||
          'https://antizapret.prostovpn.org:8443/proxy.pac',
 | 
			
		||||
          'https://antizapret.prostovpn.org:18443/proxy.pac',
 | 
			
		||||
          'https://antizapret.prostovpn.org/proxy.pac',
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
      Антицензорити: {
 | 
			
		||||
        label: 'Антицензорити',
 | 
			
		||||
        desc: \`Основной PAC-скрипт от автора расширения.
 | 
			
		||||
               Работает медленней, но охватывает больше сайтов.
 | 
			
		||||
               Блокировка определятся по доменному имени или IP адресу.<br/>
 | 
			
		||||
               <a href="https://rebrand.ly/ac-anticensority">Страница проекта</a>.\`,
 | 
			
		||||
        distinctKey: 'Anticensority',
 | 
			
		||||
        label: chrome.i18n.getMessage('Anticensority'),
 | 
			
		||||
        desc: ifRu
 | 
			
		||||
                ? \`Альтернативный PAC-скрипт от автора расширения.
 | 
			
		||||
                    Охватывает больше сайтов.
 | 
			
		||||
                    Блокировка определятся по доменному имени или IP адресу.
 | 
			
		||||
                    Подходит для провайдеров, блокирующих все сайты на одном IP.
 | 
			
		||||
                    <br/> <a href="https://github.com/anticensority/runet-censorship-bypass/wiki/PAC-скрипты:-различия">Сравнение PAC-скриптов</a>.
 | 
			
		||||
                  \`
 | 
			
		||||
                : \`Alternative PAC-script from the author of this extension.
 | 
			
		||||
                    Covers more sites.
 | 
			
		||||
                    Block is detected based on a domain name and on an IP address.
 | 
			
		||||
                    Better fits providers that block all sites on one IP.
 | 
			
		||||
                    <br/> <a href="https://github.com/anticensority/runet-censorship-bypass/wiki/PAC-скрипты:-различия">Comparison of PAC-scripts (ru)</a>.
 | 
			
		||||
                  \`,
 | 
			
		||||
        order: 1,
 | 
			
		||||
 | 
			
		||||
        /*
 | 
			
		||||
| 
						 | 
				
			
			@ -190,28 +305,17 @@
 | 
			
		|||
          Version: 0.17
 | 
			
		||||
        */
 | 
			
		||||
        pacUrls: ${JSON.stringify(anticensorityPacUrls, null, 2)},
 | 
			
		||||
        /*[
 | 
			
		||||
          // First official, shortened:
 | 
			
		||||
          'https://rebrand.ly/ac-chrome-anticensority-pac',
 | 
			
		||||
          // Second official, Cloud Flare with caching:
 | 
			
		||||
          'https://anticensority.tk/generated-pac-scripts/anticensority.pac',
 | 
			
		||||
          // GitHub.io (anticensority):
 | 
			
		||||
          '\x68\x74\x74\x70\x73\x3a\x2f\x2f\x61\x6e\x74\x69\x63\x65\x6e\x73\x6f\x72\x69\x74\x79\x2e\x67\x69\x74\x68\x75\x62\x2e\x69\x6f\x2f\x67\x65\x6e\x65\x72\x61\x74\x65\x64\x2d\x70\x61\x63\x2d\x73\x63\x72\x69\x70\x74\x73\x2f\x61\x6e\x74\x69\x63\x65\x6e\x73\x6f\x72\x69\x74\x79\x2e\x70\x61\x63',
 | 
			
		||||
          // GitHub repo (anticensority):
 | 
			
		||||
          '\x68\x74\x74\x70\x73\x3a\x2f\x2f\x72\x61\x77\x2e\x67\x69\x74\x68\x75\x62\x75\x73\x65\x72\x63\x6f\x6e\x74\x65\x6e\x74\x2e\x63\x6f\x6d\x2f\x61\x6e\x74\x69\x63\x65\x6e\x73\x6f\x72\x69\x74\x79\x2f\x67\x65\x6e\x65\x72\x61\x74\x65\x64\x2d\x70\x61\x63\x2d\x73\x63\x72\x69\x70\x74\x73\x2f\x6d\x61\x73\x74\x65\x72\x2f\x61\x6e\x74\x69\x63\x65\x6e\x73\x6f\x72\x69\x74\x79\x2e\x70\x61\x63',
 | 
			
		||||
          // Old, deprecated:
 | 
			
		||||
          'https://anticensorship-russia.tk/generated-pac-scripts/anticensority.pac',
 | 
			
		||||
          // Google Drive (0.17, anticensority):
 | 
			
		||||
          '\x68\x74\x74\x70\x73\x3a\x2f\x2f\x64\x72\x69\x76\x65\x2e\x67\x6f\x6f\x67\x6c\x65\x2e\x63\x6f\x6d\x2f\x75\x63\x3f\x65\x78\x70\x6f\x72\x74\x3d\x64\x6f\x77\x6e\x6c\x6f\x61\x64\x26\x69\x64\x3d\x30\x42\x32\x6d\x68\x42\x67\x46\x6e\x66\x34\x70\x45\x4c\x56\x6c\x47\x4e\x54\x42\x45\x4d\x58\x4e\x6d\x52\x58\x63',
 | 
			
		||||
        ],*/
 | 
			
		||||
      },
 | 
			
		||||
      onlyOwnSites: {
 | 
			
		||||
        label: 'Только свои сайты и свои прокси',
 | 
			
		||||
        desc: 'Проксируются только добавленные вручную сайты через СВОИ вручную добавленные прокси или через локальный Tor.',
 | 
			
		||||
        distinctKey: 'onlyOwnSites',
 | 
			
		||||
        label: chrome.i18n.getMessage('Only_own_sites_and_only_own_proxies'),
 | 
			
		||||
        desc: ifRu
 | 
			
		||||
                ? 'Проксируются только добавленные вручную адреса через СВОИ вручную добавленные прокси или через локальный Tor.'
 | 
			
		||||
                : 'Only added manually urls are proxied via your OWN manually added proxies or via Tor.',
 | 
			
		||||
        order: 99,
 | 
			
		||||
        pacUrls: [
 | 
			
		||||
          'data:application/x-ns-proxy-autoconfig,' + escape('function FindProxyForURL(){ return "DIRECT"; }'),
 | 
			
		||||
        ]
 | 
			
		||||
        ],
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -221,12 +325,18 @@
 | 
			
		|||
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _currentPacProviderKey: 'Антицензорити',
 | 
			
		||||
    _currentPacProviderKey: 'Антизапрет',
 | 
			
		||||
 | 
			
		||||
    /* Is it the first time extension installed?
 | 
			
		||||
       Do something, e.g. initiate PAC sync.
 | 
			
		||||
    */
 | 
			
		||||
    ifFirstInstall: false,
 | 
			
		||||
    /* We have .lastPacUpdateStamp and ._currentPacProviderLastModified.
 | 
			
		||||
       LastModified is received from a server, we kind of don't trust it,
 | 
			
		||||
       just use it for cache and maybe show to the user.
 | 
			
		||||
       UpdateStamp is got from client and we base our timers on it,
 | 
			
		||||
       malicious server can't interfere with it.
 | 
			
		||||
    */
 | 
			
		||||
    lastPacUpdateStamp: 0,
 | 
			
		||||
 | 
			
		||||
    setTitle() {
 | 
			
		||||
| 
						 | 
				
			
			@ -234,12 +344,12 @@
 | 
			
		|||
      const upDate = new Date(this.lastPacUpdateStamp).toLocaleString('ru-RU')
 | 
			
		||||
        .replace(/:\\d+$/, '').replace(/\\.\\d{4}/, '');
 | 
			
		||||
      chrome.browserAction.setTitle({
 | 
			
		||||
        title: \`Обновлялись \${upDate} | Версия \${window.apis.version.build}\`,
 | 
			
		||||
        title: \`\${chrome.i18n.getMessage('Updated')} \${upDate} | \${chrome.i18n.getMessage('Version')} \${window.apis.version.build}\`,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _currentPacProviderLastModified: 0, // Not initialized.
 | 
			
		||||
    _currentPacProviderLastModified: 0,
 | 
			
		||||
 | 
			
		||||
    getLastModifiedForKey(key = mandatory()) {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -272,7 +382,7 @@
 | 
			
		|||
 | 
			
		||||
    setCurrentPacProviderKey(
 | 
			
		||||
      newKey = mandatory(),
 | 
			
		||||
      lastModified = new Date().toUTCString()
 | 
			
		||||
      lastModified = new Date().toUTCString(),
 | 
			
		||||
    ) {
 | 
			
		||||
 | 
			
		||||
      this.mustBeKey(newKey);
 | 
			
		||||
| 
						 | 
				
			
			@ -309,27 +419,28 @@
 | 
			
		|||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      chrome.storage.local.clear(
 | 
			
		||||
      chrome.storage.local.remove(
 | 
			
		||||
        'antiCensorRu',
 | 
			
		||||
        () => chrome.storage.local.set(
 | 
			
		||||
          onlySettable,
 | 
			
		||||
          chromified(cb)
 | 
			
		||||
        )
 | 
			
		||||
          { antiCensorRu: onlySettable },
 | 
			
		||||
          chromified(cb),
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    syncWithPacProviderAsync(
 | 
			
		||||
      key = this.currentPacProvierKey, cb = throwIfError) {
 | 
			
		||||
 | 
			
		||||
      if( typeof(key) === 'function' ) {
 | 
			
		||||
        cb = key;
 | 
			
		||||
        key = this.getCurrentPacProviderKey();
 | 
			
		||||
    syncWithPacProviderAsync(opts = {}, cb = throwIfError) {
 | 
			
		||||
      const optsDefaults = Object.freeze({ key: this.getCurrentPacProviderKey(), ifUnattended: false });
 | 
			
		||||
      if( typeof(opts) === 'function' ) {
 | 
			
		||||
        cb = opts;
 | 
			
		||||
        opts = {};
 | 
			
		||||
      }
 | 
			
		||||
      let { key, ifUnattended } = { ...optsDefaults, ...opts };
 | 
			
		||||
      cb = asyncLogGroup('Syncing with PAC provider ' + key + '...', cb);
 | 
			
		||||
 | 
			
		||||
      if (key === null) {
 | 
			
		||||
        // No pac provider set.
 | 
			
		||||
        return clarifyThen('Сперва выберите PAC-провайдера.', cb);
 | 
			
		||||
        return clarifyThen(chrome.i18n.getMessage('ChoosePacProviderFirstD'), cb);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const pacProvider = this.getPacProvider(key);
 | 
			
		||||
| 
						 | 
				
			
			@ -338,6 +449,7 @@
 | 
			
		|||
        (resolve, reject) => setPacScriptFromProviderAsync(
 | 
			
		||||
          pacProvider,
 | 
			
		||||
          this.getLastModifiedForKey(key),
 | 
			
		||||
          ifUnattended,
 | 
			
		||||
          (err, res, ...warns) => {
 | 
			
		||||
 | 
			
		||||
            if (!err) {
 | 
			
		||||
| 
						 | 
				
			
			@ -354,24 +466,37 @@
 | 
			
		|||
        )
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      const ipsErrorPromise = new Promise(
 | 
			
		||||
      const updateIpsAsync = () => new Promise(
 | 
			
		||||
        (resolve, reject) => updatePacProxyIps(
 | 
			
		||||
          resolve
 | 
			
		||||
        )
 | 
			
		||||
          (err, res, ...warns) => {
 | 
			
		||||
            if (err) {
 | 
			
		||||
              reject([err, ...warns]);
 | 
			
		||||
              return;
 | 
			
		||||
            }
 | 
			
		||||
            resolve([err, res, ...warns]);
 | 
			
		||||
          },
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      Promise.all([pacSetPromise, ipsErrorPromise]).then(
 | 
			
		||||
        ([[pacErr, pacRes, ...pacWarns], ipsErr]) => {
 | 
			
		||||
      const ipsPromise = !ifUnattended
 | 
			
		||||
        ? updateIpsAsync()
 | 
			
		||||
        : tryPromiseSeveralTimesAsync(updateIpsAsync, [20, 40, 60]);
 | 
			
		||||
 | 
			
		||||
          if (pacErr && ipsErr) {
 | 
			
		||||
      Promise.all([pacSetPromise, ipsPromise]).then(
 | 
			
		||||
        ([[pacErr, pacRes, ...pacWarns], [ipsErr, ipsRes, ...ipsWarns]]) => {
 | 
			
		||||
 | 
			
		||||
          if (pacErr) {
 | 
			
		||||
            return cb(pacErr, pacRes);
 | 
			
		||||
          }
 | 
			
		||||
          const warns = pacWarns;
 | 
			
		||||
          if (ipsErr) {
 | 
			
		||||
            warns.push(ipsErr);
 | 
			
		||||
          }
 | 
			
		||||
          if (ipsWarns.length) {
 | 
			
		||||
            warns.push(...ipsWarns);
 | 
			
		||||
          }
 | 
			
		||||
          this.pushToStorageAsync(
 | 
			
		||||
            (pushErr) => cb(pacErr || pushErr, null, ...warns)
 | 
			
		||||
            (pushErr) => cb(pacErr || pushErr, null, ...warns),
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
        },
 | 
			
		||||
| 
						 | 
				
			
			@ -398,7 +523,7 @@
 | 
			
		|||
 | 
			
		||||
      console.log(
 | 
			
		||||
        'Next PAC update is scheduled on',
 | 
			
		||||
        new Date(nextUpdateMoment).toLocaleString('ru-RU')
 | 
			
		||||
        new Date(nextUpdateMoment).toLocaleString('ru-RU'),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      chrome.alarms.create(
 | 
			
		||||
| 
						 | 
				
			
			@ -421,7 +546,7 @@
 | 
			
		|||
        throw new Error('Key must be defined.');
 | 
			
		||||
      }
 | 
			
		||||
      if (this.currentProviderKey !== key) {
 | 
			
		||||
        return this.syncWithPacProviderAsync(key, cb);
 | 
			
		||||
        return this.syncWithPacProviderAsync({ key }, cb);
 | 
			
		||||
      }
 | 
			
		||||
      console.log(key + ' already installed.');
 | 
			
		||||
      cb();
 | 
			
		||||
| 
						 | 
				
			
			@ -441,11 +566,11 @@
 | 
			
		|||
            }
 | 
			
		||||
            this.setCurrentPacProviderKey(null);
 | 
			
		||||
            this.pushToStorageAsync(
 | 
			
		||||
              () => handlers.updateControlState(cb)
 | 
			
		||||
              () => handlers.updateControlState(cb),
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
          })
 | 
			
		||||
        )
 | 
			
		||||
          }),
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -453,12 +578,25 @@
 | 
			
		|||
  };
 | 
			
		||||
 | 
			
		||||
  // ON EACH LAUNCH, STARTUP, RELOAD, UPDATE, ENABLE
 | 
			
		||||
  chrome.storage.local.get(null, chromified( async (err, oldStorage) => {
 | 
			
		||||
 | 
			
		||||
    if (err) {
 | 
			
		||||
      throw err;
 | 
			
		||||
  (async () => {
 | 
			
		||||
    let ifConsentGiven = await window.utils.promisedLocalStorage.get('ifConsentGiven');
 | 
			
		||||
    if (!ifConsentGiven) {
 | 
			
		||||
      window.utils.openAndFocus('/pages/consent/index.html');
 | 
			
		||||
      await window.apis.consent.promise;
 | 
			
		||||
      ifConsentGiven = true
 | 
			
		||||
      await window.utils.promisedLocalStorage.set({ ifConsentGiven });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let oldAntiCensorRu = await window.utils.promisedLocalStorage.get('antiCensorRu') || {};
 | 
			
		||||
 | 
			
		||||
    const otherKeys = [
 | 
			
		||||
      'pac-kitchen-if-incontinence',
 | 
			
		||||
      'pac-kitchen-mods',
 | 
			
		||||
      'ip-to-host',
 | 
			
		||||
      'handlers-pac-error',
 | 
			
		||||
      'handlers-ext-error',
 | 
			
		||||
      'handlers-no-control',
 | 
			
		||||
    ];
 | 
			
		||||
    /*
 | 
			
		||||
       Event handlers that ALWAYS work (even if installation is not done
 | 
			
		||||
       or failed).
 | 
			
		||||
| 
						 | 
				
			
			@ -473,9 +611,9 @@
 | 
			
		|||
        if (alarm.name === antiCensorRu._periodicUpdateAlarmReason) {
 | 
			
		||||
          console.log(
 | 
			
		||||
            'Periodic PAC update triggered:',
 | 
			
		||||
            new Date().toLocaleString('ru-RU')
 | 
			
		||||
            new Date().toLocaleString('ru-RU'),
 | 
			
		||||
          );
 | 
			
		||||
          antiCensorRu.syncWithPacProviderAsync(() => {/* swallow */});
 | 
			
		||||
          antiCensorRu.syncWithPacProviderAsync({ ifUnattended: true }, () => { /* Swallow. */ });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
      })
 | 
			
		||||
| 
						 | 
				
			
			@ -492,28 +630,29 @@
 | 
			
		|||
    console.log('Keep cooked...');
 | 
			
		||||
    await new Promise((resolve) => window.apis.pacKitchen.keepCookedNowAsync(resolve));
 | 
			
		||||
 | 
			
		||||
    console.log('Storage on init:', oldStorage);
 | 
			
		||||
    antiCensorRu.ifFirstInstall = Object.keys(oldStorage).length === 0;
 | 
			
		||||
    //console.log('Storage on init:', oldAntiCensorRu);
 | 
			
		||||
    antiCensorRu.ifFirstInstall = Object.keys(oldAntiCensorRu).length === 0;
 | 
			
		||||
 | 
			
		||||
    if (antiCensorRu.ifFirstInstall) {
 | 
			
		||||
      // INSTALL
 | 
			
		||||
      console.log('Installing...');
 | 
			
		||||
      handlers.switch('on', 'ext-error');
 | 
			
		||||
      return chrome.runtime.openOptionsPage();
 | 
			
		||||
      chrome.runtime.openOptionsPage();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // LAUNCH, RELOAD, UPDATE
 | 
			
		||||
    // Use old or migrate to default.
 | 
			
		||||
    antiCensorRu._currentPacProviderKey =
 | 
			
		||||
      oldStorage._currentPacProviderKey || null;
 | 
			
		||||
      oldAntiCensorRu._currentPacProviderKey || null;
 | 
			
		||||
    antiCensorRu.lastPacUpdateStamp =
 | 
			
		||||
      oldStorage.lastPacUpdateStamp || antiCensorRu.lastPacUpdateStamp;
 | 
			
		||||
      oldAntiCensorRu.lastPacUpdateStamp || antiCensorRu.lastPacUpdateStamp;
 | 
			
		||||
    antiCensorRu._currentPacProviderLastModified =
 | 
			
		||||
      oldStorage._currentPacProviderLastModified
 | 
			
		||||
      oldAntiCensorRu._currentPacProviderLastModified
 | 
			
		||||
      || antiCensorRu._currentPacProviderLastModified;
 | 
			
		||||
    console.log(
 | 
			
		||||
      'Last PAC update was on',
 | 
			
		||||
      new Date(antiCensorRu.lastPacUpdateStamp).toLocaleString('ru-RU')
 | 
			
		||||
      new Date(antiCensorRu.lastPacUpdateStamp).toLocaleString('ru-RU'),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -525,34 +664,45 @@
 | 
			
		|||
         Better on each launch then on each pull.
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    await new Promise((resolve) => {
 | 
			
		||||
    await new Promise(async (resolve) => {
 | 
			
		||||
 | 
			
		||||
      const ifUpdating = antiCensorRu.version !== oldStorage.version;
 | 
			
		||||
      const ifUpdating = antiCensorRu.version !== oldAntiCensorRu.version;
 | 
			
		||||
      if (!ifUpdating) {
 | 
			
		||||
 | 
			
		||||
        // LAUNCH, RELOAD, ENABLE
 | 
			
		||||
        antiCensorRu.pacProviders = oldStorage.pacProviders;
 | 
			
		||||
        antiCensorRu.pacProviders = oldAntiCensorRu.pacProviders;
 | 
			
		||||
        console.log('Extension launched, reloaded or enabled.');
 | 
			
		||||
        return resolve();
 | 
			
		||||
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // UPDATE & MIGRATION
 | 
			
		||||
      console.log('Updating from ', oldStorage.version, 'to', antiCensorRu.version);
 | 
			
		||||
      const key = antiCensorRu._currentPacProviderKey;
 | 
			
		||||
      if (oldStorage.version === ' 0.0.1.2') {
 | 
			
		||||
        if (key !== null && key !== 'onlyOwnSites') {
 | 
			
		||||
          antiCensorRu._currentPacProviderKey = 'Антицензорити';
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      antiCensorRu.pushToStorageAsync(() => {
 | 
			
		||||
      const ifUpdatedCb = () => antiCensorRu.pushToStorageAsync(() => {
 | 
			
		||||
 | 
			
		||||
        console.log('Extension updated.');
 | 
			
		||||
        resolve();
 | 
			
		||||
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      console.log('Updating from', oldAntiCensorRu.version, 'to', antiCensorRu.version);
 | 
			
		||||
      try {
 | 
			
		||||
        if (window.apis.version.isLeq(oldAntiCensorRu.version, '0.0.1.62')) {
 | 
			
		||||
          window.apis.antiCensorRu.pacProviders['Антизапрет'].pacUrls = [
 | 
			
		||||
            'https://e.cen.rodeo:8443/proxy.pac',
 | 
			
		||||
            'https://antizapret.prostovpn.org:8443/proxy.pac',
 | 
			
		||||
            'https://antizapret.prostovpn.org:18443/proxy.pac',
 | 
			
		||||
            'https://antizapret.prostovpn.org/proxy.pac',
 | 
			
		||||
          ];
 | 
			
		||||
          console.log('Successfully updated to 0.0.1.63.');
 | 
			
		||||
        }
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
        // Log update error.
 | 
			
		||||
        console.log('UPDATE ERROR:');
 | 
			
		||||
        console.error(e);
 | 
			
		||||
      }
 | 
			
		||||
      ifUpdatedCb();
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (antiCensorRu.getPacProvider()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -575,6 +725,6 @@
 | 
			
		|||
        * Add storage.lastPacUpdateStamp.
 | 
			
		||||
    **/
 | 
			
		||||
 | 
			
		||||
  }));
 | 
			
		||||
  })();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,7 +13,7 @@
 | 
			
		|||
        order: 0,
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      hostTracker: {
 | 
			
		||||
      googleCache: {
 | 
			
		||||
        title: 'Из кэша Google',
 | 
			
		||||
        getUrl: (blockedUrl) => 'http://webcache.googleusercontent.com/search?q=cache:' + blockedUrl,
 | 
			
		||||
        order: 1,
 | 
			
		||||
| 
						 | 
				
			
			@ -27,19 +27,19 @@
 | 
			
		|||
 | 
			
		||||
      otherUnblock: {
 | 
			
		||||
        title: 'Разблокировать по-другому',
 | 
			
		||||
        getUrl: (blockedUrl) => ('https://rebrand.ly/ac-unblock#' + blockedUrl),
 | 
			
		||||
        getUrl: (blockedUrl) => ('https://anticensority.github.io/unblock#' + blockedUrl),
 | 
			
		||||
        order: 3,
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      antizapretInfo: {
 | 
			
		||||
        title: 'Сайт в реестре блокировок?',
 | 
			
		||||
        getUrl: (blockedUrl) => 'https://antizapret.info/index.php?search=' + new URL(blockedUrl).hostname,
 | 
			
		||||
        getUrl: (blockedUrl) => 'https://reestr.rublacklist.net/?q=' + new URL(blockedUrl).hostname,
 | 
			
		||||
        order: 4,
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      support: {
 | 
			
		||||
        title: 'Документация / Помощь / Поддержка',
 | 
			
		||||
        getUrl: (blockedUrl) => 'https://rebrand.ly/ac-wiki',
 | 
			
		||||
        getUrl: (blockedUrl) => 'https://github.com/anticensority/runet-censorship-bypass/wiki',
 | 
			
		||||
        order: 99,
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,48 @@
 | 
			
		|||
# For Reviewers
 | 
			
		||||
 | 
			
		||||
## Prerequirements
 | 
			
		||||
 | 
			
		||||
* You need a globally installed `gulp-cli@3.0.0`.
 | 
			
		||||
  See https://gulpjs.com/docs/en/getting-started/quick-start#install-the-gulp-command-line-utility.
 | 
			
		||||
* Node v21.7.3
 | 
			
		||||
* NPM 10.5.0
 | 
			
		||||
 | 
			
		||||
## Steps
 | 
			
		||||
 | 
			
		||||
Steps to reproduce the same zip:
 | 
			
		||||
```
 | 
			
		||||
npm ci
 | 
			
		||||
cd src/extension-common/pages/options/
 | 
			
		||||
npm ci
 | 
			
		||||
cd -
 | 
			
		||||
npm start
 | 
			
		||||
# See ./build/extension-full
 | 
			
		||||
cd ./build/extension-full
 | 
			
		||||
zip -r runet-censorship-bypass-full.zip ./*
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Minified Files
 | 
			
		||||
 | 
			
		||||
### Ace Editor
 | 
			
		||||
 | 
			
		||||
https://ace.c9.io -> https://github.com/ajaxorg/ace -> Building Ace -> "The ace-builds repository endeavours to maintain the latest build" -> https://github.com/ajaxorg/ace-builds/ -> Select tag of 1.2.5, open https://github.com/ajaxorg/ace-builds/tree/v1.2.5/src-min and download the files you want to check.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
mkdir downloaded
 | 
			
		||||
cd downloaded
 | 
			
		||||
wget https://raw.githubusercontent.com/ajaxorg/ace-builds/v1.2.5/src-min/ace.js
 | 
			
		||||
wget https://raw.githubusercontent.com/ajaxorg/ace-builds/v1.2.5/src-min/ext-searchbox.js
 | 
			
		||||
wget https://raw.githubusercontent.com/ajaxorg/ace-builds/v1.2.5/src-min/mode-javascript.js
 | 
			
		||||
wget https://raw.githubusercontent.com/ajaxorg/ace-builds/v1.2.5/src-min/worker-javascript.js
 | 
			
		||||
 | 
			
		||||
downloaded$ for i in ./*; do md5sum "$i"; done
 | 
			
		||||
2b9a1157bb3ba711a0402b6751d9ac71  ./ace.js
 | 
			
		||||
1f73efaff2853571af0e701c5e9a15ee  ./ext-searchbox.js
 | 
			
		||||
e5eebd85c4e66667c28f124e6a07e3ed  ./mode-javascript.js
 | 
			
		||||
f0d1342102d16ab7abe319b2683d10ea  ./worker-javascript.js
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## PAC-Script AntiZapret
 | 
			
		||||
 | 
			
		||||
https://antizapret.prostovpn.org/proxy.pac (old) and https://e.cen.rodeo:8443/proxy.pac (new) are generated by https://bitbucket.org/anticensority/antizapret-pac-generator-light/, reviewers may find justifications related to this PAC-script in that repo.
 | 
			
		||||
| 
						 | 
				
			
			@ -1 +0,0 @@
 | 
			
		|||
Files of this directory must be copied into final build without modifications.
 | 
			
		||||
| 
						 | 
				
			
			@ -3,7 +3,7 @@
 | 
			
		|||
    "message": "Runet Censorship Bypass${nameSuffixEn}"
 | 
			
		||||
  },
 | 
			
		||||
  "extDesc": {
 | 
			
		||||
    "message": "Circumvent Russian Internet Censorship: https://rebrand.ly/ac-wiki"
 | 
			
		||||
    "message": "Circumvent Russian Internet Censorship: https://git.io/ac-wiki"
 | 
			
		||||
  },
 | 
			
		||||
  "proxy": {
 | 
			
		||||
    "message": "proxy"
 | 
			
		||||
| 
						 | 
				
			
			@ -11,7 +11,133 @@
 | 
			
		|||
  "noControl": {
 | 
			
		||||
    "message": "Other extension controls proxy!"
 | 
			
		||||
  },
 | 
			
		||||
  "which": {
 | 
			
		||||
  "WhichQ": {
 | 
			
		||||
    "message": "Which?"
 | 
			
		||||
  },
 | 
			
		||||
  "update": {
 | 
			
		||||
    "message": "update"
 | 
			
		||||
  },
 | 
			
		||||
  "UpdatingDDD": {
 | 
			
		||||
    "message": "Updating..."
 | 
			
		||||
  },
 | 
			
		||||
  "UpdatedD": {
 | 
			
		||||
    "message": "Updated."
 | 
			
		||||
  },
 | 
			
		||||
  "DisablingDDD": {
 | 
			
		||||
    "message": "Disabling..."
 | 
			
		||||
  },
 | 
			
		||||
  "DisabledD": {
 | 
			
		||||
    "message": "Disabled."
 | 
			
		||||
  },
 | 
			
		||||
  "InstallingDDD": {
 | 
			
		||||
    "message": "Installing..."
 | 
			
		||||
  },
 | 
			
		||||
  "PacScriptWasInstalledD": {
 | 
			
		||||
    "message": "PAC-script was installed."
 | 
			
		||||
  },
 | 
			
		||||
  "Version": {
 | 
			
		||||
    "message": "Version"
 | 
			
		||||
  },
 | 
			
		||||
  "FullVersion": {
 | 
			
		||||
    "message": "Full version"
 | 
			
		||||
  },
 | 
			
		||||
  "VersionForSlowMachines": {
 | 
			
		||||
    "message": "Version for slow machines"
 | 
			
		||||
  },
 | 
			
		||||
  "FailedToDownloadPacScriptFromAddresses": {
 | 
			
		||||
    "message": "Failed to download PAC-script from addresses"
 | 
			
		||||
  },
 | 
			
		||||
  "ChoosePacProviderFirstD": {
 | 
			
		||||
    "message": "Choose PAC-provider first."
 | 
			
		||||
  },
 | 
			
		||||
  "ProblemsQ": {
 | 
			
		||||
    "message": "Problems?"
 | 
			
		||||
  },
 | 
			
		||||
  "Finish": {
 | 
			
		||||
    "message": "OK"
 | 
			
		||||
  },
 | 
			
		||||
  "Disable": {
 | 
			
		||||
    "message": "Disable"
 | 
			
		||||
  },
 | 
			
		||||
  "Only_own_sites_and_only_own_proxies": {
 | 
			
		||||
    "message": "Only own sites and only own proxies"
 | 
			
		||||
  },
 | 
			
		||||
  "Antizapret": {
 | 
			
		||||
    "message": "Antizapret"
 | 
			
		||||
  },
 | 
			
		||||
  "Anticensority": {
 | 
			
		||||
    "message": "Anticensority"
 | 
			
		||||
  },
 | 
			
		||||
  "PAC_script": {
 | 
			
		||||
    "message": "PAC-script"
 | 
			
		||||
  },
 | 
			
		||||
  "Exceptions": {
 | 
			
		||||
    "message": "Exceptions"
 | 
			
		||||
  },
 | 
			
		||||
  "Own_proxies": {
 | 
			
		||||
    "message": "Own proxies"
 | 
			
		||||
  },
 | 
			
		||||
  "Modifiers": {
 | 
			
		||||
    "message": "Modifiers"
 | 
			
		||||
  },
 | 
			
		||||
  "Notifications": {
 | 
			
		||||
    "message": "Notifications"
 | 
			
		||||
  },
 | 
			
		||||
  "Error": {
 | 
			
		||||
    "message": "Error"
 | 
			
		||||
  },
 | 
			
		||||
  "Non_critical_error": {
 | 
			
		||||
    "message": "Non-critical error. Don't worry: it works"
 | 
			
		||||
  },
 | 
			
		||||
  "Donate": {
 | 
			
		||||
    "message": "Donate"
 | 
			
		||||
  },
 | 
			
		||||
  "Updated": {
 | 
			
		||||
    "message": "Updated"
 | 
			
		||||
  },
 | 
			
		||||
  "ago": {
 | 
			
		||||
    "message": "ago"
 | 
			
		||||
  },
 | 
			
		||||
  "never": {
 | 
			
		||||
    "message": "never"
 | 
			
		||||
  },
 | 
			
		||||
  "ms": {
 | 
			
		||||
    "message": "ms"
 | 
			
		||||
  },
 | 
			
		||||
  "s": {
 | 
			
		||||
    "message": "s"
 | 
			
		||||
  },
 | 
			
		||||
  "min": {
 | 
			
		||||
    "message": "min"
 | 
			
		||||
  },
 | 
			
		||||
  "h": {
 | 
			
		||||
    "message": "h"
 | 
			
		||||
  },
 | 
			
		||||
  "d": {
 | 
			
		||||
    "message": "d"
 | 
			
		||||
  },
 | 
			
		||||
  "w": {
 | 
			
		||||
    "message": "w"
 | 
			
		||||
  },
 | 
			
		||||
  "m": {
 | 
			
		||||
    "message": "m"
 | 
			
		||||
  },
 | 
			
		||||
  "ProxyTheDomainNameBelowQ": {
 | 
			
		||||
    "message": "Proxy the domain name below?"
 | 
			
		||||
  },
 | 
			
		||||
  "auto": {
 | 
			
		||||
    "message": "auto"
 | 
			
		||||
  },
 | 
			
		||||
  "yes": {
 | 
			
		||||
    "message": "yes"
 | 
			
		||||
  },
 | 
			
		||||
  "no": {
 | 
			
		||||
    "message": "no"
 | 
			
		||||
  },
 | 
			
		||||
  "noOwnProxiesError": {
 | 
			
		||||
    "message": "Proxying of OWN sites is possible only via OWN proxies. No own proxies found that satisfy your requirements."
 | 
			
		||||
  },
 | 
			
		||||
  "AllowExtensionToRunInPrivateWindows": {
 | 
			
		||||
    "message": "For the extension to work it is required to allow it to run in private windows, see <a href='https://github.com/anticensority/runet-censorship-bypass/wiki/Как-разрешить-запуск-расширения-в-приватных-окнах-|-How-to-allow-extension-to-run-in-private-windows'>a HOWTO</a>."
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@
 | 
			
		|||
    "message": "Обход блокировок Рунета${nameSuffixRu}"
 | 
			
		||||
  },
 | 
			
		||||
  "extDesc": {
 | 
			
		||||
    "message": "Обход интернет-цензуры в России: https://rebrand.ly/ac-wiki"
 | 
			
		||||
    "message": "Обход интернет-цензуры в России: https://git.io/ac-wiki"
 | 
			
		||||
  },
 | 
			
		||||
  "proxy": {
 | 
			
		||||
    "message": "прокси"
 | 
			
		||||
| 
						 | 
				
			
			@ -11,7 +11,133 @@
 | 
			
		|||
  "noControl": {
 | 
			
		||||
    "message": "Другое расширение контролирует настройки прокси!"
 | 
			
		||||
  },
 | 
			
		||||
  "which": {
 | 
			
		||||
  "WhichQ": {
 | 
			
		||||
    "message": "Какое?"
 | 
			
		||||
  },
 | 
			
		||||
  "update": {
 | 
			
		||||
    "message": "обновить"
 | 
			
		||||
  },
 | 
			
		||||
  "UpdatingDDD": {
 | 
			
		||||
    "message": "Обновляем..."
 | 
			
		||||
  },
 | 
			
		||||
  "UpdatedD": {
 | 
			
		||||
    "message": "Обновлено."
 | 
			
		||||
  },
 | 
			
		||||
  "DisablingDDD": {
 | 
			
		||||
    "message": "Отключение..."
 | 
			
		||||
  },
 | 
			
		||||
  "DisabledD": {
 | 
			
		||||
    "message": "Отключено."
 | 
			
		||||
  },
 | 
			
		||||
  "InstallingDDD": {
 | 
			
		||||
    "message": "Установка..."
 | 
			
		||||
  },
 | 
			
		||||
  "PacScriptWasInstalledD": {
 | 
			
		||||
    "message": "PAC-скрипт установлен."
 | 
			
		||||
  },
 | 
			
		||||
  "Version": {
 | 
			
		||||
    "message": "Версия"
 | 
			
		||||
  },
 | 
			
		||||
  "FullVersion": {
 | 
			
		||||
    "message": "Полная версия"
 | 
			
		||||
  },
 | 
			
		||||
  "VersionForSlowMachines": {
 | 
			
		||||
    "message": "Версия для слабых машин"
 | 
			
		||||
  },
 | 
			
		||||
  "FailedToDownloadPacScriptFromAddresses": {
 | 
			
		||||
    "message": "Не удалось скачать PAC-скрипт с адресов"
 | 
			
		||||
  },
 | 
			
		||||
  "ChoosePacProviderFirstD": {
 | 
			
		||||
    "message": "Сперва выберите PAC-провайдера."
 | 
			
		||||
  },
 | 
			
		||||
  "ProblemsQ": {
 | 
			
		||||
    "message": "Проблемы?"
 | 
			
		||||
  },
 | 
			
		||||
  "Finish": {
 | 
			
		||||
    "message": "Готово"
 | 
			
		||||
  },
 | 
			
		||||
  "Disable": {
 | 
			
		||||
    "message": "Отключить"
 | 
			
		||||
  },
 | 
			
		||||
  "Only_own_sites_and_only_own_proxies": {
 | 
			
		||||
    "message": "Только свои сайты и свои прокси"
 | 
			
		||||
  },
 | 
			
		||||
  "Antizapret": {
 | 
			
		||||
    "message": "Антизапрет"
 | 
			
		||||
  },
 | 
			
		||||
  "Anticensority": {
 | 
			
		||||
    "message": "Антицензорити"
 | 
			
		||||
  },
 | 
			
		||||
  "PAC_script": {
 | 
			
		||||
    "message": "PAC-скрипт"
 | 
			
		||||
  },
 | 
			
		||||
  "Exceptions": {
 | 
			
		||||
    "message": "Исключения"
 | 
			
		||||
  },
 | 
			
		||||
  "Own_proxies": {
 | 
			
		||||
    "message": "Свои прокси"
 | 
			
		||||
  },
 | 
			
		||||
  "Modifiers": {
 | 
			
		||||
    "message": "Модификаторы"
 | 
			
		||||
  },
 | 
			
		||||
  "Notifications": {
 | 
			
		||||
    "message": "Уведомления"
 | 
			
		||||
  },
 | 
			
		||||
  "Error": {
 | 
			
		||||
    "message": "Ошибка"
 | 
			
		||||
  },
 | 
			
		||||
  "Non_critical_error": {
 | 
			
		||||
    "message": "Некритичная ошибка. Всё хорошо, продолжаем работу"
 | 
			
		||||
  },
 | 
			
		||||
  "Donate": {
 | 
			
		||||
    "message": "Поддержать"
 | 
			
		||||
  },
 | 
			
		||||
  "Updated": {
 | 
			
		||||
    "message": "Обновлялись"
 | 
			
		||||
  },
 | 
			
		||||
  "ago": {
 | 
			
		||||
    "message": "назад"
 | 
			
		||||
  },
 | 
			
		||||
  "never": {
 | 
			
		||||
    "message": "никогда"
 | 
			
		||||
  },
 | 
			
		||||
  "ms": {
 | 
			
		||||
    "message": "мс"
 | 
			
		||||
  },
 | 
			
		||||
  "s": {
 | 
			
		||||
    "message": "с"
 | 
			
		||||
  },
 | 
			
		||||
  "min": {
 | 
			
		||||
    "message": "мин"
 | 
			
		||||
  },
 | 
			
		||||
  "h": {
 | 
			
		||||
    "message": "ч"
 | 
			
		||||
  },
 | 
			
		||||
  "d": {
 | 
			
		||||
    "message": "дн"
 | 
			
		||||
  },
 | 
			
		||||
  "w": {
 | 
			
		||||
    "message": " недель"
 | 
			
		||||
  },
 | 
			
		||||
  "m": {
 | 
			
		||||
    "message": " месяцев"
 | 
			
		||||
  },
 | 
			
		||||
  "ProxyTheDomainNameBelowQ": {
 | 
			
		||||
    "message": "Проксировать указанное доменное имя?"
 | 
			
		||||
  },
 | 
			
		||||
  "auto": {
 | 
			
		||||
    "message": "авто"
 | 
			
		||||
  },
 | 
			
		||||
  "yes": {
 | 
			
		||||
    "message": "да"
 | 
			
		||||
  },
 | 
			
		||||
  "no": {
 | 
			
		||||
    "message": "нет"
 | 
			
		||||
  },
 | 
			
		||||
  "noOwnProxiesError": {
 | 
			
		||||
    "message": "Проксировать СВОИ сайты можно только при наличии СВОИХ прокси. Нет своих прокси, удовлетворяющих вашим требованиям."
 | 
			
		||||
  },
 | 
			
		||||
  "AllowExtensionToRunInPrivateWindows": {
 | 
			
		||||
    "message": "Для работы расширения необходимо разрешить запуск в приватных окнах, см. <a href='https://github.com/anticensority/runet-censorship-bypass/wiki/Как-разрешить-запуск-расширения-в-приватных-окнах-|-How-to-allow-extension-to-run-in-private-windows'>инструкции</a>."
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,15 +6,15 @@
 | 
			
		|||
  "description": "__MSG_extDesc__",
 | 
			
		||||
  "version": "0.0.${version}",
 | 
			
		||||
  "icons": {
 | 
			
		||||
    "128": "/icons/default-128.png"
 | 
			
		||||
    "128": "icons/default-128.png"
 | 
			
		||||
  },
 | 
			
		||||
  "author": "ilyaigpetrov@gmail.com",
 | 
			
		||||
  "homepage_url": "https://github.com/anticensorship-russia/chromium-extension",
 | 
			
		||||
  "author": "anticensority+owners@googlegroups.com",
 | 
			
		||||
 | 
			
		||||
  "permissions": [
 | 
			
		||||
      "proxy"
 | 
			
		||||
    , "alarms"
 | 
			
		||||
    , "storage"
 | 
			
		||||
    , "unlimitedStorage"
 | 
			
		||||
    , "<all_urls>"
 | 
			
		||||
    , "tabs"
 | 
			
		||||
    , "contextMenus"
 | 
			
		||||
| 
						 | 
				
			
			@ -22,14 +22,21 @@
 | 
			
		|||
    ${extra_permissions}
 | 
			
		||||
  ],
 | 
			
		||||
  "minimum_chrome_version": "55.0.0.0",
 | 
			
		||||
  "browser_specific_settings": {
 | 
			
		||||
    "gecko": {
 | 
			
		||||
      "strict_min_version": "91.1.0"
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  "background": {
 | 
			
		||||
    ${persistent}
 | 
			
		||||
    "scripts": [
 | 
			
		||||
        "00-init-apis.js"
 | 
			
		||||
      ${scripts_0x}
 | 
			
		||||
      , "11-error-handlers-api.js"
 | 
			
		||||
      , "12-errors-lib.js"
 | 
			
		||||
      , "13-http-lib.js"
 | 
			
		||||
      , "15-firefox-proxy-settings.js"
 | 
			
		||||
      ${scripts_2x}
 | 
			
		||||
      , "35-pac-kitchen-api.js"
 | 
			
		||||
      , "37-sync-pac-script-with-pac-provider-api.js"
 | 
			
		||||
| 
						 | 
				
			
			@ -38,6 +45,7 @@
 | 
			
		|||
      , "75-context-menus.js"
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  "browser_action": {
 | 
			
		||||
    "default_title": "Этот сайт благословлён | Версия ${version + versionSuffix}",
 | 
			
		||||
    "default_popup": "/pages/options/index.html"
 | 
			
		||||
| 
						 | 
				
			
			@ -46,4 +54,5 @@
 | 
			
		|||
     "page": "/pages/options/index.html",
 | 
			
		||||
     "chrome_style": false
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,108 @@
 | 
			
		|||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
  <head>
 | 
			
		||||
    <link rel="icon" type="image/x-icon" href="./ribbon-128.ico">
 | 
			
		||||
    <meta charset="utf-8">
 | 
			
		||||
    <title>Согласие на сбор данных / Data Collection Consent</title>
 | 
			
		||||
    <style>
 | 
			
		||||
      p, ol { font-size: 16px }
 | 
			
		||||
    </style>
 | 
			
		||||
  </head>
 | 
			
		||||
  <body>
 | 
			
		||||
    <div style="margin: 0 auto; max-width: 35rem; text-align: justify; font-family: sans-serif">
 | 
			
		||||
      <img src="./ribbon-128.png">
 | 
			
		||||
      <h1>Согласие на сбор данных / Data Collection Consent</h1>
 | 
			
		||||
      <p>
 | 
			
		||||
      Этот документ написан на двух языках. Английский предпочтительней.
 | 
			
		||||
      </p>
 | 
			
		||||
      <p>
 | 
			
		||||
      This document is written in two languages. English is preferable.
 | 
			
		||||
      </p>
 | 
			
		||||
      <p>
 | 
			
		||||
      Привет! На связи команда разработчиков браузерного расширения «Обход блокировок Рунета».
 | 
			
		||||
      Вы либо только что установили расширение, либо только что обновились до новой версии.
 | 
			
		||||
      По новым правилам мы <a href="https://github.com/anticensority/runet-censorship-bypass/wiki/Зачем-нам-согласие-со-сбором-данных%3F">должны</a>
 | 
			
		||||
      получить от вас согласие на сбор ваших данных и передачу их
 | 
			
		||||
      третьим лицам.
 | 
			
		||||
      </p>
 | 
			
		||||
      <p>
 | 
			
		||||
      Hi! It's "Runet Censorship Bypass" browser extension developers team.
 | 
			
		||||
      You have just installed the extension or just updated to a new version.
 | 
			
		||||
      According to the new requirements we <a href="https://github.com/anticensority/runet-censorship-bypass/wiki/Зачем-нам-согласие-со-сбором-данных%3F">have to</a> get your consent before collecting your data and
 | 
			
		||||
      sharing it with 3rd parties.
 | 
			
		||||
      </p>
 | 
			
		||||
      <p>
 | 
			
		||||
      Сообщаем вам, что после первого запуска расширения и при дальнейшем его использовании
 | 
			
		||||
      настроенные в нём PAC-скрипты и прокси-сервера могут собирать или уже* собирают
 | 
			
		||||
      некоторые ваши данные. Какие именно, зависит от выбранного вами поставщика этих ресурсов,
 | 
			
		||||
      так что рекомендуем ознакомиться с их соответствующей политикой конфиденциальности (Privacy
 | 
			
		||||
      Policy).
 | 
			
		||||
      <br>
 | 
			
		||||
      * Если вы установили и пользовалсись расширением уже некоторое время. Этого экрана согласия не
 | 
			
		||||
      было в старых версиях.
 | 
			
		||||
      </p>
 | 
			
		||||
      <p>
 | 
			
		||||
      We inform you that starting from the first launch of the extension and on further usage chosen
 | 
			
		||||
      PAC-scripts and proxy-servers may collect or are already* collecting some of your data. Which
 | 
			
		||||
      exactly depends on the chosen provider of these resources so we recommend you to get
 | 
			
		||||
      acquainted with their corresponding Privacy Policy.
 | 
			
		||||
      <br>
 | 
			
		||||
      * If you have installed and have been using the extension for some time already. This consent
 | 
			
		||||
      screen wasn't shown in the old versions.
 | 
			
		||||
      </p>
 | 
			
		||||
      <p>
 | 
			
		||||
      При первом запуске и по умолчанию будут использоваться PAC-скрипт и встроенные в него
 | 
			
		||||
      прокси-сервера, предоставляемые <a href="https://antizapret.prostovpn.org:8443">проектом
 | 
			
		||||
      "АнтиЗапрет"</a>, — политику конфиденциальности этого решения см. в
 | 
			
		||||
      <a href="https://antizapret.prostovpn.org:8443/faq.html">FAQ</a> под заголовком "Какие данные
 | 
			
		||||
      собирает сервис и каким образом они используются?".
 | 
			
		||||
      </p>
 | 
			
		||||
      <p>
 | 
			
		||||
      On the first launch and by default the PAC-script and its built-in proxy-servers provided by
 | 
			
		||||
      <a href="https://antizapret.prostovpn.org:8443">project "AntiZapret" (RU)</a> will be used, —
 | 
			
		||||
      see its Privacy Policy in
 | 
			
		||||
      <a href="https://antizapret.prostovpn.org:8443/faq.html">the FAQ (RU)</a> under the title
 | 
			
		||||
      "Какие данные собирает сервис и каким образом они используются?". Its translation to EN is
 | 
			
		||||
      present in the Privacy Policy of the extension.
 | 
			
		||||
      </p>
 | 
			
		||||
      <p>
 | 
			
		||||
      Политику конфиденциальности самого расширения см.
 | 
			
		||||
      <a
 | 
			
		||||
        href="https://github.com/anticensority/runet-censorship-bypass/wiki/Privacy-Policy-|-Политика-конфиденциальности"
 | 
			
		||||
      >здесь (EN)</a>.
 | 
			
		||||
      </p>
 | 
			
		||||
      <p>
 | 
			
		||||
      See the Privacy Policy of this extension
 | 
			
		||||
      <a
 | 
			
		||||
        href="https://github.com/anticensority/runet-censorship-bypass/wiki/Privacy-Policy-|-Политика-конфиденциальности"
 | 
			
		||||
      >here</a>.
 | 
			
		||||
      </p>
 | 
			
		||||
      <p>
 | 
			
		||||
        В расширении представлены кнопки / There are these buttons in the extension:
 | 
			
		||||
      </p>
 | 
			
		||||
      <ol>
 | 
			
		||||
        <li>
 | 
			
		||||
          "Через Google Translate" / "Via Google Translate"
 | 
			
		||||
        </li><li>
 | 
			
		||||
          "Из кэша Google" / "From Google Cache"
 | 
			
		||||
        </li><li>
 | 
			
		||||
          "Из архива archive.org" / "From archive.org archive"
 | 
			
		||||
        </li><li>
 | 
			
		||||
          "Разблокировать по-другому" / "Unblock another way"
 | 
			
		||||
        </li><li>
 | 
			
		||||
          "Сайт в реестре блокировок?" / "Is site in the registry of blockings?"
 | 
			
		||||
        </li>
 | 
			
		||||
      </ol>
 | 
			
		||||
      <p>
 | 
			
		||||
        Все эти кнопки передают URL-адрес текущей вкладки в соответствующие службы. /
 | 
			
		||||
        All these buttons share URL-address of the current tab with corresponding services.
 | 
			
		||||
      </p>
 | 
			
		||||
      <button id="agreeBtn">Разрешаю собирать заявленные данные / Allow claimed data collection
 | 
			
		||||
      </button>
 | 
			
		||||
      <button id="rejectBtn">Нет, удалите расширение / No, delete this extension
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
    <script src="./index.js"></script>
 | 
			
		||||
    <script src="../lib/keep-links-clickable.js"></script>
 | 
			
		||||
  </body>
 | 
			
		||||
</html>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
'use strict';
 | 
			
		||||
 | 
			
		||||
chrome.runtime.getBackgroundPage( (backgroundPage) =>
 | 
			
		||||
  backgroundPage.apis.errorHandlers.installListenersOn(
 | 
			
		||||
    window, 'CONSENT', () => {
 | 
			
		||||
      agreeBtn.onclick = () => {
 | 
			
		||||
        backgroundPage.apis.consent.give();
 | 
			
		||||
        window.close();
 | 
			
		||||
      }
 | 
			
		||||
      rejectBtn.onclick = () =>
 | 
			
		||||
        chrome.management.uninstallSelf();
 | 
			
		||||
    },
 | 
			
		||||
  ),
 | 
			
		||||
);
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 66 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 10 KiB  | 
| 
						 | 
				
			
			@ -1,60 +1,62 @@
 | 
			
		|||
'use strict';
 | 
			
		||||
 | 
			
		||||
const setStatusTo = (msg) => document.getElementById('status').innerHTML = msg;
 | 
			
		||||
chrome.runtime.getBackgroundPage((bgWin) => {
 | 
			
		||||
 | 
			
		||||
const red = (text) => '<span style="color: red">' + text + '</span>';
 | 
			
		||||
  const setStatusTo = (msg) => document.getElementById('status').innerHTML = msg;
 | 
			
		||||
 | 
			
		||||
const editor = window.ace.edit('editor');
 | 
			
		||||
editor.getSession().setOptions({
 | 
			
		||||
  mode: 'ace/mode/javascript',
 | 
			
		||||
  useSoftTabs: true,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
chrome.proxy.settings.onChange.addListener(
 | 
			
		||||
  (details) => setStatusTo(red( details.levelOfControl + '!') )
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
function _read() {
 | 
			
		||||
 | 
			
		||||
  chrome.proxy.settings.get({}, (details) => {
 | 
			
		||||
 | 
			
		||||
    let control = details.levelOfControl;
 | 
			
		||||
    if (control.startsWith('controlled_by_other')) {
 | 
			
		||||
      control = red(control);
 | 
			
		||||
    }
 | 
			
		||||
    setStatusTo(control);
 | 
			
		||||
    console.log(details);
 | 
			
		||||
    const pac = details.value.pacScript;
 | 
			
		||||
    const data = pac && pac.data || 'PAC скрипт не установлен.';
 | 
			
		||||
    editor.setValue( data );
 | 
			
		||||
  const red = (text) => '<span style="color: red">' + text + '</span>';
 | 
			
		||||
 | 
			
		||||
  const editor = window.ace.edit('editor');
 | 
			
		||||
  editor.getSession().setOptions({
 | 
			
		||||
    mode: 'ace/mode/javascript',
 | 
			
		||||
    useSoftTabs: true,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
  bgWin.chrome.proxy.settings.onChange.addListener(
 | 
			
		||||
    (details) => setStatusTo(red( details.levelOfControl + '!') )
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
document.querySelector('#read-button').onclick = _read;
 | 
			
		||||
  function _read() {
 | 
			
		||||
 | 
			
		||||
document.querySelector('#save-button').onclick = () => {
 | 
			
		||||
    bgWin.chrome.proxy.settings.get({}, (details) => {
 | 
			
		||||
 | 
			
		||||
      let control = details.levelOfControl;
 | 
			
		||||
      if (control.startsWith('controlled_by_other')) {
 | 
			
		||||
        control = red(control);
 | 
			
		||||
      }
 | 
			
		||||
      setStatusTo(control);
 | 
			
		||||
      const pac = details.value.pacScript;
 | 
			
		||||
      const data = pac && pac.data || 'PAC скрипт не установлен.';
 | 
			
		||||
      editor.setValue( data );
 | 
			
		||||
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  document.querySelector('#read-button').onclick = _read;
 | 
			
		||||
 | 
			
		||||
  document.querySelector('#save-button').onclick = () => {
 | 
			
		||||
 | 
			
		||||
    const config = {
 | 
			
		||||
      mode: 'pac_script',
 | 
			
		||||
      pacScript: {
 | 
			
		||||
        mandatory: false,
 | 
			
		||||
        data: editor.getValue(),
 | 
			
		||||
      },
 | 
			
		||||
    };
 | 
			
		||||
    bgWin.chrome.proxy.settings.set( {value: config}, () => alert('Saved!') );
 | 
			
		||||
 | 
			
		||||
  const config = {
 | 
			
		||||
    mode: 'pac_script',
 | 
			
		||||
    pacScript: {
 | 
			
		||||
      mandatory: false,
 | 
			
		||||
      data: editor.getValue(),
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
  chrome.proxy.settings.set( {value: config}, () => alert('Saved!') );
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
  document.querySelector('#clear-button').onclick = () => {
 | 
			
		||||
 | 
			
		||||
document.querySelector('#clear-button').onclick = () => {
 | 
			
		||||
    bgWin.chrome.proxy.settings.clear({}, () => {
 | 
			
		||||
 | 
			
		||||
  chrome.proxy.settings.clear({}, () => {
 | 
			
		||||
      alert('Cleared! Reading...');
 | 
			
		||||
      _read();
 | 
			
		||||
 | 
			
		||||
    alert('Cleared! Reading...');
 | 
			
		||||
    _read();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,7 +17,9 @@ chrome.runtime.getBackgroundPage( (backgroundPage) =>
 | 
			
		|||
# Сначала идёт список проксируемых сайтов,
 | 
			
		||||
# затем ==== на отдельной строке,
 | 
			
		||||
# затем исключённые сайты.
 | 
			
		||||
# После ещё одной строки с ==== идёт белый список.
 | 
			
		||||
# Сортировка — с конца строки.
 | 
			
		||||
# Адреса со звёздочками поддерживаются: *.kasparov.ru, например.
 | 
			
		||||
 | 
			
		||||
# ПРОКСИРОВАТЬ:
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -26,7 +28,16 @@ ${(mods.included || []).join('\n')}
 | 
			
		|||
===============================
 | 
			
		||||
# НЕ ПРОКСИРОВАТЬ:
 | 
			
		||||
 | 
			
		||||
${(mods.excluded || []).join('\n')}`;
 | 
			
		||||
${(mods.excluded || []).join('\n')}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
===============================
 | 
			
		||||
# БЕЛЫЙ СПИСОК
 | 
			
		||||
# Разрешить расширению работать только с этими адресами:
 | 
			
		||||
 | 
			
		||||
${(mods.whitelist || []).join('\n')}
 | 
			
		||||
 | 
			
		||||
`.trim();
 | 
			
		||||
 | 
			
		||||
        status.innerText = 'Успешно загружено!';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -35,7 +46,7 @@ ${(mods.excluded || []).join('\n')}`;
 | 
			
		|||
 | 
			
		||||
      saveBtn.onclick = function() {
 | 
			
		||||
 | 
			
		||||
        let [proxyList, dontProxyList] = editor.value
 | 
			
		||||
        let [proxyList, dontProxyList, whitelist] = editor.value
 | 
			
		||||
          .trim()
 | 
			
		||||
          .replace(/#.*/g, '')
 | 
			
		||||
          .split(/=+/g)
 | 
			
		||||
| 
						 | 
				
			
			@ -45,12 +56,14 @@ ${(mods.excluded || []).join('\n')}`;
 | 
			
		|||
            .filter((host) => host)
 | 
			
		||||
          )
 | 
			
		||||
        dontProxyList = dontProxyList || [];
 | 
			
		||||
        whitelist = whitelist || [];
 | 
			
		||||
 | 
			
		||||
        const exceptions = {};
 | 
			
		||||
        proxyList.forEach((host) => (exceptions[host] = true));
 | 
			
		||||
        dontProxyList.forEach((host) => (exceptions[host] = false));
 | 
			
		||||
        const mods = backgroundPage.apis.pacKitchen.getPacMods();
 | 
			
		||||
        mods.exceptions = exceptions;
 | 
			
		||||
        mods.whitelist = whitelist;
 | 
			
		||||
        backgroundPage.apis.pacKitchen.keepCookedNowAsync(mods, (err) => {
 | 
			
		||||
          if (!err) {
 | 
			
		||||
            status.innerText = 'Успешно сохранено!';
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,7 +16,7 @@ Use only if really required because of performance penalty.
 | 
			
		|||
      const location = ln.href;
 | 
			
		||||
      ln.onclick = function() {
 | 
			
		||||
 | 
			
		||||
        chrome.tabs.create({active: !this.dataset.inBg, url: location});
 | 
			
		||||
        chrome.tabs.create({active: this.dataset.inBg === "false", url: location});
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
      };
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
<!DOCTYPE html>
 | 
			
		||||
<html style="display: none; will-change: contents, display">
 | 
			
		||||
<html style="visibility: hidden; will-change: contents, visibility">
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta charset="utf-8">
 | 
			
		||||
    <title>Настройки</title>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -1,25 +1,27 @@
 | 
			
		|||
{
 | 
			
		||||
  "name": "hello-react",
 | 
			
		||||
  "name": "options-page-builder",
 | 
			
		||||
  "version": "1.0.0",
 | 
			
		||||
  "main": "index.js",
 | 
			
		||||
  "license": "MIT",
 | 
			
		||||
  "license": "GPLv3",
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "babel-cli": "^6.24.1",
 | 
			
		||||
    "babel-loader": "^7.0.0",
 | 
			
		||||
    "babel-plugin-dynamic-import-webpack": "^1.0.1",
 | 
			
		||||
    "babel-cli": "^6.26.0",
 | 
			
		||||
    "babel-loader": "^7.1.5",
 | 
			
		||||
    "babel-plugin-dynamic-import-webpack": "^1.1.0",
 | 
			
		||||
    "babel-preset-flow": "^6.23.0",
 | 
			
		||||
    "babel-preset-react": "^6.24.1",
 | 
			
		||||
    "concat-stream": "^1.6.0",
 | 
			
		||||
    "concat-stream": "^1.6.2",
 | 
			
		||||
    "csjs-inject": "^1.0.1",
 | 
			
		||||
    "flow-bin": "^0.45.0",
 | 
			
		||||
    "gulp": "^4.0.2",
 | 
			
		||||
    "inferno": "~3.2.0",
 | 
			
		||||
    "inferno-component": "^3.1.2",
 | 
			
		||||
    "inferno-create-element": "^3.1.2",
 | 
			
		||||
    "webpack": "^2.5.1"
 | 
			
		||||
    "inferno-component": "^3.10.1",
 | 
			
		||||
    "inferno-create-element": "^3.10.1",
 | 
			
		||||
    "webpack": "^5.82.0",
 | 
			
		||||
    "webpack-cli": "^5.0.2"
 | 
			
		||||
  },
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "check": "flow status",
 | 
			
		||||
    "build:prod": "webpack --define process.env.NODE_ENV=\"'production'\" --env=prod",
 | 
			
		||||
    "build:prod": "webpack --node-env=\"'production'\" --env=prod",
 | 
			
		||||
    "build:dev:nocomp": "NODE_ENV=development webpack --define process.env.NODE_ENV=\"'development'\" --env=dev",
 | 
			
		||||
    "build:dev": "NODE_ENV=development webpack --debug --define process.env.NODE_ENV=\"'development'\" --output-pathinfo --env=dev",
 | 
			
		||||
    "gulp": "cd .. && npm run gulp",
 | 
			
		||||
| 
						 | 
				
			
			@ -27,6 +29,6 @@
 | 
			
		|||
    "start": "cd .. && npm start"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "babel-plugin-inferno": "^3.2.0"
 | 
			
		||||
    "babel-plugin-inferno": "^3.5.1"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,12 +17,15 @@ export default function getApp(theState) {
 | 
			
		|||
    constructor(props) {
 | 
			
		||||
 | 
			
		||||
      super(props);
 | 
			
		||||
      const hash = window.location.hash.substr(1);
 | 
			
		||||
      const hashParams = new URLSearchParams(hash);
 | 
			
		||||
 | 
			
		||||
      const sanitizedUrl = theState.flags.ifOpenedUnsafely ? { hash: '', search: '' } : window.location;
 | 
			
		||||
      const hashParams = new URLSearchParams(sanitizedUrl.hash.substr(1));
 | 
			
		||||
      const searchParams = new URLSearchParams(sanitizedUrl.search.substr(1));
 | 
			
		||||
      this.state = {
 | 
			
		||||
        status: 'Загрузка...',
 | 
			
		||||
        ifInputsDisabled: false,
 | 
			
		||||
        hashParams: hashParams,
 | 
			
		||||
        hashParams,
 | 
			
		||||
        searchParams,
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      this.setStatusTo = this.setStatusTo.bind(this);
 | 
			
		||||
| 
						 | 
				
			
			@ -47,7 +50,10 @@ export default function getApp(theState) {
 | 
			
		|||
 | 
			
		||||
      this.setStatusTo(
 | 
			
		||||
        <ol style="list-style-type: initial;">
 | 
			
		||||
          {newsArr.map(([title, url]) => (<li><a href={url}>{title}</a></li>))}
 | 
			
		||||
          {newsArr
 | 
			
		||||
            .map(([title, url]) => (<li><a href={url}>{title}</a></li>))
 | 
			
		||||
            .reverse() // News order.
 | 
			
		||||
          }
 | 
			
		||||
        </ol>
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -59,16 +65,16 @@ export default function getApp(theState) {
 | 
			
		|||
      const uiComEtag = 'ui-last-comments-etag';
 | 
			
		||||
      const uiLastNewsArr = 'ui-last-news-arr';
 | 
			
		||||
 | 
			
		||||
      const statusFromHash = this.state.hashParams.get('status');
 | 
			
		||||
      if (statusFromHash) {
 | 
			
		||||
        return this.setStatusTo(statusFromHash);
 | 
			
		||||
      const statusFromUrl = this.state.searchParams.get('status');
 | 
			
		||||
      if (statusFromUrl) {
 | 
			
		||||
        return this.setStatusTo(statusFromUrl);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const comDate = localStorage[uiComDate];
 | 
			
		||||
      const query = comDate ? `?since=${comDate}` : '';
 | 
			
		||||
      const oldEtag = localStorage[uiComEtag];
 | 
			
		||||
      const headers = {
 | 
			
		||||
        'User-Agent': 'anticensorship-russia',
 | 
			
		||||
        'User-Agent': 'https://github.com/anticensority/runet-censorship-bypass',
 | 
			
		||||
      };
 | 
			
		||||
      if (oldEtag) {
 | 
			
		||||
        Object.assign(headers, {
 | 
			
		||||
| 
						 | 
				
			
			@ -79,8 +85,9 @@ export default function getApp(theState) {
 | 
			
		|||
        headers: new Headers(headers),
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      //const ghUrl = `https://api.github.com/repos/anticensority/chromium-extension/issues/10/comments${query}`;
 | 
			
		||||
      const ghUrl = `https://api.github.com/repos/anticensority/for-testing/issues/1/comments${query}`;
 | 
			
		||||
      // I comment and uncomment this variable manually before release or build:
 | 
			
		||||
      const ghUrl = `https://api.github.com/repos/anticensority/chromium-extension/issues/10/comments${query}`;
 | 
			
		||||
      // const ghUrl = `https://api.github.com/repos/anticensority/for-testing/issues/1/comments${query}`;
 | 
			
		||||
 | 
			
		||||
      const [error, comments, etag] = await fetch(
 | 
			
		||||
        ghUrl,
 | 
			
		||||
| 
						 | 
				
			
			@ -223,7 +230,10 @@ export default function getApp(theState) {
 | 
			
		|||
      this.setStatusTo(
 | 
			
		||||
        (<span>
 | 
			
		||||
          <span style="color:red">
 | 
			
		||||
            {err ? <span><span class="emoji">🔥</span> Ошибка!</span> : 'Некритичная oшибка.'}
 | 
			
		||||
            {err
 | 
			
		||||
              ? <span><span class="emoji">🔥</span> {chrome.i18n.getMessage('Error')}!</span>
 | 
			
		||||
              : `${chrome.i18n.getMessage('Non_critical_error')}.`
 | 
			
		||||
            }
 | 
			
		||||
          </span>
 | 
			
		||||
          <br/>
 | 
			
		||||
          <span style="font-size: 0.9em; color: darkred" dangerouslySetInnerHTML={{__html: messageHtml}}></span>
 | 
			
		||||
| 
						 | 
				
			
			@ -256,7 +266,6 @@ export default function getApp(theState) {
 | 
			
		|||
      this.setStatusTo(beforeStatus);
 | 
			
		||||
      this.switchInputs('off');
 | 
			
		||||
      operation((err, res, ...warns) => {
 | 
			
		||||
 | 
			
		||||
        warns = warns.filter( (w) => w );
 | 
			
		||||
        if (err || warns.length) {
 | 
			
		||||
          this.showErrors(err, ...warns);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@ export default function getApplyMods(theState) {
 | 
			
		|||
 | 
			
		||||
  const resetMods = function resetMods(props) {
 | 
			
		||||
 | 
			
		||||
    const ifSure = props.bgWindow.confirm('Сбросиь все модификаторы и ИСКЛЮЧЕНИЯ?');
 | 
			
		||||
    const ifSure = props.bgWindow.confirm('Сбросить все модификаторы и ИСКЛЮЧЕНИЯ?');
 | 
			
		||||
    if (!ifSure) {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -75,13 +75,18 @@ export default function getExcEditor(theState) {
 | 
			
		|||
    constructor(props) {
 | 
			
		||||
 | 
			
		||||
      super(props);
 | 
			
		||||
      const trimmedInputValueOrSpace =
 | 
			
		||||
        props.currentTab &&
 | 
			
		||||
        props.currentTab.url &&
 | 
			
		||||
        !props.currentTab.url.startsWith('chrome')
 | 
			
		||||
          ? '*.' + (new URL(props.currentTab.url).hostname.replace(/^www\./g, ''))
 | 
			
		||||
          : '';
 | 
			
		||||
 | 
			
		||||
      const pacMods = props.apis.pacKitchen.getPacMods();
 | 
			
		||||
      this.state = {
 | 
			
		||||
        trimmedInputValueOrSpace:
 | 
			
		||||
          props.currentTab && !props.currentTab.url.startsWith('chrome') ? new URL(props.currentTab.url).hostname : '',
 | 
			
		||||
        trimmedInputValueOrSpace,
 | 
			
		||||
        sortedListOfOptions: this.modsToOpts(pacMods),
 | 
			
		||||
        isHostHidden: {}
 | 
			
		||||
        hostToIfHidden: {},
 | 
			
		||||
      };
 | 
			
		||||
      this.handleRadioClick = this.handleRadioClick.bind(this);
 | 
			
		||||
      this.handleInputOrClick = this.handleInputOrClick.bind(this);
 | 
			
		||||
| 
						 | 
				
			
			@ -91,11 +96,11 @@ export default function getExcEditor(theState) {
 | 
			
		|||
    hideAllOptions() {
 | 
			
		||||
 | 
			
		||||
      this.setState({
 | 
			
		||||
        isHostHidden: this.state.sortedListOfOptions.reduce(
 | 
			
		||||
          (isHostHidden, [excHost]) => {
 | 
			
		||||
        hostToIfHidden: this.state.sortedListOfOptions.reduce(
 | 
			
		||||
          (hostToIfHidden, [excHost]) => {
 | 
			
		||||
 | 
			
		||||
            isHostHidden[excHost] = true;
 | 
			
		||||
            return isHostHidden;
 | 
			
		||||
            hostToIfHidden[excHost] = true;
 | 
			
		||||
            return hostToIfHidden;
 | 
			
		||||
 | 
			
		||||
          },
 | 
			
		||||
        {}),
 | 
			
		||||
| 
						 | 
				
			
			@ -105,7 +110,7 @@ export default function getExcEditor(theState) {
 | 
			
		|||
 | 
			
		||||
    isHostValid(host) {
 | 
			
		||||
 | 
			
		||||
      const ValidHostnameRegex = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/;
 | 
			
		||||
      const ValidHostnameRegex = /^(?:\*\.)?(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/;
 | 
			
		||||
      if(!ValidHostnameRegex.test(host)) {
 | 
			
		||||
        this.props.funs.showErrors(new TypeError('Должно быть только доменное имя, без протокола, порта и пути. Попробуйте ещё раз.'));
 | 
			
		||||
        return false;
 | 
			
		||||
| 
						 | 
				
			
			@ -137,7 +142,7 @@ export default function getExcEditor(theState) {
 | 
			
		|||
          case 'this-no':
 | 
			
		||||
            if (ifYesClicked && !pacMods.filteredCustomsString) {
 | 
			
		||||
              this.props.funs.showErrors( new TypeError(
 | 
			
		||||
                'Проксировать СВОИ сайты можно только при наличии СВОИХ прокси. Нет своих прокси, удовлетворяющих вашим требованиям.'
 | 
			
		||||
                chrome.i18n.getMessage('noOwnProxiesError'),
 | 
			
		||||
              ));
 | 
			
		||||
              return false;
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -205,7 +210,9 @@ export default function getExcEditor(theState) {
 | 
			
		|||
      const ifInit = !event;
 | 
			
		||||
      const currentHost = ifTriangleClicked ? '' : (trimmedInput || (ifInit ? '' : ' '));
 | 
			
		||||
      setInputValue(currentHost);
 | 
			
		||||
      this.setState({trimmedInputValueOrSpace: currentHost});
 | 
			
		||||
      this.setState({
 | 
			
		||||
        trimmedInputValueOrSpace: currentHost,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      // Episode 2.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -274,7 +281,7 @@ export default function getExcEditor(theState) {
 | 
			
		|||
      })();
 | 
			
		||||
 | 
			
		||||
      this.setState({
 | 
			
		||||
        isHostHidden: hidden,
 | 
			
		||||
        hostToIfHidden: hidden,
 | 
			
		||||
        sortedListOfOptions: options,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -293,10 +300,10 @@ export default function getExcEditor(theState) {
 | 
			
		|||
 | 
			
		||||
      return (
 | 
			
		||||
        <section style="padding-bottom: 1em;">
 | 
			
		||||
          <div>Проксировать указанный сайт?</div>
 | 
			
		||||
          <div>{chrome.i18n.getMessage('ProxyTheDomainNameBelowQ')}</div>
 | 
			
		||||
          <div id="exc-address-container">
 | 
			
		||||
            <div id="exc-address" class={inputProxyingState !== undefined ? ( inputProxyingState === true ? scopedCss.ifYes : scopedCss.ifNo ) : ''}>
 | 
			
		||||
              <span>*.</span><input placeholder="navalny.com" list="exc-list" id="exc-editor"
 | 
			
		||||
              <input placeholder="*.navalny.com" list="exc-list" id="exc-editor"
 | 
			
		||||
                value={this.state.trimmedInputValueOrSpace}
 | 
			
		||||
                ref={(inputNode) => { this.rawInput = inputNode; }}
 | 
			
		||||
                onKeyDown={this.handleKeyDown.bind(this)}
 | 
			
		||||
| 
						 | 
				
			
			@ -316,9 +323,10 @@ export default function getExcEditor(theState) {
 | 
			
		|||
 | 
			
		||||
                // 1. Option's value may be changed to hide it from the tooltip.
 | 
			
		||||
                // 2. Space is used in matching so even an empty input (replaced with space) has tooltip with prompts.
 | 
			
		||||
                const ifProxy = excState;
 | 
			
		||||
                return <option
 | 
			
		||||
                  value={ this.state.isHostHidden[excHost] ? '\n' : excHost + ' ' }
 | 
			
		||||
                  label={ excState === true ? labelIfProxied : (excState === false ? labelIfNotProxied : labelIfAuto) }/>
 | 
			
		||||
                  value={ this.state.hostToIfHidden[excHost] ? '\n' : excHost + ' ' }
 | 
			
		||||
                  label={ ifProxy === true ? labelIfProxied : (ifProxy === false ? labelIfNotProxied : labelIfAuto) }/>
 | 
			
		||||
 | 
			
		||||
              })
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -327,19 +335,19 @@ export default function getExcEditor(theState) {
 | 
			
		|||
            <li><input id="this-auto" type="radio" checked name="if-proxy-this-site" onClick={this.handleRadioClick}/>{' '}
 | 
			
		||||
                  <label for="this-auto">{/*<span class="emoji">🔄(looks fat)</span>*/}<svg
 | 
			
		||||
                    class="icon"
 | 
			
		||||
                    style="position: relative; top: 0.15em;"><use xlink:href="#iconLoopRound"></use></svg> авто</label>
 | 
			
		||||
                    style="position: relative; top: 0.15em;"><use xlink:href="#iconLoopRound"></use></svg> {chrome.i18n.getMessage('auto')}</label>
 | 
			
		||||
            </li>
 | 
			
		||||
            <li>
 | 
			
		||||
              <input id="this-yes" type="radio" name="if-proxy-this-site" checked={inputProxyingState === true} onClick={this.handleRadioClick}/>
 | 
			
		||||
              {' '}<label for="this-yes">
 | 
			
		||||
                    <span
 | 
			
		||||
                      class="emoji____buggy"
 | 
			
		||||
                    >✔</span> да
 | 
			
		||||
                    >✔</span> {chrome.i18n.getMessage('yes')}
 | 
			
		||||
                  </label>
 | 
			
		||||
            </li>
 | 
			
		||||
            <li>
 | 
			
		||||
              <input id="this-no" type="radio" name="if-proxy-this-site" checked={inputProxyingState === false} onClick={this.handleRadioClick}/>
 | 
			
		||||
              {' '}<label for="this-no"><span class="emoji">✘</span> нет</label></li>
 | 
			
		||||
              {' '}<label for="this-no"><span class="emoji">✘</span> {chrome.i18n.getMessage('no')}</label></li>
 | 
			
		||||
          </ol>
 | 
			
		||||
        </section>
 | 
			
		||||
      );
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -67,7 +67,7 @@ export default function getExceptions(theState) {
 | 
			
		|||
                <InfoLi
 | 
			
		||||
                  type="checkbox"
 | 
			
		||||
                  conf={{
 | 
			
		||||
                    label: '<span>Собирать <a href="../errors-to-exc/index.html">последние ошибки</a> сайтов</span>',
 | 
			
		||||
                    label: '<span>Собирать <a data-in-bg="false" href="../errors-to-exc/index.html">последние ошибки</a> сайтов</span>',
 | 
			
		||||
                    key: 'lookupLastErrors',
 | 
			
		||||
                    desc: 'Собирать последние ошибки в запросах, чтобы вручную добавлять избранные из них в исключения.',
 | 
			
		||||
                  }}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
import Inferno from 'inferno';
 | 
			
		||||
import css from 'csjs-inject';
 | 
			
		||||
 | 
			
		||||
export default function getFooter() {
 | 
			
		||||
export default function getFooter(theState) {
 | 
			
		||||
 | 
			
		||||
  const scopedCss = css`
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -17,23 +17,22 @@ export default function getFooter() {
 | 
			
		|||
 | 
			
		||||
  `;
 | 
			
		||||
 | 
			
		||||
  return function (props) {
 | 
			
		||||
  return (props) => (
 | 
			
		||||
    <div class="horPadded">
 | 
			
		||||
      <section class={scopedCss.statusRow}>
 | 
			
		||||
        <div class={scopedCss.status} style="will-change: contents">
 | 
			
		||||
          {typeof(props.status) === 'string' ? <div dangerouslySetInnerHTML={{ __html: props.status }}></div> : props.status}
 | 
			
		||||
        </div>
 | 
			
		||||
      </section>
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <div class="horPadded">
 | 
			
		||||
        <section class={scopedCss.statusRow}>
 | 
			
		||||
          <div clss={scopedCss.status} style="will-change: contents">{props.status}</div>
 | 
			
		||||
        </section>
 | 
			
		||||
 | 
			
		||||
        <footer class={scopedCss.controlRow + ' horFlex nowrap'}>
 | 
			
		||||
          <input type="button" value="Готово" disabled={props.ifInputsDisabled} onClick={() => window.close()} />
 | 
			
		||||
          <a href="../troubleshoot/index.html">
 | 
			
		||||
            Проблемы?
 | 
			
		||||
          </a>
 | 
			
		||||
        </footer>
 | 
			
		||||
      </div>
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
  };
 | 
			
		||||
      <footer class={scopedCss.controlRow + ' horFlex nowrap'}>
 | 
			
		||||
        <input type="button" value={chrome.i18n.getMessage('Finish')} disabled={props.ifInputsDisabled} style={{ display: theState.flags.ifInsideEdgeOptionsPage ? 'none' : 'initial' }} onClick={() => window.close()} />
 | 
			
		||||
        <a href="https://github.com/anticensority/runet-censorship-bypass/wiki/Поддержать">{chrome.i18n.getMessage('Donate')}</a>
 | 
			
		||||
        <a data-in-bg="false" href="../troubleshoot/index.html">
 | 
			
		||||
          {chrome.i18n.getMessage('ProblemsQ')}
 | 
			
		||||
        </a>
 | 
			
		||||
      </footer>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,6 +25,9 @@ export default function getInfoLi() {
 | 
			
		|||
    .infoRow {
 | 
			
		||||
      position: relative;
 | 
			
		||||
    }
 | 
			
		||||
    .infoRow a {
 | 
			
		||||
      text-decoration: underline;
 | 
			
		||||
    }
 | 
			
		||||
    .infoRow > input[type="checkbox"] {
 | 
			
		||||
      position: relative;
 | 
			
		||||
      top: -0.08em;
 | 
			
		||||
| 
						 | 
				
			
			@ -49,7 +52,7 @@ export default function getInfoLi() {
 | 
			
		|||
      position: absolute;
 | 
			
		||||
      white-space: initial;
 | 
			
		||||
      word-break: initial;
 | 
			
		||||
      top: 100%;
 | 
			
		||||
      /* top: 100%; Commented to get rid of bug when tooltip is placed below InfoLi children fields. */
 | 
			
		||||
      left: 0;
 | 
			
		||||
      right: 1em;
 | 
			
		||||
      z-index: 1;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,8 +7,10 @@ export default function getLastUpdateDate(theState) {
 | 
			
		|||
 | 
			
		||||
    componentWillMount() {
 | 
			
		||||
 | 
			
		||||
      this.onStorageChangedHandler = (changes) =>
 | 
			
		||||
        changes.lastPacUpdateStamp.newValue && this.forceUpdate();
 | 
			
		||||
      this.onStorageChangedHandler = (changes) => {
 | 
			
		||||
        const ac = changes.antiCensorRu;
 | 
			
		||||
        return ac && ac.newValue && ac.newValue.lastPacUpdateStamp && this.forceUpdate();
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      chrome.storage.onChanged.addListener( this.onStorageChangedHandler );
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -22,17 +24,17 @@ export default function getLastUpdateDate(theState) {
 | 
			
		|||
 | 
			
		||||
    getDate(antiCensorRu) {
 | 
			
		||||
 | 
			
		||||
      let dateForUser = 'никогда';
 | 
			
		||||
      let dateForUser = chrome.i18n.getMessage('never');
 | 
			
		||||
      if( antiCensorRu.lastPacUpdateStamp ) {
 | 
			
		||||
        let diff = Date.now() - antiCensorRu.lastPacUpdateStamp;
 | 
			
		||||
        let units = 'мс';
 | 
			
		||||
        let units = chrome.i18n.getMessage('ms');
 | 
			
		||||
        const gauges = [
 | 
			
		||||
          [1000, 'с'],
 | 
			
		||||
          [60, 'мин'],
 | 
			
		||||
          [60, 'ч'],
 | 
			
		||||
          [24, 'дн'],
 | 
			
		||||
          [7, ' недель'],
 | 
			
		||||
          [4, ' месяцев'],
 | 
			
		||||
          [1000, chrome.i18n.getMessage('s')],
 | 
			
		||||
          [60,   chrome.i18n.getMessage('min')],
 | 
			
		||||
          [60,   chrome.i18n.getMessage('h')],
 | 
			
		||||
          [24,   chrome.i18n.getMessage('d')],
 | 
			
		||||
          [7,    chrome.i18n.getMessage('w')],
 | 
			
		||||
          [4,    chrome.i18n.getMessage('m')],
 | 
			
		||||
        ];
 | 
			
		||||
        for(const g of gauges) {
 | 
			
		||||
          const diffy = Math.floor(diff / g[0]);
 | 
			
		||||
| 
						 | 
				
			
			@ -41,10 +43,10 @@ export default function getLastUpdateDate(theState) {
 | 
			
		|||
          diff = diffy;
 | 
			
		||||
          units = g[1];
 | 
			
		||||
        }
 | 
			
		||||
        dateForUser = diff + units + ' назад';
 | 
			
		||||
        dateForUser = diff + units + ' ' + chrome.i18n.getMessage('ago');
 | 
			
		||||
      }
 | 
			
		||||
      return {
 | 
			
		||||
        text: `${dateForUser} / ${antiCensorRu.pacUpdatePeriodInMinutes/60}ч`,
 | 
			
		||||
        text: `${dateForUser} / ${antiCensorRu.pacUpdatePeriodInMinutes/60}${chrome.i18n.getMessage('h')}`,
 | 
			
		||||
        title: new Date(antiCensorRu.lastPacUpdateStamp).toLocaleString('ru-RU'),
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -53,7 +55,7 @@ export default function getLastUpdateDate(theState) {
 | 
			
		|||
    render(props) {
 | 
			
		||||
 | 
			
		||||
      const date = this.getDate(props.apis.antiCensorRu);
 | 
			
		||||
      return (<div>Обновлялись: <span class="updateDate" title={date.title}>{ date.text }</span></div>);
 | 
			
		||||
      return (<div>{chrome.i18n.getMessage('Updated')}: <span class="updateDate" title={date.title}>{ date.text }</span></div>);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,6 +31,7 @@ export default function getMain(theState) {
 | 
			
		|||
  const Notifications = getNotifications(theState);
 | 
			
		||||
 | 
			
		||||
  const checksName = 'pacMods';
 | 
			
		||||
  let selection = [0, 0]; // TODO: dirty hack but seems ok.
 | 
			
		||||
 | 
			
		||||
  return class Main extends Component {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -144,23 +145,43 @@ export default function getMain(theState) {
 | 
			
		|||
      return createElement(TabPanel, Object.assign({}, props, {
 | 
			
		||||
        tabs: [
 | 
			
		||||
          {
 | 
			
		||||
            label: 'PAC-скрипт',
 | 
			
		||||
            label: chrome.i18n.getMessage('PAC_script'),
 | 
			
		||||
            content: createElement(PacChooser, props),
 | 
			
		||||
            key: 'pacScript',
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            label: 'Исключения',
 | 
			
		||||
            label: chrome.i18n.getMessage('Exceptions'),
 | 
			
		||||
            content: createElement(Exceptions, props),
 | 
			
		||||
            key: 'exceptions',
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            label: 'Свои прокси',
 | 
			
		||||
            label: chrome.i18n.getMessage('Own_proxies'),
 | 
			
		||||
            content: createElement(
 | 
			
		||||
              ModList,
 | 
			
		||||
              Object.assign({}, props, {
 | 
			
		||||
                orderedConfigs: this.state.catToOrderedMods['ownProxies'],
 | 
			
		||||
                childrenOfMod: {
 | 
			
		||||
                  customProxyStringRaw: ProxyEditor,
 | 
			
		||||
                  replaceDirectWith: ({ conf, onNewValue, ifInputsDisabled }) =>
 | 
			
		||||
                    (<input
 | 
			
		||||
                      style="width: 100%; margin: 0.5em 0"
 | 
			
		||||
                      disabled={ifInputsDisabled}
 | 
			
		||||
                      value={conf.value || ''}
 | 
			
		||||
                      onInput={(event) => {
 | 
			
		||||
 | 
			
		||||
                        const t = event.target;
 | 
			
		||||
                        selection = [t.selectionStart, t.selectionEnd];
 | 
			
		||||
                        onNewValue(true, t.value);
 | 
			
		||||
                      }}
 | 
			
		||||
                      ref={(input) => {
 | 
			
		||||
 | 
			
		||||
                        if (input) {
 | 
			
		||||
                          input.focus();
 | 
			
		||||
                          input.selectionStart = selection[0];
 | 
			
		||||
                          input.selectionEnd = selection[1];
 | 
			
		||||
                        }
 | 
			
		||||
                      }}
 | 
			
		||||
                    />),
 | 
			
		||||
                },
 | 
			
		||||
                name: checksName,
 | 
			
		||||
              }, modsHandlers)
 | 
			
		||||
| 
						 | 
				
			
			@ -168,7 +189,7 @@ export default function getMain(theState) {
 | 
			
		|||
            key: 'ownProxies',
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            label: 'Модификаторы',
 | 
			
		||||
            label: chrome.i18n.getMessage('Modifiers'),
 | 
			
		||||
            content: createElement(
 | 
			
		||||
              ModList,
 | 
			
		||||
              Object.assign({}, props, {
 | 
			
		||||
| 
						 | 
				
			
			@ -183,7 +204,7 @@ export default function getMain(theState) {
 | 
			
		|||
            key: 'applyMods',
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            label: 'Уведомления',
 | 
			
		||||
            label: chrome.i18n.getMessage('Notifications'),
 | 
			
		||||
            content: createElement(Notifications, props),
 | 
			
		||||
            key: 'notifications',
 | 
			
		||||
          },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -55,7 +55,14 @@ export default function getModList(theState) {
 | 
			
		|||
            const child = ifMayHaveChild && this.state.checks[index]
 | 
			
		||||
              && createElement(
 | 
			
		||||
                props.childrenOfMod[conf.key],
 | 
			
		||||
                Object.assign({}, props, {conf, onNewValue: (ifValid, newValue) => this.handleNewValue(ifValid, confMeta, newValue)})
 | 
			
		||||
                Object.assign(
 | 
			
		||||
                  {},
 | 
			
		||||
                  props,
 | 
			
		||||
                  {
 | 
			
		||||
                    conf,
 | 
			
		||||
                    onNewValue: (ifValid, newValue) => this.handleNewValue(ifValid, confMeta, newValue),
 | 
			
		||||
                  },
 | 
			
		||||
                )
 | 
			
		||||
              );
 | 
			
		||||
 | 
			
		||||
            return (<InfoLi
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -55,16 +55,16 @@ export default function getPacChooser(theState) {
 | 
			
		|||
 | 
			
		||||
    constructor(props) {
 | 
			
		||||
 | 
			
		||||
      super();
 | 
			
		||||
      super(props);
 | 
			
		||||
      this.state = {
 | 
			
		||||
        chosenPacName: 'none',
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      this.updatePac = function updatePac(onSuccess) {
 | 
			
		||||
        props.funs.conduct(
 | 
			
		||||
          'Обновляем...',
 | 
			
		||||
          (cb) => props.apis.antiCensorRu.syncWithPacProviderAsync(cb),
 | 
			
		||||
          'Обновлено.',
 | 
			
		||||
          chrome.i18n.getMessage('UpdatingDDD'),
 | 
			
		||||
          (cb) => theState.apis.antiCensorRu.syncWithPacProviderAsync(cb),
 | 
			
		||||
          chrome.i18n.getMessage('UpdatedD'),
 | 
			
		||||
          onSuccess
 | 
			
		||||
        );
 | 
			
		||||
      };
 | 
			
		||||
| 
						 | 
				
			
			@ -75,7 +75,7 @@ export default function getPacChooser(theState) {
 | 
			
		|||
 | 
			
		||||
    getCurrentProviderId() {
 | 
			
		||||
 | 
			
		||||
      return this.props.apis.antiCensorRu.getCurrentPacProviderKey() || 'none';
 | 
			
		||||
      return theState.apis.antiCensorRu.getCurrentPacProviderKey() || 'none';
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -94,24 +94,24 @@ export default function getPacChooser(theState) {
 | 
			
		|||
      const pacKey = event.target.id;
 | 
			
		||||
      if (
 | 
			
		||||
        pacKey === (
 | 
			
		||||
          this.props.apis.antiCensorRu.getCurrentPacProviderKey() || 'none'
 | 
			
		||||
          theState.apis.antiCensorRu.getCurrentPacProviderKey() || 'none'
 | 
			
		||||
        )
 | 
			
		||||
      ) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
      if (pacKey === 'none') {
 | 
			
		||||
        this.props.funs.conduct(
 | 
			
		||||
          'Отключение...',
 | 
			
		||||
          (cb) => this.props.apis.antiCensorRu.clearPacAsync(cb),
 | 
			
		||||
          'Отключено.',
 | 
			
		||||
          chrome.i18n.getMessage('DisablingDDD'),
 | 
			
		||||
          (cb) => theState.apis.antiCensorRu.clearPacAsync(cb),
 | 
			
		||||
          chrome.i18n.getMessage('DisabledD'),
 | 
			
		||||
          () => this.setState({ chosenPacName: 'none' }),
 | 
			
		||||
          checkChosenProvider
 | 
			
		||||
        );
 | 
			
		||||
      } else {
 | 
			
		||||
        this.props.funs.conduct(
 | 
			
		||||
          'Установка...',
 | 
			
		||||
          (cb) => this.props.apis.antiCensorRu.installPacAsync(pacKey, cb),
 | 
			
		||||
          'PAC-скрипт установлен.',
 | 
			
		||||
          chrome.i18n.getMessage('InstallingDDD'),
 | 
			
		||||
          (cb) => theState.apis.antiCensorRu.installPacAsync(pacKey, cb),
 | 
			
		||||
          chrome.i18n.getMessage('PacScriptWasInstalledD'),
 | 
			
		||||
          checkChosenProvider
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
| 
						 | 
				
			
			@ -124,10 +124,10 @@ export default function getPacChooser(theState) {
 | 
			
		|||
      const iddyToCheck = this.getCurrentProviderId();
 | 
			
		||||
      return (
 | 
			
		||||
        <div>
 | 
			
		||||
          {props.flags.ifInsideOptionsPage && (<header>PAC-скрипт:</header>)}
 | 
			
		||||
          {props.flags.ifInsideOptionsPage && (<header>{chrome.i18n.getMessage('PAC_script')}:</header>)}
 | 
			
		||||
          <ul>
 | 
			
		||||
            {
 | 
			
		||||
              [...props.apis.antiCensorRu.getSortedEntriesForProviders(), {key: 'none', label: 'Отключить'}].map((provConf) =>
 | 
			
		||||
              [...theState.apis.antiCensorRu.getSortedEntriesForProviders(), {key: 'none', label: chrome.i18n.getMessage('Disable')}].map((provConf) =>
 | 
			
		||||
                (<InfoLi
 | 
			
		||||
                  onClick={this.radioClickHandler}
 | 
			
		||||
                  conf={provConf}
 | 
			
		||||
| 
						 | 
				
			
			@ -135,7 +135,7 @@ export default function getPacChooser(theState) {
 | 
			
		|||
                  name="pacProvider"
 | 
			
		||||
                  checked={iddyToCheck === provConf.key}
 | 
			
		||||
                  ifInputsDisabled={props.ifInputsDisabled}
 | 
			
		||||
                  nodeAfterLabel={<a href="" class={scopedCss.updateButton} onClick={this.updateClickHandler}>[обновить]</a>}
 | 
			
		||||
                  nodeAfterLabel={<a href="" class={scopedCss.updateButton} onClick={this.updateClickHandler}>[{chrome.i18n.getMessage('update')}]</a>}
 | 
			
		||||
                />)
 | 
			
		||||
              )
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -145,10 +145,10 @@ export default function getPacChooser(theState) {
 | 
			
		|||
            <div class={scopedCss.fullLineHeight}>
 | 
			
		||||
              {
 | 
			
		||||
                props.flags.ifMini
 | 
			
		||||
                  ? (<a class={scopedCss.otherVersion + ' emoji'} href="https://rebrand.ly/ac-versions"
 | 
			
		||||
                      title="Полная версия">🏋</a>)
 | 
			
		||||
                  : (<a class={scopedCss.otherVersion + ' emoji'} href="https://rebrand.ly/ac-versions"
 | 
			
		||||
                      title="Версия для слабых машин">🐌</a>)
 | 
			
		||||
                  ? (<a class={scopedCss.otherVersion + ' emoji'} href="https://github.com/anticensority/runet-censorship-bypass/wiki/Различные-версии-расширения"
 | 
			
		||||
                      title={chrome.i18n.getMessage("FullVersion")}>🏋</a>)
 | 
			
		||||
                  : (<a class={scopedCss.otherVersion + ' emoji'} href="https://github.com/anticensority/runet-censorship-bypass/wiki/Различные-версии-расширения"
 | 
			
		||||
                      title={chrome.i18n.getMessage("VersionForSlowMachines")}>🐌</a>)
 | 
			
		||||
              }
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -159,7 +159,7 @@ export default function getPacChooser(theState) {
 | 
			
		|||
 | 
			
		||||
    componentDidMount() {
 | 
			
		||||
 | 
			
		||||
      if (this.props.apis.antiCensorRu.ifFirstInstall) {
 | 
			
		||||
      if (theState.apis.antiCensorRu.ifFirstInstall) {
 | 
			
		||||
        this.updatePac();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -64,7 +64,7 @@ export default function getProxyEditor(theState) {
 | 
			
		|||
    {
 | 
			
		||||
      text-align: center;
 | 
			
		||||
    }
 | 
			
		||||
    table.editor tr.proxyRow input[name="hostname"] {
 | 
			
		||||
    table.editor tr.proxyRow input[name="crededHostname"] {
 | 
			
		||||
      padding: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -158,8 +158,14 @@ export default function getProxyEditor(theState) {
 | 
			
		|||
    return true;
 | 
			
		||||
 | 
			
		||||
  };
 | 
			
		||||
  const splitBySemi = (proxyString) => proxyString.replace(/#.*$/mg, '').trim().split(/\s*;\s*/g).filter((s) => s);
 | 
			
		||||
  const joinBySemi = (strs) => strs.join(';\n') + ';';
 | 
			
		||||
  const splitBySemi = (proxyString) => proxyString
 | 
			
		||||
    .replace(/#.*$/mg, '')
 | 
			
		||||
    .trim()
 | 
			
		||||
    .split(/\s*;\r?\n\s*/g)
 | 
			
		||||
    .map((s) => s.trim())
 | 
			
		||||
    .filter((s) => s);
 | 
			
		||||
 | 
			
		||||
  const joinBySemi = (strs) => strs.join(';\n');
 | 
			
		||||
  const normalizeProxyString = (str) => joinBySemi(splitBySemi(str));
 | 
			
		||||
 | 
			
		||||
  const PROXY_TYPE_LABEL_PAIRS = [['PROXY', 'PROXY/HTTP'],['HTTPS'],['SOCKS4'],['SOCKS5'],['SOCKS']];
 | 
			
		||||
| 
						 | 
				
			
			@ -217,11 +223,11 @@ export default function getProxyEditor(theState) {
 | 
			
		|||
 | 
			
		||||
      }, {});
 | 
			
		||||
      const type = that.state.selectedNewType;
 | 
			
		||||
      const hostname = elements.newHostname;
 | 
			
		||||
      const crededHostname = elements.newHostname;
 | 
			
		||||
      const port = elements.newPort;
 | 
			
		||||
 | 
			
		||||
      const newValue = `${that.props.proxyStringRaw}; ${type} ${hostname}:${port}`
 | 
			
		||||
        .trim().replace(/(\s*;\s*)+/, '; ');
 | 
			
		||||
      const newValue = `${that.props.proxyStringRaw};\n${type} ${crededHostname}:${port}`
 | 
			
		||||
        .trim().replace(/(\s*;\n\s*)+/, ';\n');
 | 
			
		||||
      that.props.setProxyStringRaw(true, newValue);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -322,10 +328,17 @@ export default function getProxyEditor(theState) {
 | 
			
		|||
              </tr>
 | 
			
		||||
              {/* ADD NEW PROXY ENDS. */}
 | 
			
		||||
              {
 | 
			
		||||
                splitBySemi(this.props.proxyStringRaw).map((proxyAsString, index) => {
 | 
			
		||||
                splitBySemi(this.props.proxyStringRaw).map((proxyAsStringRaw, index) => {
 | 
			
		||||
 | 
			
		||||
                  const proxyAsString = proxyAsStringRaw.trim();
 | 
			
		||||
 | 
			
		||||
                  const {
 | 
			
		||||
                    type,
 | 
			
		||||
                    creds,
 | 
			
		||||
                    hostname,
 | 
			
		||||
                    port,
 | 
			
		||||
                  } = theState.utils.parseProxyScheme(proxyAsString);
 | 
			
		||||
 | 
			
		||||
                  const [type, addr] = proxyAsString.trim().split(/\s+/);
 | 
			
		||||
                  const [hostname, port] = addr.split(':');
 | 
			
		||||
                  return (
 | 
			
		||||
                    <tr class={scopedCss.proxyRow}>
 | 
			
		||||
                      <td>
 | 
			
		||||
| 
						 | 
				
			
			@ -335,7 +348,7 @@ export default function getProxyEditor(theState) {
 | 
			
		|||
                        >X</button>
 | 
			
		||||
                      </td>
 | 
			
		||||
                      <td>{type}</td>
 | 
			
		||||
                      <td><input value={hostname} name="hostname" readonly/></td>
 | 
			
		||||
                      <td><input value={`${creds && `${creds}@`}${hostname}`} name="crededHostname" readonly/></td>
 | 
			
		||||
                      <td>{port}</td>
 | 
			
		||||
                      <td>
 | 
			
		||||
                        <button type="button" disabled={props.ifInputsDisabled}
 | 
			
		||||
| 
						 | 
				
			
			@ -350,6 +363,7 @@ export default function getProxyEditor(theState) {
 | 
			
		|||
              }
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
          <a href="https://github.com/anticensority/runet-censorship-bypass/wiki/Прокси-и-пароль">Запароленные прокси?</a>
 | 
			
		||||
        </form>
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -390,28 +404,34 @@ export default function getProxyEditor(theState) {
 | 
			
		|||
      const errors = splitBySemi(this.state.stashedExports)
 | 
			
		||||
        .map((proxyAsString) => {
 | 
			
		||||
 | 
			
		||||
          const [rawType, addr, ...rest] = proxyAsString.split(/\s+/);
 | 
			
		||||
          if (rest && rest.length) {
 | 
			
		||||
            return new Error(
 | 
			
		||||
              `"${rest.join(', ')}" кажется мне лишним. Вы забыли ";"?`
 | 
			
		||||
            );
 | 
			
		||||
          }
 | 
			
		||||
          const {
 | 
			
		||||
            type,
 | 
			
		||||
            creds,
 | 
			
		||||
            hostname,
 | 
			
		||||
            port,
 | 
			
		||||
            username,
 | 
			
		||||
            password,
 | 
			
		||||
          } = theState.utils.parseProxyScheme(proxyAsString);
 | 
			
		||||
          const crededAddr = `${creds ? `${creds}@` : ''}${hostname}:${port}`;
 | 
			
		||||
 | 
			
		||||
          const knownTypes = PROXY_TYPE_LABEL_PAIRS.map(([type, label]) => type);
 | 
			
		||||
          if( !knownTypes.includes(rawType.toUpperCase()) ) {
 | 
			
		||||
          if( !knownTypes.includes(type.toUpperCase()) ) {
 | 
			
		||||
            return new Error(
 | 
			
		||||
              `Неверный тип ${rawType}. Известные типы: ${knownTypes.join(', ')}.`
 | 
			
		||||
              `Неверный тип ${type}. Известные типы: ${knownTypes.join(', ')}.`
 | 
			
		||||
            );
 | 
			
		||||
          }
 | 
			
		||||
          if (!(addr && /^[^:]+:\d+$/.test(addr))) {
 | 
			
		||||
          if (!(crededAddr && /^(?:.+@)?[^:]+:\d+$/.test(crededAddr))) {
 | 
			
		||||
            return new Error(
 | 
			
		||||
              `Адрес прокси "${addr || ''}" не соответствует формату "<домен_или_IP>:<порт_из_цифр>".`
 | 
			
		||||
              `Адрес прокси "${crededAddr || ''}" не соответствует формату "<опц_логин>:<опц_пароль>@<домен_или_IP>:<порт_из_цифр>".`
 | 
			
		||||
            );
 | 
			
		||||
          }
 | 
			
		||||
          const [hostname, rawPort] = addr.split(':');
 | 
			
		||||
          const port = parseInt(rawPort);
 | 
			
		||||
          if (port < 0 || port > 65535) {
 | 
			
		||||
          if (password && !username) {
 | 
			
		||||
            return new Error('Вашему пользователю не хватает имени?');
 | 
			
		||||
          }
 | 
			
		||||
          const portInt = parseInt(port);
 | 
			
		||||
          if (portInt < 0 || portInt > 65535) {
 | 
			
		||||
            return new Error(
 | 
			
		||||
              `Порт "${rawPort}" должен быть целым числом от 0 до 65535.`
 | 
			
		||||
              `Порт "${port}" должен быть целым числом от 0 до 65535.`
 | 
			
		||||
            );
 | 
			
		||||
          }
 | 
			
		||||
          return false;
 | 
			
		||||
| 
						 | 
				
			
			@ -495,7 +515,7 @@ PROXY foobar.com:8080; # Not HTTP!`.trim()}
 | 
			
		|||
                    value={
 | 
			
		||||
                      this.state.stashedExports !== false
 | 
			
		||||
                        ? this.state.stashedExports
 | 
			
		||||
                        : (this.props.proxyStringRaw || '').replace(/\s*;\s*/g, ';\n')
 | 
			
		||||
                        : (this.props.proxyStringRaw || '').replace(/\s*;\n\s*/g, ';\n')
 | 
			
		||||
                    }
 | 
			
		||||
                  /></td>
 | 
			
		||||
              </tr>
 | 
			
		||||
| 
						 | 
				
			
			@ -514,10 +534,12 @@ PROXY foobar.com:8080; # Not HTTP!`.trim()}
 | 
			
		|||
    return proxyStringRaw
 | 
			
		||||
      .replace(/#.*$/mg, '') // Strip comments.
 | 
			
		||||
      .replace(/[^\S\r\n]*DIRECT[^\S\r\n]*/g, '') // Remove DIRECT from old versions.
 | 
			
		||||
      /*
 | 
			
		||||
      .split( /(?:[^\S\r\n]*(?:;|\r?\n)+[^\S\r\n]*)+/g )
 | 
			
		||||
      .map( (p) => p.trim() )
 | 
			
		||||
      .filter((p) => p)
 | 
			
		||||
      .join(';\n');
 | 
			
		||||
      */
 | 
			
		||||
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -54,7 +54,7 @@ export default function getTabPannel({ flags, baseCss }) {
 | 
			
		|||
    .navLabels {
 | 
			
		||||
      background-color: var(--cr-grey-panel);
 | 
			
		||||
      text-align: center;
 | 
			
		||||
      min-width: 24em;
 | 
			
		||||
      min-width: 25em;
 | 
			
		||||
    }
 | 
			
		||||
    .navLabels li label {
 | 
			
		||||
      display: inline-block;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@ export default function append(document, { flags }) {
 | 
			
		|||
  document.querySelector('style').innerHTML = `
 | 
			
		||||
    /* GLOBAL VARIABLES */
 | 
			
		||||
 | 
			
		||||
    :root {
 | 
			
		||||
    body {
 | 
			
		||||
      --ribbon-color: #4169e1;
 | 
			
		||||
      --blue-bg: dodgerblue;
 | 
			
		||||
      --default-grey: #bfbfbf;
 | 
			
		||||
| 
						 | 
				
			
			@ -12,13 +12,13 @@ export default function append(document, { flags }) {
 | 
			
		|||
      --cr-icon-selected: #d7d7d7;
 | 
			
		||||
      --cr-popup-border: #bababa;
 | 
			
		||||
      --cr-grey-panel: #f2f2f2;
 | 
			
		||||
      ${ flags.ifInsideOptionsPage ? '' : 'max-width: 24em;' }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* BASE ELEMENTS */
 | 
			
		||||
 | 
			
		||||
    body {
 | 
			
		||||
      margin: 0;
 | 
			
		||||
      max-width: 25em;
 | 
			
		||||
    }
 | 
			
		||||
    a, a:visited {
 | 
			
		||||
      color: var(--ribbon-color);
 | 
			
		||||
| 
						 | 
				
			
			@ -27,9 +27,11 @@ export default function append(document, { flags }) {
 | 
			
		|||
    a:hover {
 | 
			
		||||
      text-decoration: underline;
 | 
			
		||||
    }
 | 
			
		||||
    /*
 | 
			
		||||
    label {
 | 
			
		||||
      user-select: none;
 | 
			
		||||
    }
 | 
			
		||||
    */
 | 
			
		||||
    div, section, header, ul, ol {
 | 
			
		||||
      margin: 0;
 | 
			
		||||
      padding: 0;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,7 +9,13 @@ import getApp from './components/App';
 | 
			
		|||
chrome.runtime.getBackgroundPage( (bgWindow) =>
 | 
			
		||||
  bgWindow.apis.errorHandlers.installListenersOn(
 | 
			
		||||
    window, 'PUP', async() => {
 | 
			
		||||
 | 
			
		||||
      /*
 | 
			
		||||
        `Extension context invalidated` error is thrown if `window.closed` is true and call to
 | 
			
		||||
        `window.chrome.i18n` or other `window.chrome` api happens. Use bgWindow.chrome instead.
 | 
			
		||||
        Use winChrome for tab-related calls like winChrome.tabs.getCurrent.
 | 
			
		||||
      */
 | 
			
		||||
      window.winChrome = window.chrome;
 | 
			
		||||
      window.chrome = bgWindow.chrome;
 | 
			
		||||
      let theState;
 | 
			
		||||
      {
 | 
			
		||||
        const apis = bgWindow.apis;
 | 
			
		||||
| 
						 | 
				
			
			@ -29,15 +35,26 @@ chrome.runtime.getBackgroundPage( (bgWindow) =>
 | 
			
		|||
      // IF INSIDE OPTIONS TAB
 | 
			
		||||
 | 
			
		||||
      const currentTab = await new Promise(
 | 
			
		||||
        (resolve) => chrome.tabs.query(
 | 
			
		||||
        (resolve) => winChrome.tabs.query(
 | 
			
		||||
          {active: true, currentWindow: true},
 | 
			
		||||
          ([tab]) => resolve(tab)
 | 
			
		||||
          ([tab]) => resolve(tab),
 | 
			
		||||
        )
 | 
			
		||||
      );
 | 
			
		||||
      // winChrome.runtime.sendMessage({ currentTab, eventName: 'POPUP_OPENED' });
 | 
			
		||||
 | 
			
		||||
      theState.flags.ifInsideOptionsPage = !(currentTab && currentTab.url) || /.*:\/\/extensions\/\?options=/g.test(currentTab.url) || currentTab.url.startsWith('about:addons');
 | 
			
		||||
      theState.flags.ifInsideEdgeOptionsPage = theState.flags.ifInsideOptionsPage && currentTab && currentTab.url && currentTab.url.startsWith('edge://');
 | 
			
		||||
 | 
			
		||||
      theState.flags.ifInsideOptionsPage = !currentTab || currentTab.url.startsWith('chrome://extensions/?options=');
 | 
			
		||||
      theState.currentTab = currentTab;
 | 
			
		||||
 | 
			
		||||
      // If opened not via popup and not via options modal.
 | 
			
		||||
      // E.g., if opened via copy-pasting an URL into the address bar from somewhere.
 | 
			
		||||
      // If browser is not Chrome (Opera, e.g.) then options page may be opened in a separate tab
 | 
			
		||||
      // and then you will get a false positive.
 | 
			
		||||
      theState.flags.ifOpenedUnsafely = Boolean(await new Promise(
 | 
			
		||||
        (resolve) => winChrome.tabs.getCurrent(resolve),
 | 
			
		||||
      ));
 | 
			
		||||
 | 
			
		||||
      // STATE DEFINED, COMPOSE.
 | 
			
		||||
 | 
			
		||||
      appendGlobalCss(document, theState);
 | 
			
		||||
| 
						 | 
				
			
			@ -49,7 +66,13 @@ chrome.runtime.getBackgroundPage( (bgWindow) =>
 | 
			
		|||
      );
 | 
			
		||||
      // READY TO RENDER
 | 
			
		||||
 | 
			
		||||
      document.documentElement.style.display = 'initial';
 | 
			
		||||
      const show = () => { document.documentElement.style.visibility = 'initial'; };
 | 
			
		||||
 | 
			
		||||
      if (theState.flags.ifInsideOptionsPage) {
 | 
			
		||||
        show();
 | 
			
		||||
      } else {
 | 
			
		||||
        setTimeout(show, 200); // Mac bug: https://bugs.chromium.org/p/chromium/issues/detail?id=428044
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
  )
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -5,17 +5,31 @@
 | 
			
		|||
    <title>Устранение проблем</title>
 | 
			
		||||
  </head>
 | 
			
		||||
  <body>
 | 
			
		||||
 | 
			
		||||
    <h1>Устранение проблем</h1>
 | 
			
		||||
    <ol>
 | 
			
		||||
      <li><a href id="view-errors">Детали последних ошибок</a></li>
 | 
			
		||||
      <li><a href id="reset-settings">Сбросить настройки</a></li>
 | 
			
		||||
      <li><a href="https://rebrand.ly/ac-support" target="_blank">Файл самопомощи</a></li>
 | 
			
		||||
      <li><a href="https://rebrand.ly/ac-contact">Напишите нам!</a></li>
 | 
			
		||||
      <li><a data-in-bg="false" href class="view-errors">Детали последних ошибок</a></li>
 | 
			
		||||
      <li><a data-in-bg="false" href class="reset-settings">Сбросить настройки</a></li>
 | 
			
		||||
      <li><a data-in-bg="false" href="https://github.com/anticensority/runet-censorship-bypass/wiki/Если-расширение-не-работает" target="_blank">Файл самопомощи</a></li>
 | 
			
		||||
      <li><a data-in-bg="false" href="https://groups.google.com/g/anticensority">Напишите нам!</a></li>
 | 
			
		||||
    </ol>
 | 
			
		||||
    <h2>Для продвинутых</h2>
 | 
			
		||||
    <ol>
 | 
			
		||||
      <li><a href="https://rebrand.ly/ac-logs">Как прочитать логи?</a></li>
 | 
			
		||||
      <li><a href="../debug/index.html">Отладка PAC-скрипта</a></li>
 | 
			
		||||
      <li><a data-in-bg="false" href="https://github.com/anticensority/runet-censorship-bypass/wiki/Как-прочитать-логи%3F-%28для-продвинутых%29">Как прочитать логи?</a></li>
 | 
			
		||||
      <li><a data-in-bg="false" href="../debug/index.html">Отладка PAC-скрипта</a></li>
 | 
			
		||||
    </ol>
 | 
			
		||||
    <hr/>
 | 
			
		||||
    <h1>Troubleshooting</h1>
 | 
			
		||||
    <ol>
 | 
			
		||||
      <li><a data-in-bg="false" href class="view-errors">Details of last errors</a></li>
 | 
			
		||||
      <li><a data-in-bg="false" href class="reset-settings">Reset settings</a></li>
 | 
			
		||||
      <li><a data-in-bg="false" href="https://github.com/anticensority/runet-censorship-bypass/wiki/Если-расширение-не-работает" target="_blank">Troubleshoot guide (ru)</a></li>
 | 
			
		||||
      <li><a data-in-bg="false" href="https://groups.google.com/g/anticensority">Write to us!</a></li>
 | 
			
		||||
    </ol>
 | 
			
		||||
    <h2>Advanced</h2>
 | 
			
		||||
    <ol>
 | 
			
		||||
      <li><a data-in-bg="false" href="https://github.com/anticensority/runet-censorship-bypass/wiki/Как-прочитать-логи%3F-%28для-продвинутых%29">How to read logs?</a></li>
 | 
			
		||||
      <li><a data-in-bg="false" href="../debug/index.html">PAC-script debugging</a></li>
 | 
			
		||||
    </ol>
 | 
			
		||||
    <script src="./index.js"></script>
 | 
			
		||||
    <script src="../lib/keep-links-clickable.js"></script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,15 +4,22 @@ chrome.runtime.getBackgroundPage( (backgroundPage) =>
 | 
			
		|||
  backgroundPage.apis.errorHandlers.installListenersOn(
 | 
			
		||||
    window, 'TRBL', () => {
 | 
			
		||||
 | 
			
		||||
      document.getElementById('reset-settings').onclick = () => {
 | 
			
		||||
      document.querySelectorAll('.reset-settings').forEach((el) => {
 | 
			
		||||
 | 
			
		||||
        backgroundPage.localStorage.clear();
 | 
			
		||||
        chrome.storage.local.clear( () => chrome.runtime.reload() );
 | 
			
		||||
        el.onclick = () => {
 | 
			
		||||
 | 
			
		||||
      };
 | 
			
		||||
          backgroundPage.localStorage.clear();
 | 
			
		||||
          chrome.storage.local.clear( () => chrome.runtime.reload() );
 | 
			
		||||
 | 
			
		||||
      document.getElementById('view-errors').onclick = () =>
 | 
			
		||||
        backgroundPage.apis.errorHandlers.viewError('all');
 | 
			
		||||
        };
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    })
 | 
			
		||||
      document.querySelectorAll('.view-errors').forEach((el) => {
 | 
			
		||||
 | 
			
		||||
        el.onclick = () =>
 | 
			
		||||
          backgroundPage.apis.errorHandlers.viewError('all');
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
  ),
 | 
			
		||||
);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,10 @@
 | 
			
		|||
'use strict';
 | 
			
		||||
 | 
			
		||||
const Storage = require('_project-root/tools/sinon-storage');
 | 
			
		||||
const Storage = require('symlink-to/project-root/tools/sinon-storage');
 | 
			
		||||
const Chai = require('chai');
 | 
			
		||||
const Mocha = require('mocha');
 | 
			
		||||
 | 
			
		||||
const CachelessRequire = require('_project-root/tools/cacheless-require')(module);
 | 
			
		||||
const CachelessRequire = require('symlink-to/project-root/tools/cacheless-require')(module);
 | 
			
		||||
 | 
			
		||||
Mocha.describe('window.apis.pacKitchen', function () {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@
 | 
			
		|||
const Chai = require('chai');
 | 
			
		||||
const Mocha = require('mocha');
 | 
			
		||||
 | 
			
		||||
const CachelessRequire = require('_project-root/tools/cacheless-require')(module);
 | 
			
		||||
const CachelessRequire = require('symlink-to/project-root/tools/cacheless-require')(module);
 | 
			
		||||
 | 
			
		||||
Mocha.describe('window.utils', function () {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -69,27 +69,24 @@
 | 
			
		|||
 | 
			
		||||
  const reinit = function reinit() {
 | 
			
		||||
 | 
			
		||||
    // Defaults.
 | 
			
		||||
    const _antizapret = {
 | 
			
		||||
      /* Don't use directly, please.
 | 
			
		||||
         Encoded to counter abuse. */
 | 
			
		||||
      host: '\x70\x72\x6f\x78\x79\x2e\x61\x6e\x74\x69\x7a\x61\x70\x72\x65\x74\x2e\x70\x72\x6f\x73\x74\x6f\x76\x70\x6e\x2e\x6f\x72\x67',
 | 
			
		||||
    };
 | 
			
		||||
    privates._strToHostObj = {
 | 
			
		||||
      [_antizapret.host]: _antizapret,
 | 
			
		||||
    };
 | 
			
		||||
    privates._strToHostObj = [
 | 
			
		||||
      /* Please, don't use proxies directly (without PAC-script). */
 | 
			
		||||
      'n.thenewone.lol',
 | 
			
		||||
      's.thenewone.lol',
 | 
			
		||||
      // antizapret.prostovpn.org:
 | 
			
		||||
      'proxy.antizapret.prostovpn.org',
 | 
			
		||||
      'proxy-ssl.antizapret.prostovpn.org',
 | 
			
		||||
      'proxy-nossl.antizapret.prostovpn.org',
 | 
			
		||||
      'proxy-fbtw-ssl.antizapret.prostovpn.org',
 | 
			
		||||
    ].reduce((acc, hostname) => Object.assign(acc, { [hostname]: { host: hostname }}), {
 | 
			
		||||
      // Defaults:
 | 
			
		||||
      localhost: { host: 'localhost' },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    privates._ipToHostObj = {};
 | 
			
		||||
    for( const ip of [
 | 
			
		||||
      // IPs of Antizapret.
 | 
			
		||||
      '195.123.209.38',
 | 
			
		||||
      '137.74.171.91',
 | 
			
		||||
      '51.15.39.201',
 | 
			
		||||
      '2001:bc8:4700:2300::1:d07',
 | 
			
		||||
      '2a02:27ac::10',
 | 
			
		||||
    ] ) {
 | 
			
		||||
      privates._ipToHostObj[ip] = _antizapret;
 | 
			
		||||
    }
 | 
			
		||||
    privates._ipToHostObj = {
 | 
			
		||||
      '127.0.0.1': { host: 'localhost' },
 | 
			
		||||
      '0.0.0.0': { host: 'localhost' },
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Persisted.
 | 
			
		||||
    const ipToHost = _state(ip2host);
 | 
			
		||||
| 
						 | 
				
			
			@ -115,7 +112,7 @@
 | 
			
		|||
  const getIpsFor = function getIpsFor(host, cb = mandatory()) {
 | 
			
		||||
 | 
			
		||||
    if (host.trim() === 'localhost') {
 | 
			
		||||
      return cb(null, ['127.0.0.1', '::1']);
 | 
			
		||||
      return cb(null, ['127.0.0.1', '0.0.0.0', '::1']);
 | 
			
		||||
    }
 | 
			
		||||
    const types = [1, 28];
 | 
			
		||||
    const promises = types.map(
 | 
			
		||||
| 
						 | 
				
			
			@ -237,12 +234,11 @@
 | 
			
		|||
        getIpsFor(hostStr, (err, ips, ...warns) => {
 | 
			
		||||
 | 
			
		||||
          console.log('Got IPs + err?:', ips, err);
 | 
			
		||||
          if (!err) {
 | 
			
		||||
            resolveIps(ips);
 | 
			
		||||
          } else {
 | 
			
		||||
          if (err) {
 | 
			
		||||
            reject([err, null, ...warns]);
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          resolveIps(ips);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
      }).then(
 | 
			
		||||
| 
						 | 
				
			
			@ -264,7 +260,9 @@
 | 
			
		|||
 | 
			
		||||
    _updateAllAsync(cb = mandatory()) {
 | 
			
		||||
 | 
			
		||||
      const hostArr = Object.keys(privates._strToHostObj);
 | 
			
		||||
      const hostArr = Object.keys(privates._strToHostObj)
 | 
			
		||||
        .filter((hostStr) => hostStr !== 'localhost');
 | 
			
		||||
 | 
			
		||||
      console.log('Update all:', hostArr);
 | 
			
		||||
 | 
			
		||||
      const promises = hostArr.map(
 | 
			
		||||
| 
						 | 
				
			
			@ -277,29 +275,18 @@
 | 
			
		|||
      );
 | 
			
		||||
      Promise.all( promises ).then( (cbsRes) => {
 | 
			
		||||
 | 
			
		||||
        const errors = cbsRes.map( ([err]) => err ).filter( (err) => err );
 | 
			
		||||
        let newError;
 | 
			
		||||
        const ifAllErrors = cbsRes.length === errors.length;
 | 
			
		||||
        if (errors.length) {
 | 
			
		||||
          if (ifAllErrors) {
 | 
			
		||||
            newError = errors.shift();
 | 
			
		||||
          } else {
 | 
			
		||||
            newError = errors;
 | 
			
		||||
          }
 | 
			
		||||
          newError = clarify(
 | 
			
		||||
            newError,
 | 
			
		||||
        let ipErrors = cbsRes.map( ([err]) => err ).filter( (err) => err );
 | 
			
		||||
        let warns = [];
 | 
			
		||||
        if (ipErrors.length) {
 | 
			
		||||
          warns = [clarify(
 | 
			
		||||
            ipErrors,
 | 
			
		||||
            'Не удалось получить один или несколько IP адресов для' +
 | 
			
		||||
            ' прокси-серверов. Иконка для уведомления об обходе' +
 | 
			
		||||
            ' блокировок может не отображаться.'
 | 
			
		||||
          );
 | 
			
		||||
          if (ifAllErrors) {
 | 
			
		||||
            return cb(newError);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        cb(null, null, newError);
 | 
			
		||||
 | 
			
		||||
          )];
 | 
			
		||||
        } else {}
 | 
			
		||||
        cb(null, null, ...warns);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    _replaceAllAsync(hostArr = mandatory(), cb) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,137 @@
 | 
			
		|||
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
  chrome.webNavigation.onErrorOccurred.addListener((details) => {
 | 
			
		||||
  const timeouted = window.utils.timeouted;
 | 
			
		||||
 | 
			
		||||
  const isProxied = (requestDetails) => false;
 | 
			
		||||
  const isProxySideError = (details) =>
 | 
			
		||||
    /* About !main_frame: Main frame websocket errors are followed by webnavigation errors
 | 
			
		||||
       which chrome-internals code resets the state of the popup.
 | 
			
		||||
    */
 | 
			
		||||
    details.error === 'net::ERR_TUNNEL_CONNECTION_FAILED' && details.type !== 'main_frame' && isProxied(details) ||
 | 
			
		||||
    details.error === 'NS_ERROR_CONNECTION_REFUSED' && Boolean(details.proxyInfo);
 | 
			
		||||
 | 
			
		||||
  const urlToA = (url) => new URL(url).host.link(
 | 
			
		||||
    encodeURIComponent(url),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const isProxyErrorHandledAsync = async (details) => {
 | 
			
		||||
 | 
			
		||||
    if (!isProxySideError(details)) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    let fromPageHref = '';
 | 
			
		||||
    let toUrlHref = '';
 | 
			
		||||
    let fromPageHtml = '';
 | 
			
		||||
    let youMayReportHtml = '';
 | 
			
		||||
    const initiator = details.initiator !== 'null' && details.initiator;
 | 
			
		||||
    try {
 | 
			
		||||
      if (initiator) {
 | 
			
		||||
        fromPageHref = new URL(initiator).href; // Sanitize: only urls, not other stuff.
 | 
			
		||||
        fromPageHtml = ` со страницы ${urlToA(fromPageHref)}`;
 | 
			
		||||
      }
 | 
			
		||||
      toUrlHref = new URL(details.url).href;
 | 
			
		||||
      youMayReportHtml = ` Вы можете <b>${'сообщить об ошибке'.link(
 | 
			
		||||
        encodeURIComponent(
 | 
			
		||||
          '/pages/report-proxy-error/index.html?' +
 | 
			
		||||
          new URLSearchParams({
 | 
			
		||||
            fromPageHref,
 | 
			
		||||
            requestFailedTo: toUrlHref,
 | 
			
		||||
          }),
 | 
			
		||||
        ),
 | 
			
		||||
      )}</b> администратору прокси.`;
 | 
			
		||||
    } catch(e) {
 | 
			
		||||
      /* For malformed urls. */
 | 
			
		||||
      console.log('Error handling malformed URLs:', details);
 | 
			
		||||
      const msg = `Error handling malformed URLs: ${JSON.stringify(details, null, 2)}`;
 | 
			
		||||
      throw new TypeError(msg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Service workers have tabId = -1, get active tubId for them.
 | 
			
		||||
    const tabId = details.tabId < 0
 | 
			
		||||
      ? await new Promise((resolve) => chrome.tabs.query(
 | 
			
		||||
          { active: true },
 | 
			
		||||
          ([tab]) => resolve(tab.id)),
 | 
			
		||||
        )
 | 
			
		||||
      : details.tabId;
 | 
			
		||||
 | 
			
		||||
    const [oldPopup, oldText, oldColor] = await new Promise((resolve) =>
 | 
			
		||||
      chrome.browserAction.getPopup({ tabId }, (oldPopup) =>
 | 
			
		||||
        chrome.browserAction.getBadgeText({ tabId }, (oldText) =>
 | 
			
		||||
          chrome.browserAction.getBadgeBackgroundColor({ tabId }, (oldColor) => resolve([
 | 
			
		||||
            oldPopup,
 | 
			
		||||
            oldText,
 | 
			
		||||
            oldColor,
 | 
			
		||||
          ])),
 | 
			
		||||
        ),
 | 
			
		||||
      )
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const popupPrefix = chrome.runtime.getURL(`/pages/options/index.html?status=<span style="color: red">🔥 Прокси-сервер отказался обслуживать запрос к%20`);
 | 
			
		||||
    if (decodeURIComponent(oldPopup).startsWith(popupPrefix)) {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    const popup = `${popupPrefix}${urlToA(details.url)}${fromPageHtml}</span>. Это могло быть намеренно или по ошибке.${youMayReportHtml}#tab=exceptions`;
 | 
			
		||||
 | 
			
		||||
    chrome.browserAction.setPopup({
 | 
			
		||||
      tabId,
 | 
			
		||||
      popup,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    chrome.browserAction.setBadgeBackgroundColor({
 | 
			
		||||
      tabId,
 | 
			
		||||
      color: 'red',
 | 
			
		||||
    });
 | 
			
		||||
    chrome.browserAction.setBadgeText({
 | 
			
		||||
      tabId,
 | 
			
		||||
      text: '❗',
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let limit = 5;
 | 
			
		||||
    let ifOnTurn = true;
 | 
			
		||||
    let ifError = false;
 | 
			
		||||
    const flip = () => {
 | 
			
		||||
 | 
			
		||||
      if (!ifOnTurn && !--limit || ifError) {
 | 
			
		||||
        clearInterval(timer);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      chrome.browserAction.setBadgeText({
 | 
			
		||||
        tabId,
 | 
			
		||||
        text: ifOnTurn ? '❗' : '',
 | 
			
		||||
      }, () => {
 | 
			
		||||
        ifError = chrome.runtime.lastError;
 | 
			
		||||
      });
 | 
			
		||||
      ifOnTurn = !ifOnTurn;
 | 
			
		||||
    };
 | 
			
		||||
    flip();
 | 
			
		||||
    const timer = setInterval(flip, 500);
 | 
			
		||||
 | 
			
		||||
    const restoringHandler = timeouted((eventDetails) => {
 | 
			
		||||
 | 
			
		||||
      if(eventDetails && tabId !== ((eventDetails.currentTab || eventDetails).id || eventDetails.tabId)) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      clearInterval(timer);
 | 
			
		||||
 | 
			
		||||
      chrome.browserAction.setPopup({ tabId, popup: oldPopup});
 | 
			
		||||
      chrome.browserAction.setBadgeBackgroundColor({ tabId, color: oldColor});
 | 
			
		||||
      chrome.browserAction.setBadgeText({ tabId, text: oldText});
 | 
			
		||||
 | 
			
		||||
      chrome.runtime.onMessage.removeListener(restoringHandler);
 | 
			
		||||
      chrome.tabs.onRemoved.removeListener(restoringHandler);
 | 
			
		||||
      chrome.tabs.onReplaced.removeListener(restoringHandler);
 | 
			
		||||
      chrome.webNavigation.onBeforeNavigate.removeListener(restoringHandler);
 | 
			
		||||
    });
 | 
			
		||||
    chrome.runtime.onMessage.addListener(restoringHandler);
 | 
			
		||||
    chrome.tabs.onRemoved.addListener(restoringHandler);
 | 
			
		||||
    chrome.tabs.onReplaced.addListener(restoringHandler); // When does it happen?
 | 
			
		||||
    chrome.webNavigation.onBeforeNavigate.addListener(restoringHandler);
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  chrome.webNavigation.onErrorOccurred.addListener(timeouted(async (details) => {
 | 
			
		||||
 | 
			
		||||
    const tabId = details.tabId;
 | 
			
		||||
    if ( !(details.frameId === 0 && tabId >= 0) ||
 | 
			
		||||
| 
						 | 
				
			
			@ -12,13 +142,16 @@
 | 
			
		|||
          ].includes(details.error) ) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (await isProxyErrorHandledAsync(details)) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    chrome.browserAction.setPopup({
 | 
			
		||||
      tabId,
 | 
			
		||||
      popup: './pages/options/index.html#tab=exceptions&status=Правый клик по иконке — меню инструментов!',
 | 
			
		||||
      popup: './pages/options/index.html?status=Правый клик по иконке — меню инструментов!#tab=exceptions',
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    window.chrome.browserAction.setBadgeBackgroundColor({
 | 
			
		||||
    chrome.browserAction.setBadgeBackgroundColor({
 | 
			
		||||
      tabId,
 | 
			
		||||
      color: '#4285f4',
 | 
			
		||||
    });
 | 
			
		||||
| 
						 | 
				
			
			@ -27,6 +160,10 @@
 | 
			
		|||
      text: '●●●',
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  });
 | 
			
		||||
  }));
 | 
			
		||||
 | 
			
		||||
  chrome.webRequest.onErrorOccurred.addListener(
 | 
			
		||||
    timeouted(isProxyErrorHandledAsync),
 | 
			
		||||
    {urls: ['<all_urls>']},
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,8 +9,6 @@
 | 
			
		|||
  to "loading".
 | 
			
		||||
  So if you set a title earlier it may be cleared by browser.
 | 
			
		||||
  It pertains not only to page refesh but to newly opened pages too.
 | 
			
		||||
  Also on loosing title see:
 | 
			
		||||
    https://github.com/ilyaigpetrov/repository-for-chrome-bugs/blob/master/browserAction-title-lost-after-setting/background.js
 | 
			
		||||
  Crazy parallel Chrome.
 | 
			
		||||
**/
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -43,7 +41,7 @@
 | 
			
		|||
 | 
			
		||||
  const setRedBadge = (opts) => {
 | 
			
		||||
 | 
			
		||||
    window.chrome.browserAction.setBadgeBackgroundColor({
 | 
			
		||||
    chrome.browserAction.setBadgeBackgroundColor({
 | 
			
		||||
      color: '#db4b2f',
 | 
			
		||||
    });
 | 
			
		||||
    chrome.browserAction.setBadgeText(opts);
 | 
			
		||||
| 
						 | 
				
			
			@ -59,7 +57,7 @@
 | 
			
		|||
        if (err) {
 | 
			
		||||
          // E.g., no tab with such id happens.
 | 
			
		||||
          // Because requestDetails may be stale.
 | 
			
		||||
          console.log('Notifier error ignored:', err);
 | 
			
		||||
          console.log('Notifier error ignored (this is normal, it happens):', err);
 | 
			
		||||
          return cb();
 | 
			
		||||
        }
 | 
			
		||||
        const ifTitleSetAlready = /\n/.test(title);
 | 
			
		||||
| 
						 | 
				
			
			@ -142,6 +140,23 @@
 | 
			
		|||
    if (!host) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    {
 | 
			
		||||
      /*
 | 
			
		||||
        If we fetch a resource from a proxy address it is almost never proxied and
 | 
			
		||||
        shouldn't be shown.
 | 
			
		||||
        Think about localhost as a proxy and a user working with a web site on localhost.
 | 
			
		||||
      */
 | 
			
		||||
      /*
 | 
			
		||||
        Host is constructed from hostname and port. Hostname never contains port,
 | 
			
		||||
        it is an ip or a domain name. See hostname and host
 | 
			
		||||
        in `new URL('https://localhost:8080')`.
 | 
			
		||||
      */
 | 
			
		||||
      const hostnameFromUrl = new URL(requestDetails.url).hostname;
 | 
			
		||||
      const hostnameFromProxy = new URL(`https://${host}`).hostname;
 | 
			
		||||
      if (hostnameFromUrl === requestDetails.ip || hostnameFromUrl === hostnameFromProxy) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const ifMainFrame = requestDetails.type === 'main_frame';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,32 @@
 | 
			
		|||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta charset="utf-8">
 | 
			
		||||
    <title>Сообщить об ошибке прокси-сервера</title>
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1">
 | 
			
		||||
    <style>
 | 
			
		||||
      :root {
 | 
			
		||||
        font-size: 1.3em;
 | 
			
		||||
      }
 | 
			
		||||
    </style>
 | 
			
		||||
  </head>
 | 
			
		||||
  <body>
 | 
			
		||||
 | 
			
		||||
    <h1>Сообщить об ошибке прокси-сервера</h1>
 | 
			
		||||
      Перешлите администратору вашего прокси следующее:
 | 
			
		||||
      <fieldset>
 | 
			
		||||
        <pre id="errorInfo"></pre>
 | 
			
		||||
      </fieldset>
 | 
			
		||||
      Вот известные нам электронные адреса для популярных прокси-серверов (кликните по email для открытия шаблона письма):
 | 
			
		||||
      <ol>
 | 
			
		||||
        <li>
 | 
			
		||||
          Только если вы используете <a
 | 
			
		||||
            href="https://antizapret.prostovpn.org">Антизапрет</a>:
 | 
			
		||||
          <a
 | 
			
		||||
            href="mailto:antizapret@prostovpn.org">antizapret@prostovpn.org</a>.
 | 
			
		||||
        </li>
 | 
			
		||||
      </ol>
 | 
			
		||||
    <script src="./index.js"></script>
 | 
			
		||||
    <script src="../lib/keep-links-clickable.js"></script>
 | 
			
		||||
  </body>
 | 
			
		||||
</html>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,39 @@
 | 
			
		|||
'use strict';
 | 
			
		||||
 | 
			
		||||
chrome.runtime.getBackgroundPage( (bgWindow) =>
 | 
			
		||||
  bgWindow.apis.errorHandlers.installListenersOn(
 | 
			
		||||
    window, 'PRERR', () => {
 | 
			
		||||
 | 
			
		||||
      const params = new URLSearchParams(location.search.substr(1));
 | 
			
		||||
      const requestFailedTo = params.get('requestFailedTo');
 | 
			
		||||
      const fromPageHref = params.get('fromPageHref') || requestFailedTo;
 | 
			
		||||
 | 
			
		||||
      const acr = bgWindow.apis.antiCensorRu;
 | 
			
		||||
      const pacKey = acr.getCurrentPacProviderKey();
 | 
			
		||||
      const pacModTime = acr.getLastModifiedForKey(pacKey);
 | 
			
		||||
 | 
			
		||||
      const errorReport = `
 | 
			
		||||
Your proxy blocked the following request:
 | 
			
		||||
  * Request was from page: ${fromPageHref}
 | 
			
		||||
  * To address: ${requestFailedTo}
 | 
			
		||||
  * Used PAC-script: ${pacKey}
 | 
			
		||||
  * Its Last-Modified HTTP-header: ${pacModTime}
 | 
			
		||||
I think it's a mistake! Could you, please, take action to fix it.
 | 
			
		||||
Thank you!
 | 
			
		||||
 | 
			
		||||
Ваш прокси-сервер заблокировал следующий запрос:
 | 
			
		||||
  * Запрос был со страницы: ${fromPageHref}
 | 
			
		||||
  * Адрес запроса: ${requestFailedTo}
 | 
			
		||||
  * Мой PAC-скрипт: ${pacKey}
 | 
			
		||||
  * Его HTTP-заголовок Last-Modified: ${pacModTime}
 | 
			
		||||
Я думаю, это произошло по ошибке! Пожалуйста, примите действия для её исправления.
 | 
			
		||||
Спасибо!
 | 
			
		||||
      `.trim();
 | 
			
		||||
      errorInfo.innerText = errorReport;
 | 
			
		||||
      document.querySelectorAll('a[href^="mailto:"]').forEach((a) => {
 | 
			
		||||
 | 
			
		||||
        a.href = `${a.href}?subject=${encodeURIComponent(new URL(requestFailedTo).hostname)} TUNNEL_CONNECTION_FAILED&body=${encodeURIComponent(errorReport)}`;
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
  ),
 | 
			
		||||
);
 | 
			
		||||
| 
						 | 
				
			
			@ -1,31 +1,30 @@
 | 
			
		|||
'use strict';
 | 
			
		||||
 | 
			
		||||
const pacUrls = [
 | 
			
		||||
  // GitHub.io (anticensority), cached:
 | 
			
		||||
  'https://anticensority.github.io/generated-pac-scripts/anticensority.pac',
 | 
			
		||||
  // GitHub repo (anticensority), cached:
 | 
			
		||||
  'https://raw.githubusercontent.com/anticensority/generated-pac-scripts/master/anticensority.pac',
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const commonContext = {
 | 
			
		||||
  version: '1.5',
 | 
			
		||||
  version: '1.66',
 | 
			
		||||
  anticensorityPacUrls: [
 | 
			
		||||
    // First official, shortened:
 | 
			
		||||
    'https://rebrand.ly/ac-chrome-anticensority-pac',
 | 
			
		||||
    // Second official, Cloud Flare with caching:
 | 
			
		||||
    'https://anticensority.tk/generated-pac-scripts/anticensority.pac',
 | 
			
		||||
    // GitHub.io (anticensority):
 | 
			
		||||
    '\x68\x74\x74\x70\x73\x3a\x2f\x2f\x61\x6e\x74\x69\x63\x65\x6e\x73\x6f\x72\x69\x74\x79\x2e\x67\x69\x74\x68\x75\x62\x2e\x69\x6f\x2f\x67\x65\x6e\x65\x72\x61\x74\x65\x64\x2d\x70\x61\x63\x2d\x73\x63\x72\x69\x70\x74\x73\x2f\x61\x6e\x74\x69\x63\x65\x6e\x73\x6f\x72\x69\x74\x79\x2e\x70\x61\x63',
 | 
			
		||||
    // GitHub repo (anticensority):
 | 
			
		||||
    '\x68\x74\x74\x70\x73\x3a\x2f\x2f\x72\x61\x77\x2e\x67\x69\x74\x68\x75\x62\x75\x73\x65\x72\x63\x6f\x6e\x74\x65\x6e\x74\x2e\x63\x6f\x6d\x2f\x61\x6e\x74\x69\x63\x65\x6e\x73\x6f\x72\x69\x74\x79\x2f\x67\x65\x6e\x65\x72\x61\x74\x65\x64\x2d\x70\x61\x63\x2d\x73\x63\x72\x69\x70\x74\x73\x2f\x6d\x61\x73\x74\x65\x72\x2f\x61\x6e\x74\x69\x63\x65\x6e\x73\x6f\x72\x69\x74\x79\x2e\x70\x61\x63',
 | 
			
		||||
    // Old, deprecated:
 | 
			
		||||
    'https://anticensorship-russia.tk/generated-pac-scripts/anticensority.pac',
 | 
			
		||||
    // Google Drive (0.17, anticensority):
 | 
			
		||||
    '\x68\x74\x74\x70\x73\x3a\x2f\x2f\x64\x72\x69\x76\x65\x2e\x67\x6f\x6f\x67\x6c\x65\x2e\x63\x6f\x6d\x2f\x75\x63\x3f\x65\x78\x70\x6f\x72\x74\x3d\x64\x6f\x77\x6e\x6c\x6f\x61\x64\x26\x69\x64\x3d\x30\x42\x32\x6d\x68\x42\x67\x46\x6e\x66\x34\x70\x45\x4c\x56\x6c\x47\x4e\x54\x42\x45\x4d\x58\x4e\x6d\x52\x58\x63',
 | 
			
		||||
  ]
 | 
			
		||||
    ...pacUrls,
 | 
			
		||||
  ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
exports.contexts = {};
 | 
			
		||||
 | 
			
		||||
const  extra_permissions = ', "webRequest", "webRequestBlocking", "webNavigation"';
 | 
			
		||||
 | 
			
		||||
exports.contexts.full = Object.assign({}, commonContext, {
 | 
			
		||||
  versionSuffix: '',
 | 
			
		||||
  nameSuffixEn: '',
 | 
			
		||||
  nameSuffixRu: '',
 | 
			
		||||
  extra_permissions: ', "webRequest", "webNavigation"',
 | 
			
		||||
  extra_permissions,
 | 
			
		||||
  persistent: '',
 | 
			
		||||
  scripts_0x: '',
 | 
			
		||||
  scripts_2x: ', "20-ip-to-host-api.js"',
 | 
			
		||||
  scripts_8x: ', "80-error-menu.js", "83-last-errors.js", "85-block-informer.js"',
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -36,18 +35,34 @@ exports.contexts.mini = Object.assign({}, commonContext, {
 | 
			
		|||
  nameSuffixRu: ' МИНИ',
 | 
			
		||||
  extra_permissions: '',
 | 
			
		||||
  persistent: '"persistent": false,',
 | 
			
		||||
  scripts_0x: '',
 | 
			
		||||
  scripts_2x: ', "20-for-mini-only.js"',
 | 
			
		||||
  scripts_8x: '',
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
exports.contexts.beta = Object.assign({}, commonContext, {
 | 
			
		||||
  anticensorityPacUrls: ['https://rebrand.ly/ac-beta-pac'],
 | 
			
		||||
  version: '1.5',
 | 
			
		||||
exports.contexts.firefox = Object.assign({}, commonContext, {
 | 
			
		||||
  versionSuffix: '',
 | 
			
		||||
  nameSuffixEn: ' FOR TESTING',
 | 
			
		||||
  nameSuffixRu: ' ДЛЯ ТЕСТОВ',
 | 
			
		||||
  extra_permissions: ', "webRequest", "webNavigation"',
 | 
			
		||||
  nameSuffixEn: '',
 | 
			
		||||
  nameSuffixRu: '',
 | 
			
		||||
  extra_permissions,
 | 
			
		||||
  persistent: '',
 | 
			
		||||
  scripts_0x: ', "01-chrome-proxy-settings.js"',
 | 
			
		||||
  scripts_2x: ', "20-ip-to-host-api.js"',
 | 
			
		||||
  scripts_8x: ', "80-error-menu.js", "83-last-errors.js", "85-block-informer.js"',
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
exports.contexts.beta = Object.assign({}, commonContext, {
 | 
			
		||||
  anticensorityPacUrls: [
 | 
			
		||||
    'https://raw.githubusercontent.com/anticensority/for-testing/master/anticensority.pac',
 | 
			
		||||
    'https://anticensority.github.io/for-testing/anticensority.pac',
 | 
			
		||||
  ],
 | 
			
		||||
  version: '1.14',
 | 
			
		||||
  versionSuffix: '',
 | 
			
		||||
  nameSuffixEn: ' FOR TESTING',
 | 
			
		||||
  nameSuffixRu: ' ДЛЯ ТЕСТОВ',
 | 
			
		||||
  extra_permissions,
 | 
			
		||||
  persistent: '',
 | 
			
		||||
  scripts_0x: '',
 | 
			
		||||
  scripts_2x: ', "20-ip-to-host-api.js"',
 | 
			
		||||
  scripts_8x: ', "80-error-menu.js", "83-last-errors.js", "85-block-informer.js"',
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										17
									
								
								package.json
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										17
									
								
								package.json
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| 
						 | 
				
			
			@ -2,17 +2,16 @@
 | 
			
		|||
  "name": "subjective-good-is-evil",
 | 
			
		||||
  "version": "1.0.0",
 | 
			
		||||
  "description": "",
 | 
			
		||||
  "main": "index.js",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "generate": "cd pac-generator && npm start",
 | 
			
		||||
  	"prestart": "npm run generate",
 | 
			
		||||
	"start": "npm run bench",
 | 
			
		||||
    "bench": "cd ./pac-performance-analyses/benchmark && dnx run ../../pac-generator/generated-PACs",
 | 
			
		||||
	"test":  "rm -r pac-generator/generated-PACs"
 | 
			
		||||
    "postinstall": "opencollective postinstall"
 | 
			
		||||
  },
 | 
			
		||||
  "author": "ilyaigpetrov",
 | 
			
		||||
  "author": "anticensority+owners@googlegroups.com",
 | 
			
		||||
  "license": "ISC",
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "http-server": "^0.8.5"
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "opencollective": "^1.0.3"
 | 
			
		||||
  },
 | 
			
		||||
  "collective": {
 | 
			
		||||
    "type": "opencollective",
 | 
			
		||||
    "url": "https://opencollective.com/anticensority"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user