├── .trufflehogignore ├── src ├── site │ ├── resources │ │ └── CNAME │ └── site.xml ├── test │ ├── resources │ │ ├── brotli │ │ │ ├── a100.txt │ │ │ └── a100.txt.br │ │ └── log4j.xml │ └── java │ │ ├── org │ │ └── littleshoot │ │ │ └── proxy │ │ │ ├── SimpleProxyTest.java │ │ │ ├── StopProxyTest.java │ │ │ ├── IdlingProxyTest.java │ │ │ ├── UnencryptedTCPChainedProxyTest.java │ │ │ ├── UnencryptedUDTChainedProxyTest.java │ │ │ ├── MITMUsernamePasswordAuthenticatingProxyTest.java │ │ │ ├── MitmWithUnencryptedTCPChainedProxyTest.java │ │ │ ├── MitmWithUnencryptedUDTChainedProxyTest.java │ │ │ ├── ratelimiter │ │ │ ├── AuthenticationRateLimitTest.java │ │ │ ├── AuthenticationFailureRateLimitTest.java │ │ │ └── RateLimitTestBase.java │ │ │ ├── NoChainedProxiesTest.java │ │ │ ├── impl │ │ │ ├── LogCollectorAppender.java │ │ │ ├── DefaultHttpProxyServerTest.java │ │ │ ├── ProxyToServerConnectionUtilsTest.java │ │ │ └── HttpPipeliningBlockerTest.java │ │ │ ├── ChainedProxyWithFallbackToDirectDueToSSLTest.java │ │ │ ├── EncryptedTCPChainedProxyTest.java │ │ │ ├── EncryptedUDTChainedProxyTest.java │ │ │ ├── MitmWithEncryptedTCPChainedProxyTest.java │ │ │ ├── MitmWithEncryptedUDTChainedProxyTest.java │ │ │ ├── ResponseInfo.java │ │ │ ├── PerformanceServer.java │ │ │ ├── CustomProxyToServerExHandlerTest.java │ │ │ ├── UsernamePasswordAuthenticatingProxyTest.java │ │ │ ├── ClientAuthenticationNotRequiredTCPChainedProxyTest.java │ │ │ ├── MitmWithClientAuthenticationNotRequiredTCPChainedProxyTest.java │ │ │ ├── BadServerAuthenticationTCPChainedProxyTest.java │ │ │ ├── BadClientAuthenticationTCPChainedProxyTest.java │ │ │ ├── CustomClientToProxyExHandlerTest.java │ │ │ ├── MitmWithBadServerAuthenticationTCPChainedProxyTest.java │ │ │ ├── MitmWithBadClientAuthenticationTCPChainedProxyTest.java │ │ │ ├── BaseProxyTest.java │ │ │ ├── ChainedProxyWithFallbackToOtherChainedProxyDueToSSLTest.java │ │ │ ├── ChainedProxyWithFallbackTest.java │ │ │ ├── ProxyHeadersTest.java │ │ │ ├── DefaultFailureHttpResponseComposerTest.java │ │ │ └── test │ │ │ └── ServerErrorTest.java │ │ └── io │ │ └── netty │ │ └── handler │ │ └── codec │ │ └── compression │ │ ├── AbstractCompressionTest.java │ │ └── GeneralHttpContentDecompressorTest.java └── main │ ├── java │ ├── org │ │ └── littleshoot │ │ │ └── proxy │ │ │ ├── TransportProtocol.java │ │ │ ├── ExceptionHandler.java │ │ │ ├── monitoring │ │ │ ├── ProxyThreadPoolsObserver.java │ │ │ └── NoOpProxyThreadPoolsObserver.java │ │ │ ├── HostResolver.java │ │ │ ├── MitmManagerFactory.java │ │ │ ├── extras │ │ │ ├── SelfSignedMitmManagerFactory.java │ │ │ └── SelfSignedMitmManager.java │ │ │ ├── DnsSecServerResolver.java │ │ │ ├── authenticator │ │ │ ├── BasicCredentials.java │ │ │ ├── BasicProxyAuthenticator.java │ │ │ └── AbstractProxyAuthenticator.java │ │ │ ├── UnknownTransportProtocolException.java │ │ │ ├── impl │ │ │ ├── TraceUtils.java │ │ │ ├── ReadLoggingHandler.java │ │ │ ├── TracingAwareHttpRequestEncoder.java │ │ │ ├── NetworkUtils.java │ │ │ ├── CategorizedThreadFactory.java │ │ │ ├── HttpPipeliningBlocker.java │ │ │ ├── ConnectionState.java │ │ │ ├── ConnectionFlowStep.java │ │ │ └── ProtocolHeadersRequestDecoder.java │ │ │ ├── DefaultHostResolver.java │ │ │ ├── RequestTracer.java │ │ │ ├── FailureHttpResponseComposer.java │ │ │ ├── GlobalStateHandler.java │ │ │ ├── HttpFiltersSourceAdapter.java │ │ │ ├── SslEngineSource.java │ │ │ ├── ratelimit │ │ │ ├── NoOpRateLimiter.java │ │ │ └── RateLimiter.java │ │ │ ├── ProxyAuthenticator.java │ │ │ ├── ChainedProxyManager.java │ │ │ ├── FlowContext.java │ │ │ ├── ChainedProxyAdapter.java │ │ │ ├── FullFlowContext.java │ │ │ ├── ActivityTrackerAdapter.java │ │ │ ├── DefaultFailureHttpResponseComposer.java │ │ │ ├── MitmManager.java │ │ │ ├── HttpFiltersSource.java │ │ │ ├── ChainedProxy.java │ │ │ ├── HttpProxyServer.java │ │ │ └── HttpFiltersAdapter.java │ └── io │ │ └── netty │ │ └── handler │ │ └── codec │ │ └── compression │ │ ├── GeneralHttpContentDecompressor.java │ │ ├── BrotliHttpContentCompressor.java │ │ ├── BrotliDecoder.java │ │ └── BrotliEncoder.java │ └── config │ └── log4j.xml ├── CODEOWNERS ├── deploy.bash ├── littleproxy_cert ├── littleproxy_keystore.jks ├── chain_proxy_keystore_1.jks ├── scripts ├── run_circle_tests.sh ├── env.sh └── setup-maven-m2-mirrors.sh ├── perf.bash ├── performance ├── site │ └── wikipedia │ │ └── germany_files │ │ ├── geoiplookup │ │ ├── 170px-Grimm.jpg │ │ ├── fileicon-ogg.png │ │ ├── magnify-clip.png │ │ ├── search-ltr.png │ │ ├── 170px-W7x_022.jpg │ │ ├── 17px-North.svg.png │ │ ├── 17px-South.svg.png │ │ ├── 17px-West.svg.png │ │ ├── 17px-WMA_button2b.png │ │ ├── 197px-Beethoven.jpg │ │ ├── wikimedia-button.png │ │ ├── 11px-Increase2.svg.png │ │ ├── 15px-Sound-icon.svg.png │ │ ├── 220px-33rdG8Leaders.jpg │ │ ├── 220px-Eurozone.svg.png │ │ ├── 220px-German_Reich1.png │ │ ├── 220px-Wernerprokla.jpg │ │ ├── 45px-Sound-icon.svg.png │ │ ├── 120px-Cityscape_Berlin.jpg │ │ ├── 120px-Köln_Panorama.jpg │ │ ├── 13px-Speaker_Icon.svg.png │ │ ├── 140px-Deutschland_topo.jpg │ │ ├── 15px-Bremen_Wappen.svg.png │ │ ├── 15px-Cscr-featured.svg.png │ │ ├── 16px-Gnome-globe.svg.png │ │ ├── 18px-Commons-logo.svg.png │ │ ├── 200px-Circle_frame.svg.png │ │ ├── 23px-Flag_of_Chile.svg.png │ │ ├── 23px-Flag_of_Hesse.svg.png │ │ ├── 23px-Flag_of_India.svg.png │ │ ├── 23px-Flag_of_Italy.svg.png │ │ ├── 23px-Flag_of_Japan.svg.png │ │ ├── 23px-Flag_of_Spain.svg.png │ │ ├── 250px-EU-Germany.svg.png │ │ ├── 60px-Flag_of_NATO.svg.png │ │ ├── 11px-Speakerlink-new.svg.png │ │ ├── 120px-Stadtbild_München.jpg │ │ ├── 180px-Dom_Berlin_abends.JPG │ │ ├── 180px-Kölner_Dom_nachts.jpg │ │ ├── 20px-Flag_of_Denmark.svg.png │ │ ├── 20px-Padlock-silver.svg.png │ │ ├── 21px-Flag_of_Iceland.svg.png │ │ ├── 21px-Flag_of_Israel.svg.png │ │ ├── 21px-Wikiquote-logo.svg.png │ │ ├── 22px-Flag_of_Brazil.svg.png │ │ ├── 23px-Flag_of_Austria.svg.png │ │ ├── 23px-Flag_of_Berlin.svg.png │ │ ├── 23px-Flag_of_Bremen.svg.png │ │ ├── 23px-Flag_of_Canada.svg.png │ │ ├── 23px-Flag_of_Estonia.svg.png │ │ ├── 23px-Flag_of_Europe.svg.png │ │ ├── 23px-Flag_of_Finland.svg.png │ │ ├── 23px-Flag_of_France.svg.png │ │ ├── 23px-Flag_of_Germany.svg.png │ │ ├── 23px-Flag_of_Greece.svg.png │ │ ├── 23px-Flag_of_Hamburg.svg.png │ │ ├── 23px-Flag_of_Hungary.svg.png │ │ ├── 23px-Flag_of_Ireland.svg.png │ │ ├── 23px-Flag_of_Latvia.svg.png │ │ ├── 23px-Flag_of_Mexico.svg.png │ │ ├── 23px-Flag_of_Poland.svg.png │ │ ├── 23px-Flag_of_Russia.svg.png │ │ ├── 23px-Flag_of_Saxony.svg.png │ │ ├── 23px-Flag_of_Sweden.svg.png │ │ ├── 23px-Flag_of_Turkey.svg.png │ │ ├── 24px-Wikisource-logo.svg.png │ │ ├── 25px-Wikibooks-logo.svg.png │ │ ├── 25px-Wikivoyage-logo.svg.png │ │ ├── 32px-Europe_green_light.png │ │ ├── 32px-Flag_of_Europe.svg.png │ │ ├── 32px-Flag_of_Germany.svg.png │ │ ├── 111px-2011_Joachim_Gauck-2.jpg │ │ ├── 125px-Flag_of_Germany.svg.png │ │ ├── 15px-Brandenburg_Wappen.svg.png │ │ ├── 16px-Openstreetmap_logo.svg.png │ │ ├── 170px-Uniformmodell_Hessen4.JPG │ │ ├── 17px-Boxed_East_arrow.svg.png │ │ ├── 220px-Map-Germany-1945.svg.png │ │ ├── 23px-Flag_of_Argentina.svg.png │ │ ├── 23px-Flag_of_Australia.svg.png │ │ ├── 23px-Flag_of_Indonesia.svg.png │ │ ├── 23px-Flag_of_Lithuania.svg.png │ │ ├── 23px-Flag_of_Luxembourg.svg.png │ │ ├── 23px-Flag_of_Saarland.svg.png │ │ ├── 23px-Flag_of_Slovakia.svg.png │ │ ├── 23px-Flag_of_Slovenia.svg.png │ │ ├── 23px-Flag_of_Thuringia.svg.png │ │ ├── 23px-Wiktionary-logo-en.svg.png │ │ ├── 50px-Compass_rose_pale.svg.png │ │ ├── 70px-States_of_Germany.svg.png │ │ ├── poweredby_mediawiki_88x31.png │ │ ├── 15px-Wappen_des_Saarlands.svg.png │ │ ├── 16px-Flag_of_Switzerland.svg.png │ │ ├── 170px-Heringsdorf_Seeschloss.JPG │ │ ├── 170px-Knowledge_German_EU_map.png │ │ ├── 23px-Flag_of_Brandenburg.svg.png │ │ ├── 23px-Flag_of_Lower_Saxony.svg.png │ │ ├── 23px-Flag_of_New_Zealand.svg.png │ │ ├── 23px-Flag_of_Saudi_Arabia.svg.png │ │ ├── 23px-Flag_of_South_Africa.svg.png │ │ ├── 23px-Flag_of_South_Korea.svg.png │ │ ├── 25px-Wikiversity-logo-en.svg.png │ │ ├── 15px-Coat_of_arms_of_Hamburg.svg.png │ │ ├── 15px-Coat_of_arms_of_Hesse.svg.png │ │ ├── 15px-Coat_of_arms_of_Saxony.svg.png │ │ ├── 15px-Wappen_Sachsen-Anhalt.svg.png │ │ ├── 170px-Golden_Eagle_in_flight_-_5.jpg │ │ ├── 170px-Steilküste_bei_Ahrenshoop.jpg │ │ ├── 220px-Caspar_David_Friedrich_023.jpg │ │ ├── 220px-Fotothek_df_pk_0000180_001.jpg │ │ ├── 220px-Thefalloftheberlinwall1989.JPG │ │ ├── 23px-Flag_of_Belgium_(civil).svg.png │ │ ├── 23px-Flag_of_Saxony-Anhalt.svg.png │ │ ├── 23px-Flag_of_the_Netherlands.svg.png │ │ ├── 85px-Coat_of_Arms_of_Germany.svg.png │ │ ├── 120px-Carte_du_Conseil_de_l'Europe.png │ │ ├── 15px-Coat_of_arms_of_Thuringia.svg.png │ │ ├── 170px-Einstein_1921_by_F_Schmutzer.jpg │ │ ├── 220px-ICE_3_Oberhaider-Wald-Tunnel.jpg │ │ ├── 220px-Leopard_2_A5_der_Bundeswehr.jpg │ │ ├── 220px-Mercedes_Benz_AMG_SLS_Black.jpg │ │ ├── 220px-seek=32-Eurofighter_9803.ogg.jpg │ │ ├── 23px-Flag_of_Bavaria_(striped).svg.png │ │ ├── 23px-Flag_of_the_United_States.svg.png │ │ ├── 90px-Bundesadler_Bundesorgane.svg.png │ │ ├── 120px-Hamburger_Rathaus_von_St-Petri.jpg │ │ ├── 15px-Coat_of_arms_of_Lower_Saxony.svg.png │ │ ├── 160px-Holy_Roman_Empire_ca.1600.svg.png │ │ ├── 220px-Hacker-Pschorr_Oktoberfest_Girl.jpg │ │ ├── 220px-Invasions_of_the_Roman_Empire_1.png │ │ ├── 23px-Flag_of_Baden-Württemberg.svg.png │ │ ├── 23px-Flag_of_Rhineland-Palatinate.svg.png │ │ ├── 23px-Flag_of_Schleswig-Holstein.svg.png │ │ ├── 23px-Flag_of_the_Czech_Republic.svg.png │ │ ├── 23px-Flag_of_the_United_Kingdom.svg.png │ │ ├── 300px-Political_System_of_Germany.svg.png │ │ ├── 15px-Country_symbol_of_Berlin_color.svg.png │ │ ├── 15px-Landessymbol_Freistaat_Bayern.svg.png │ │ ├── 220px-Fritz_Lang_-_Boulevard_der_Stars.jpg │ │ ├── 23px-Flag_of_North_Rhine-Westphalia.svg.png │ │ ├── 100px-Europe_(orthographic_projection).svg.png │ │ ├── 125px-Angela_Merkel_(August_2012)_cropped.jpg │ │ ├── 160px-Weltliche_Schatzkammer_Wien_(189)2.JPG │ │ ├── 23px-Flagge_Königreich_Württemberg.svg.png │ │ ├── 300px-Karte_Bundesrepublik_Deutschland.svg.png │ │ ├── 15px-Coat_of_arms_of_Schleswig-Holstein.svg.png │ │ ├── 23px-Flag_of_Germany_(3-2_aspect_ratio).svg.png │ │ ├── 15px-Coat_of_arms_of_North_Rhine-Westfalia.svg.png │ │ ├── 15px-Coat_of_arms_of_Rhineland-Palatinate.svg.png │ │ ├── 170px-Heidelberg_Universitätsbibliothek_2003.jpg │ │ ├── 23px-Flag_of_Mecklenburg-Western_Pomerania.svg.png │ │ ├── 23px-Flag_of_the_People's_Republic_of_China.svg.png │ │ ├── 23px-Flagge_Großherzogtum_Baden_(1891–1918).svg.png │ │ ├── 170px-Martin_Luther_by_Lucas_Cranach_der_Ältere.jpeg │ │ ├── 15px-Coat_of_arms_of_Baden-Württemberg_(lesser).svg.png │ │ ├── 220px-Population_of_German_territories_1800_-_2000.JPG │ │ ├── 170px-Bundesarchiv_Bild_183-S33882,_Adolf_Hitler_retouched.jpg │ │ ├── 15px-Coat_of_arms_of_Mecklenburg-Western_Pomerania_(great).svg.png │ │ ├── 220px-Reichstag_building_Berlin_view_from_west_before_sunset.jpg │ │ ├── 170px-Luebeck-Heiligen-Geist-Hospital_von_Westen_gesehen-20100905.jpg │ │ └── 220px-FIFA_WC-qualification_2014_-_Austria_vs._Germany_2012-09-11_(03).jpg └── other_proxies │ └── node-proxy.js ├── littleproxy.properties ├── perfServer.bash ├── .gitignore ├── tagRelease.bash ├── run.bash ├── COPYRIGHT.txt ├── docker-compose.yaml ├── .mvn └── extensions.xml ├── .github └── dependabot.yml ├── Netty_4_Upgrade_Notes.md └── Yuml_Class_Diagram.md /.trufflehogignore: -------------------------------------------------------------------------------- 1 | src/test/java/ -------------------------------------------------------------------------------- /src/site/resources/CNAME: -------------------------------------------------------------------------------- 1 | littleproxy.org -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @osklyarenko @viacheslav-fomin-main @mjallday @iapetus999 @coanmj-vgs 2 | 3 | -------------------------------------------------------------------------------- /deploy.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mvn -Dmaven.test.skip=true -DperformRelease=true deploy 4 | -------------------------------------------------------------------------------- /littleproxy_cert: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/littleproxy_cert -------------------------------------------------------------------------------- /littleproxy_keystore.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/littleproxy_keystore.jks -------------------------------------------------------------------------------- /chain_proxy_keystore_1.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/chain_proxy_keystore_1.jks -------------------------------------------------------------------------------- /scripts/run_circle_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "$CIRCLE_TAG" == "" ] 4 | then 5 | mvn test -T2C 6 | fi -------------------------------------------------------------------------------- /perf.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ab -n 1000 -c 100 -X 127.0.0.1:8080 http://issues.littleshoot.org:8080/favicon.ico 4 | -------------------------------------------------------------------------------- /src/test/resources/brotli/a100.txt: -------------------------------------------------------------------------------- 1 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -------------------------------------------------------------------------------- /src/test/resources/brotli/a100.txt.br: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/src/test/resources/brotli/a100.txt.br -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/geoiplookup: -------------------------------------------------------------------------------- 1 | Geo = {"city":"Austin","country":"US","lat":"30.167801","lon":"-97.822502","IP":"70.113.105.37","netmask":"24"} -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/170px-Grimm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/170px-Grimm.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/fileicon-ogg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/fileicon-ogg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/magnify-clip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/magnify-clip.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/search-ltr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/search-ltr.png -------------------------------------------------------------------------------- /littleproxy.properties: -------------------------------------------------------------------------------- 1 | # Exposes proxy connection properties via JMX. 2 | jmx=false 3 | # Idle connections are disconnected after X seconds of inactivity 4 | idle_connection_timeout=70 -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/170px-W7x_022.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/170px-W7x_022.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/17px-North.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/17px-North.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/17px-South.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/17px-South.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/17px-West.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/17px-West.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/17px-WMA_button2b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/17px-WMA_button2b.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/197px-Beethoven.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/197px-Beethoven.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/wikimedia-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/wikimedia-button.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/11px-Increase2.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/11px-Increase2.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/15px-Sound-icon.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/15px-Sound-icon.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/220px-33rdG8Leaders.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/220px-33rdG8Leaders.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/220px-Eurozone.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/220px-Eurozone.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/220px-German_Reich1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/220px-German_Reich1.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/220px-Wernerprokla.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/220px-Wernerprokla.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/45px-Sound-icon.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/45px-Sound-icon.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/120px-Cityscape_Berlin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/120px-Cityscape_Berlin.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/120px-Köln_Panorama.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/120px-Köln_Panorama.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/13px-Speaker_Icon.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/13px-Speaker_Icon.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/140px-Deutschland_topo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/140px-Deutschland_topo.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/15px-Bremen_Wappen.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/15px-Bremen_Wappen.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/15px-Cscr-featured.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/15px-Cscr-featured.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/16px-Gnome-globe.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/16px-Gnome-globe.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/18px-Commons-logo.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/18px-Commons-logo.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/200px-Circle_frame.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/200px-Circle_frame.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Chile.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Chile.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Hesse.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Hesse.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_India.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_India.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Italy.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Italy.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Japan.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Japan.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Spain.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Spain.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/250px-EU-Germany.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/250px-EU-Germany.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/60px-Flag_of_NATO.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/60px-Flag_of_NATO.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/11px-Speakerlink-new.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/11px-Speakerlink-new.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/120px-Stadtbild_München.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/120px-Stadtbild_München.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/180px-Dom_Berlin_abends.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/180px-Dom_Berlin_abends.JPG -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/180px-Kölner_Dom_nachts.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/180px-Kölner_Dom_nachts.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/20px-Flag_of_Denmark.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/20px-Flag_of_Denmark.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/20px-Padlock-silver.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/20px-Padlock-silver.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/21px-Flag_of_Iceland.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/21px-Flag_of_Iceland.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/21px-Flag_of_Israel.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/21px-Flag_of_Israel.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/21px-Wikiquote-logo.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/21px-Wikiquote-logo.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/22px-Flag_of_Brazil.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/22px-Flag_of_Brazil.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Austria.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Austria.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Berlin.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Berlin.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Bremen.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Bremen.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Canada.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Canada.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Estonia.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Estonia.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Europe.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Europe.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Finland.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Finland.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_France.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_France.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Germany.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Germany.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Greece.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Greece.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Hamburg.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Hamburg.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Hungary.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Hungary.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Ireland.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Ireland.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Latvia.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Latvia.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Mexico.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Mexico.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Poland.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Poland.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Russia.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Russia.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Saxony.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Saxony.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Sweden.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Sweden.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Turkey.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Turkey.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/24px-Wikisource-logo.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/24px-Wikisource-logo.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/25px-Wikibooks-logo.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/25px-Wikibooks-logo.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/25px-Wikivoyage-logo.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/25px-Wikivoyage-logo.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/32px-Europe_green_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/32px-Europe_green_light.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/32px-Flag_of_Europe.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/32px-Flag_of_Europe.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/32px-Flag_of_Germany.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/32px-Flag_of_Germany.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/111px-2011_Joachim_Gauck-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/111px-2011_Joachim_Gauck-2.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/125px-Flag_of_Germany.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/125px-Flag_of_Germany.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/15px-Brandenburg_Wappen.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/15px-Brandenburg_Wappen.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/16px-Openstreetmap_logo.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/16px-Openstreetmap_logo.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/170px-Uniformmodell_Hessen4.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/170px-Uniformmodell_Hessen4.JPG -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/17px-Boxed_East_arrow.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/17px-Boxed_East_arrow.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/220px-Map-Germany-1945.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/220px-Map-Germany-1945.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Argentina.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Argentina.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Australia.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Australia.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Indonesia.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Indonesia.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Lithuania.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Lithuania.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Luxembourg.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Luxembourg.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Saarland.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Saarland.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Slovakia.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Slovakia.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Slovenia.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Slovenia.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Thuringia.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Thuringia.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Wiktionary-logo-en.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Wiktionary-logo-en.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/50px-Compass_rose_pale.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/50px-Compass_rose_pale.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/70px-States_of_Germany.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/70px-States_of_Germany.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/poweredby_mediawiki_88x31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/poweredby_mediawiki_88x31.png -------------------------------------------------------------------------------- /perfServer.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | function die() { 3 | echo $* 4 | exit 1 5 | } 6 | 7 | mvn test-compile exec:java -Dexec.mainClass="org.littleshoot.proxy.PerformanceServer" -Dexec.classpathScope="test" -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/15px-Wappen_des_Saarlands.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/15px-Wappen_des_Saarlands.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/16px-Flag_of_Switzerland.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/16px-Flag_of_Switzerland.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/170px-Heringsdorf_Seeschloss.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/170px-Heringsdorf_Seeschloss.JPG -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/170px-Knowledge_German_EU_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/170px-Knowledge_German_EU_map.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Brandenburg.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Brandenburg.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Lower_Saxony.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Lower_Saxony.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_New_Zealand.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_New_Zealand.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Saudi_Arabia.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Saudi_Arabia.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_South_Africa.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_South_Africa.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_South_Korea.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_South_Korea.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/25px-Wikiversity-logo-en.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/25px-Wikiversity-logo-en.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/15px-Coat_of_arms_of_Hamburg.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/15px-Coat_of_arms_of_Hamburg.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/15px-Coat_of_arms_of_Hesse.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/15px-Coat_of_arms_of_Hesse.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/15px-Coat_of_arms_of_Saxony.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/15px-Coat_of_arms_of_Saxony.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/15px-Wappen_Sachsen-Anhalt.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/15px-Wappen_Sachsen-Anhalt.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/170px-Golden_Eagle_in_flight_-_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/170px-Golden_Eagle_in_flight_-_5.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/170px-Steilküste_bei_Ahrenshoop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/170px-Steilküste_bei_Ahrenshoop.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/220px-Caspar_David_Friedrich_023.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/220px-Caspar_David_Friedrich_023.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/220px-Fotothek_df_pk_0000180_001.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/220px-Fotothek_df_pk_0000180_001.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/220px-Thefalloftheberlinwall1989.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/220px-Thefalloftheberlinwall1989.JPG -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Belgium_(civil).svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Belgium_(civil).svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Saxony-Anhalt.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Saxony-Anhalt.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_the_Netherlands.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_the_Netherlands.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/85px-Coat_of_Arms_of_Germany.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/85px-Coat_of_Arms_of_Germany.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/120px-Carte_du_Conseil_de_l'Europe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/120px-Carte_du_Conseil_de_l'Europe.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/15px-Coat_of_arms_of_Thuringia.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/15px-Coat_of_arms_of_Thuringia.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/170px-Einstein_1921_by_F_Schmutzer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/170px-Einstein_1921_by_F_Schmutzer.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/220px-ICE_3_Oberhaider-Wald-Tunnel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/220px-ICE_3_Oberhaider-Wald-Tunnel.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/220px-Leopard_2_A5_der_Bundeswehr.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/220px-Leopard_2_A5_der_Bundeswehr.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/220px-Mercedes_Benz_AMG_SLS_Black.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/220px-Mercedes_Benz_AMG_SLS_Black.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/220px-seek=32-Eurofighter_9803.ogg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/220px-seek=32-Eurofighter_9803.ogg.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Bavaria_(striped).svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Bavaria_(striped).svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_the_United_States.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_the_United_States.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/90px-Bundesadler_Bundesorgane.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/90px-Bundesadler_Bundesorgane.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/120px-Hamburger_Rathaus_von_St-Petri.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/120px-Hamburger_Rathaus_von_St-Petri.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/15px-Coat_of_arms_of_Lower_Saxony.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/15px-Coat_of_arms_of_Lower_Saxony.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/160px-Holy_Roman_Empire_ca.1600.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/160px-Holy_Roman_Empire_ca.1600.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/220px-Hacker-Pschorr_Oktoberfest_Girl.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/220px-Hacker-Pschorr_Oktoberfest_Girl.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/220px-Invasions_of_the_Roman_Empire_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/220px-Invasions_of_the_Roman_Empire_1.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Baden-Württemberg.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Baden-Württemberg.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Rhineland-Palatinate.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Rhineland-Palatinate.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Schleswig-Holstein.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Schleswig-Holstein.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_the_Czech_Republic.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_the_Czech_Republic.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_the_United_Kingdom.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_the_United_Kingdom.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/300px-Political_System_of_Germany.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/300px-Political_System_of_Germany.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/15px-Country_symbol_of_Berlin_color.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/15px-Country_symbol_of_Berlin_color.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/15px-Landessymbol_Freistaat_Bayern.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/15px-Landessymbol_Freistaat_Bayern.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/220px-Fritz_Lang_-_Boulevard_der_Stars.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/220px-Fritz_Lang_-_Boulevard_der_Stars.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_North_Rhine-Westphalia.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_North_Rhine-Westphalia.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/100px-Europe_(orthographic_projection).svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/100px-Europe_(orthographic_projection).svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/125px-Angela_Merkel_(August_2012)_cropped.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/125px-Angela_Merkel_(August_2012)_cropped.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/160px-Weltliche_Schatzkammer_Wien_(189)2.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/160px-Weltliche_Schatzkammer_Wien_(189)2.JPG -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flagge_Königreich_Württemberg.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flagge_Königreich_Württemberg.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/300px-Karte_Bundesrepublik_Deutschland.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/300px-Karte_Bundesrepublik_Deutschland.svg.png -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/TransportProtocol.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | /** 4 | * Enumeration of transport protocols supported by LittleProxy. 5 | */ 6 | public enum TransportProtocol { 7 | TCP, UDT 8 | } -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/15px-Coat_of_arms_of_Schleswig-Holstein.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/15px-Coat_of_arms_of_Schleswig-Holstein.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Germany_(3-2_aspect_ratio).svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Germany_(3-2_aspect_ratio).svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/15px-Coat_of_arms_of_North_Rhine-Westfalia.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/15px-Coat_of_arms_of_North_Rhine-Westfalia.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/15px-Coat_of_arms_of_Rhineland-Palatinate.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/15px-Coat_of_arms_of_Rhineland-Palatinate.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/170px-Heidelberg_Universitätsbibliothek_2003.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/170px-Heidelberg_Universitätsbibliothek_2003.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_Mecklenburg-Western_Pomerania.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_Mecklenburg-Western_Pomerania.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flag_of_the_People's_Republic_of_China.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flag_of_the_People's_Republic_of_China.svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/23px-Flagge_Großherzogtum_Baden_(1891–1918).svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/23px-Flagge_Großherzogtum_Baden_(1891–1918).svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/170px-Martin_Luther_by_Lucas_Cranach_der_Ältere.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/170px-Martin_Luther_by_Lucas_Cranach_der_Ältere.jpeg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/15px-Coat_of_arms_of_Baden-Württemberg_(lesser).svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/15px-Coat_of_arms_of_Baden-Württemberg_(lesser).svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/220px-Population_of_German_territories_1800_-_2000.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/220px-Population_of_German_territories_1800_-_2000.JPG -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | log.txt 2 | *.swp 3 | *.settings 4 | *.classpath 5 | *.project 6 | *.iml 7 | *.ipr 8 | *.iws 9 | *.DS_Store 10 | *.orig 11 | target/ 12 | .idea/ 13 | jmeter.log 14 | lib/ 15 | LittleProxy.pro 16 | /bin 17 | *.jks 18 | performance/site/ 19 | -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/170px-Bundesarchiv_Bild_183-S33882,_Adolf_Hitler_retouched.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/170px-Bundesarchiv_Bild_183-S33882,_Adolf_Hitler_retouched.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/15px-Coat_of_arms_of_Mecklenburg-Western_Pomerania_(great).svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/15px-Coat_of_arms_of_Mecklenburg-Western_Pomerania_(great).svg.png -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/220px-Reichstag_building_Berlin_view_from_west_before_sunset.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/220px-Reichstag_building_Berlin_view_from_west_before_sunset.jpg -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/170px-Luebeck-Heiligen-Geist-Hospital_von_Westen_gesehen-20100905.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/170px-Luebeck-Heiligen-Geist-Hospital_von_Westen_gesehen-20100905.jpg -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/ExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | public interface ExceptionHandler { 4 | 5 | /** 6 | * Handles proxy exceptions 7 | * 8 | * @param cause error cause 9 | */ 10 | void handle(Throwable cause); 11 | } 12 | -------------------------------------------------------------------------------- /performance/site/wikipedia/germany_files/220px-FIFA_WC-qualification_2014_-_Austria_vs._Germany_2012-09-11_(03).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/verygoodsecurity/LittleProxy/HEAD/performance/site/wikipedia/germany_files/220px-FIFA_WC-qualification_2014_-_Austria_vs._Germany_2012-09-11_(03).jpg -------------------------------------------------------------------------------- /scripts/env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mkdir -p ~/.aws 4 | 5 | echo " 6 | [default] 7 | region = us-west-2 8 | aws_access_key_id=$AWS_ACCESS_KEY_ID 9 | aws_secret_access_key=$AWS_SECRET_ACCESS_KEY 10 | [dev/vault] 11 | region = us-west-2 12 | role_arn = arn:aws:iam::883127560329:role/VGSStageDeploy 13 | source_profile = default 14 | " > ~/.aws/credentials -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/monitoring/ProxyThreadPoolsObserver.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.monitoring; 2 | 3 | import org.littleshoot.proxy.TransportProtocol; 4 | import org.littleshoot.proxy.impl.ProxyThreadPools; 5 | 6 | public interface ProxyThreadPoolsObserver { 7 | void observe(TransportProtocol protocol, ProxyThreadPools proxyThreadPools); 8 | } 9 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/SimpleProxyTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | /** 4 | * Tests just a single basic proxy. 5 | */ 6 | public class SimpleProxyTest extends BaseProxyTest { 7 | @Override 8 | protected void setUp() { 9 | this.proxyServer = bootstrapProxy() 10 | .withPort(0) 11 | .start(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/HostResolver.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import java.net.InetSocketAddress; 4 | import java.net.UnknownHostException; 5 | 6 | /** 7 | * Resolves host and port into an InetSocketAddress. 8 | */ 9 | public interface HostResolver { 10 | public InetSocketAddress resolve(String host, int port) 11 | throws UnknownHostException; 12 | } 13 | -------------------------------------------------------------------------------- /tagRelease.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ARGS=1 # One arg to script expected. 4 | 5 | if [ $# -ne "$ARGS" ] 6 | then 7 | echo "Must include the version number" 8 | exit 1 9 | fi 10 | 11 | RELEASE_VERSION=$1 12 | svn copy "http://svn.littleshoot.org/svn/littleproxy/trunk" "http://svn.littleshoot.org/svn/littleproxy/tags/littleproxy-${RELEASE_VERSION}" -m "Tag for LittleProxy release ${RELEASE_VERSION}" 13 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/monitoring/NoOpProxyThreadPoolsObserver.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.monitoring; 2 | 3 | import org.littleshoot.proxy.TransportProtocol; 4 | import org.littleshoot.proxy.impl.ProxyThreadPools; 5 | 6 | public class NoOpProxyThreadPoolsObserver implements ProxyThreadPoolsObserver { 7 | 8 | @Override 9 | public void observe(TransportProtocol protocol, ProxyThreadPools proxyThreadPools) { 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/MitmManagerFactory.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import io.netty.channel.Channel; 4 | 5 | public interface MitmManagerFactory { 6 | 7 | /** 8 | * Retrieves an instance of {@link MitmManager} based on specific attributes from cxt channel 9 | * 10 | * @param channel current channel from cxt 11 | * @return concrete instance of {@link MitmManager} 12 | */ 13 | MitmManager getInstance(Channel channel); 14 | } 15 | -------------------------------------------------------------------------------- /run.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | function die() { 3 | echo $* 4 | exit 1 5 | } 6 | 7 | mvn package -Dmaven.test.skip=true || die "Could not package" 8 | 9 | fullPath=`dirname $0` 10 | jar=`find $fullPath/target/littleproxy*-littleproxy-shade.jar` 11 | cp=`echo $jar | sed 's,./,'$fullPath'/,'` 12 | javaArgs="-server -XX:+HeapDumpOnOutOfMemoryError -Xmx800m -jar "$cp" $*" 13 | 14 | echo "Running using Java on path at `which java` with args $javaArgs" 15 | java $javaArgs || die "Java process exited abnormally" 16 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/extras/SelfSignedMitmManagerFactory.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.extras; 2 | 3 | import io.netty.channel.Channel; 4 | import org.littleshoot.proxy.MitmManager; 5 | import org.littleshoot.proxy.MitmManagerFactory; 6 | 7 | /** 8 | * The factory for self signed mitm manager 9 | */ 10 | public class SelfSignedMitmManagerFactory implements MitmManagerFactory { 11 | @Override 12 | public MitmManager getInstance(Channel channel) { 13 | return new SelfSignedMitmManager(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/DnsSecServerResolver.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import java.net.InetSocketAddress; 4 | import java.net.UnknownHostException; 5 | 6 | import org.littleshoot.dnssec4j.VerifiedAddressFactory; 7 | 8 | public class DnsSecServerResolver implements HostResolver { 9 | @Override 10 | public InetSocketAddress resolve(String host, int port) 11 | throws UnknownHostException { 12 | return VerifiedAddressFactory.newInetSocketAddress(host, port, true); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/authenticator/BasicCredentials.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.authenticator; 2 | 3 | public final class BasicCredentials { 4 | 5 | private final String username; 6 | private final String password; 7 | 8 | public BasicCredentials(String username, String password) { 9 | this.username = username; 10 | this.password = password; 11 | } 12 | 13 | public String getUsername() { 14 | return username; 15 | } 16 | 17 | public String getPassword() { 18 | return password; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /COPYRIGHT.txt: -------------------------------------------------------------------------------- 1 | The LittleProxy Project - Copyright 2009 Last Bamboo LLC, and is licensed under the 2 | Apache License version 2.0 as published by the Apache Software Foundation. 3 | 4 | A summary of the individual contributors is given below. Any omission should be 5 | sent to Adam Fisk . 6 | 7 | SVN Login(s) Name 8 | ------------------------------------------------------------------------------- 9 | afisk Adam Fisk 10 | ------------------------------------------------------------------------------- 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/UnknownTransportProtocolException.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | /** 4 | * This exception indicates that the system was asked to use a TransportProtocol that it didn't know how to handle. 5 | */ 6 | public class UnknownTransportProtocolException extends RuntimeException { 7 | private static final long serialVersionUID = 1L; 8 | 9 | public UnknownTransportProtocolException(TransportProtocol transportProtocol) { 10 | super(String.format("Unknown TransportProtocol: %1$s", transportProtocol)); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/impl/TraceUtils.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.impl; 2 | 3 | public final class TraceUtils { 4 | 5 | public static final String HEADER_X_B3_TRACEID = "X-B3-TraceId"; 6 | public static final String HEADER_X_B3_SPANID = "X-B3-SpanId"; 7 | public static final String HEADER_X_B3_SAMPLED = "X-B3-Sampled"; 8 | public static final String HEADER_X_B3_PARENTSPANID = "X-B3-ParentSpanId"; 9 | public static final String HEADER_VGS_REQUEST_ID = "VGS-Request-Id"; 10 | public static final String HEADER_TRACEPARENT = "traceparent"; 11 | } 12 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.2" 2 | services: 3 | 4 | build: 5 | image: maven:3.8.6-jdk-8-slim 6 | command: bash -c "apt-get update && 7 | apt-get install git -y && 8 | mvn -T 1C -U clean install -DskipTests -Dsurefire.failIfNoSpecifiedTests=false -DfailIfNoTests=false -Dmaven.test.skip=true --threads 5 -B" 9 | working_dir: /app 10 | environment: 11 | AWS_PROFILE: dev/vault 12 | AWS_DEFAULT_REGION: us-west-2 13 | AWS_REGION: us-west-2 14 | volumes: 15 | - .:/app/ 16 | - ~/.m2/:/root/.m2/ 17 | - ~/.aws/:/root/.aws/:ro 18 | -------------------------------------------------------------------------------- /.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | com.verygoodsecurity 5 | aws-maven 6 | 1.4.5 7 | 8 | 9 | 10 | kr.motd.maven 11 | os-maven-plugin 12 | 1.7.0 13 | 14 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/DefaultHostResolver.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import java.net.InetAddress; 4 | import java.net.InetSocketAddress; 5 | import java.net.UnknownHostException; 6 | 7 | /** 8 | * Default implementation of {@link HostResolver} that just uses 9 | * {@link InetAddress#getByName(String)}. 10 | */ 11 | public class DefaultHostResolver implements HostResolver { 12 | @Override 13 | public InetSocketAddress resolve(String host, int port) 14 | throws UnknownHostException { 15 | InetAddress addr = InetAddress.getByName(host); 16 | return new InetSocketAddress(addr, port); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/StopProxyTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import org.junit.Test; 4 | import org.littleshoot.proxy.impl.DefaultHttpProxyServer; 5 | 6 | public class StopProxyTest { 7 | @Test 8 | public void testStop() { 9 | HttpProxyServer proxyServer = DefaultHttpProxyServer.bootstrap() 10 | .withPort(0) 11 | .start(); 12 | 13 | proxyServer.stop(); 14 | } 15 | 16 | @Test 17 | public void testAbort() { 18 | HttpProxyServer proxyServer = DefaultHttpProxyServer.bootstrap() 19 | .withPort(0) 20 | .start(); 21 | 22 | proxyServer.abort(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/RequestTracer.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import io.netty.channel.Channel; 4 | 5 | public interface RequestTracer { 6 | 7 | 8 | /** 9 | * 10 | * Start tracing proxy request for this channel and message 11 | * @param channel 12 | * @param msg 13 | */ 14 | void start(Channel channel, Object msg); 15 | 16 | /** 17 | * Request is served. Finish tracing. 18 | * @param channel 19 | * @param msg 20 | */ 21 | void finish(Channel channel, Object msg); 22 | 23 | /** 24 | * Current trace in this channel 25 | * @param channel 26 | * @return current trace 27 | */ 28 | String getCurrentTrace(Channel channel); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/FailureHttpResponseComposer.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import io.netty.handler.codec.http.FullHttpResponse; 4 | import io.netty.handler.codec.http.HttpRequest; 5 | 6 | /** 7 | * Interface for objects that can provide a custom http response on a specific failure. 8 | */ 9 | public interface FailureHttpResponseComposer { 10 | 11 | /** 12 | * Creates an {@link FullHttpResponse} based on initial request and failure cause 13 | * @param httpRequest initial request 14 | * @param cause an exception thrown during a failure 15 | * @return failure http response 16 | */ 17 | FullHttpResponse compose(HttpRequest httpRequest, Throwable cause); 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/IdlingProxyTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertEquals; 6 | 7 | /** 8 | * Tests just a single basic proxy. 9 | */ 10 | public class IdlingProxyTest extends AbstractProxyTest { 11 | @Override 12 | protected void setUp() { 13 | this.proxyServer = bootstrapProxy() 14 | .withPort(0) 15 | .withIdleConnectionTimeout(1) 16 | .start(); 17 | } 18 | 19 | @Test 20 | public void testTimeout() throws Exception { 21 | ResponseInfo response = httpGetWithApacheClient(webHost, "/hang", true, 22 | false); 23 | assertEquals("Received: " + response, 504, response.getStatusCode()); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/GlobalStateHandler.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import io.netty.channel.Channel; 4 | 5 | /** 6 | * Netty is not designed for thread local or global state usage. 7 | * It guarantees that a channel is handled by only one thread 8 | * but that thread is constantly reused by other channels so 9 | * the state can be messed up. 10 | * 11 | * This handler lets serialize the state to a channel so it 12 | * can be deserialized when needed. 13 | */ 14 | public interface GlobalStateHandler { 15 | 16 | /** 17 | * Deserializes global state from channel. 18 | * 19 | * @param channel client connection channel 20 | */ 21 | void restoreFromChannel(Channel channel); 22 | 23 | /** 24 | * Clears global state. 25 | */ 26 | void clear(); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/authenticator/BasicProxyAuthenticator.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.authenticator; 2 | 3 | import org.littleshoot.proxy.impl.ProxyUtils; 4 | 5 | import io.netty.handler.codec.http.HttpRequest; 6 | 7 | /** 8 | * Basic Auth user/password authenticator 9 | */ 10 | public abstract class BasicProxyAuthenticator extends AbstractProxyAuthenticator { 11 | 12 | @Override 13 | public boolean authenticate(HttpRequest request) { 14 | BasicCredentials credentials = ProxyUtils.getBasicCredentials(request); 15 | 16 | if (credentials != null) { 17 | return authenticate(credentials.getUsername(), credentials.getPassword(), request); 18 | } 19 | 20 | return false; 21 | } 22 | 23 | public abstract boolean authenticate(String username, String password, HttpRequest request); 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/UnencryptedTCPChainedProxyTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import static org.littleshoot.proxy.TransportProtocol.*; 4 | 5 | public class UnencryptedTCPChainedProxyTest extends BaseChainedProxyTest { 6 | @Override 7 | protected HttpProxyServerBootstrap upstreamProxy() { 8 | return super.upstreamProxy() 9 | .withTransportProtocol(TCP); 10 | } 11 | 12 | @Override 13 | protected ChainedProxy newChainedProxy() { 14 | return new BaseChainedProxy() { 15 | @Override 16 | public TransportProtocol getTransportProtocol() { 17 | return TransportProtocol.TCP; 18 | } 19 | 20 | @Override 21 | public boolean requiresEncryption() { 22 | return false; 23 | } 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/UnencryptedUDTChainedProxyTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import static org.littleshoot.proxy.TransportProtocol.*; 4 | 5 | public class UnencryptedUDTChainedProxyTest extends BaseChainedProxyTest { 6 | @Override 7 | protected HttpProxyServerBootstrap upstreamProxy() { 8 | return super.upstreamProxy() 9 | .withTransportProtocol(UDT); 10 | } 11 | 12 | @Override 13 | protected ChainedProxy newChainedProxy() { 14 | return new BaseChainedProxy() { 15 | @Override 16 | public TransportProtocol getTransportProtocol() { 17 | return TransportProtocol.UDT; 18 | } 19 | 20 | @Override 21 | public boolean requiresEncryption() { 22 | return false; 23 | } 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/MITMUsernamePasswordAuthenticatingProxyTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import org.littleshoot.proxy.extras.SelfSignedMitmManagerFactory; 4 | 5 | /** 6 | * Tests a single proxy that requires username/password authentication and that 7 | * uses MITM. 8 | */ 9 | public class MITMUsernamePasswordAuthenticatingProxyTest extends 10 | UsernamePasswordAuthenticatingProxyTest { 11 | @Override 12 | protected void setUp() { 13 | this.proxyServer = bootstrapProxy() 14 | .withPort(0) 15 | .withProxyAuthenticator(new TestBasicProxyAuthenticator(USERNAME, PASSWORD)) 16 | .withManInTheMiddle(new SelfSignedMitmManagerFactory()) 17 | .start(); 18 | } 19 | 20 | @Override 21 | protected boolean isMITM() { 22 | return true; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/MitmWithUnencryptedTCPChainedProxyTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import static org.littleshoot.proxy.TransportProtocol.*; 4 | 5 | public class MitmWithUnencryptedTCPChainedProxyTest extends MitmWithChainedProxyTest { 6 | @Override 7 | protected HttpProxyServerBootstrap upstreamProxy() { 8 | return super.upstreamProxy() 9 | .withTransportProtocol(TCP); 10 | } 11 | 12 | @Override 13 | protected ChainedProxy newChainedProxy() { 14 | return new BaseChainedProxy() { 15 | @Override 16 | public TransportProtocol getTransportProtocol() { 17 | return TransportProtocol.TCP; 18 | } 19 | 20 | @Override 21 | public boolean requiresEncryption() { 22 | return false; 23 | } 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/MitmWithUnencryptedUDTChainedProxyTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import static org.littleshoot.proxy.TransportProtocol.*; 4 | 5 | public class MitmWithUnencryptedUDTChainedProxyTest extends MitmWithChainedProxyTest { 6 | @Override 7 | protected HttpProxyServerBootstrap upstreamProxy() { 8 | return super.upstreamProxy() 9 | .withTransportProtocol(UDT); 10 | } 11 | 12 | @Override 13 | protected ChainedProxy newChainedProxy() { 14 | return new BaseChainedProxy() { 15 | @Override 16 | public TransportProtocol getTransportProtocol() { 17 | return TransportProtocol.UDT; 18 | } 19 | 20 | @Override 21 | public boolean requiresEncryption() { 22 | return false; 23 | } 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | 8 | registries: 9 | maven-github: 10 | type: maven-repository 11 | url: https://maven.pkg.github.com/verygoodsecurity/parent-pom 12 | username: ${{secrets.VGS_GITHUB_USER}} 13 | password: ${{secrets.VGS_GITHUB_TOKEN}} 14 | updates: 15 | - package-ecosystem: "maven" 16 | directory: "/" 17 | registries: 18 | - "maven-github" 19 | schedule: 20 | interval: "daily" 21 | open-pull-requests-limit: 200 22 | groups: 23 | parent-update: 24 | patterns: 25 | - "com.verygood.security*" 26 | -------------------------------------------------------------------------------- /scripts/setup-maven-m2-mirrors.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | mkdir -p ~/.m2 4 | 5 | echo ' 6 | 7 | 11 | 12 | 13 | repo1 14 | central 15 | Maven Central 16 | https://repo1.maven.org/maven2/ 17 | 18 | 19 | 20 | repo2 21 | central 22 | Maven Central 2 23 | https://repo2.maven.org/maven2/ 24 | 25 | 26 | 27 | ' > ~/.m2/settings.xml 28 | -------------------------------------------------------------------------------- /src/site/site.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | org.apache.maven.skins 8 | maven-fluido-skin 9 | 1.3.0 10 | 11 | 12 | 13 | true 14 | true 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/HttpFiltersSourceAdapter.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.handler.codec.http.HttpRequest; 5 | 6 | /** 7 | * Convenience base class for implementations of {@link HttpFiltersSource}. 8 | */ 9 | public class HttpFiltersSourceAdapter implements HttpFiltersSource { 10 | 11 | public HttpFilters filterRequest(HttpRequest originalRequest) { 12 | return new HttpFiltersAdapter(originalRequest, null); 13 | } 14 | 15 | @Override 16 | public HttpFilters filterRequest(HttpRequest originalRequest, 17 | ChannelHandlerContext ctx) { 18 | return filterRequest(originalRequest); 19 | } 20 | 21 | @Override 22 | public int getMaximumRequestBufferSizeInBytes() { 23 | return 0; 24 | } 25 | 26 | @Override 27 | public int getMaximumResponseBufferSizeInBytes() { 28 | return 0; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/SslEngineSource.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import javax.net.ssl.SSLEngine; 4 | 5 | /** 6 | * Source for {@link SSLEngine}s. 7 | */ 8 | public interface SslEngineSource { 9 | 10 | /** 11 | * Returns an {@link SSLEngine} to use for a server connection from 12 | * LittleProxy to the client. 13 | * 14 | * @return 15 | */ 16 | SSLEngine newSslEngine(); 17 | 18 | /** 19 | * Returns an {@link SSLEngine} to use for a client connection from 20 | * LittleProxy to the upstream server. * 21 | * 22 | * Note: Peer information is needed to send the server_name extension in 23 | * handshake with Server Name Indication (SNI). 24 | * 25 | * @param peerHost 26 | * to start a client connection to the server. 27 | * @param peerPort 28 | * to start a client connection to the server. 29 | * @return 30 | */ 31 | SSLEngine newSslEngine(String peerHost, int peerPort); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/extras/SelfSignedMitmManager.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.extras; 2 | 3 | import io.netty.handler.codec.http.HttpRequest; 4 | import org.littleshoot.proxy.MitmManager; 5 | 6 | import javax.net.ssl.SSLEngine; 7 | import javax.net.ssl.SSLSession; 8 | 9 | /** 10 | * {@link MitmManager} that uses self-signed certs for everything. 11 | */ 12 | public class SelfSignedMitmManager implements MitmManager { 13 | SelfSignedSslEngineSource selfSignedSslEngineSource = 14 | new SelfSignedSslEngineSource(true); 15 | 16 | @Override 17 | public SSLEngine serverSslEngine(String peerHost, int peerPort) { 18 | return selfSignedSslEngineSource.newSslEngine(peerHost, peerPort); 19 | } 20 | 21 | @Override 22 | public SSLEngine serverSslEngine() { 23 | return selfSignedSslEngineSource.newSslEngine(); 24 | } 25 | 26 | @Override 27 | public SSLEngine clientSslEngineFor(HttpRequest httpRequest, SSLSession serverSslSession) { 28 | return selfSignedSslEngineSource.newSslEngine(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/ratelimiter/AuthenticationRateLimitTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.ratelimiter; 2 | 3 | import org.junit.Test; 4 | import org.littleshoot.proxy.ResponseInfo; 5 | 6 | import io.netty.handler.codec.http.HttpResponseStatus; 7 | 8 | import static org.junit.Assert.assertEquals; 9 | import static org.junit.Assert.assertTrue; 10 | 11 | public class AuthenticationRateLimitTest extends RateLimitTestBase { 12 | 13 | @Test 14 | public void testAuthenticationLimits() throws Exception { 15 | int numRequests = 0; 16 | 17 | boolean rateLimited = false; 18 | int numValidRequests = 0; 19 | while (numRequests++ < AUTHENTICATION_LIMIT + 1) { 20 | ResponseInfo proxiedResponse = httpGetWithApacheClient(webHost, DEFAULT_RESOURCE, true, false); 21 | 22 | if (proxiedResponse.getStatusCode() == HttpResponseStatus.TOO_MANY_REQUESTS.code()) { 23 | rateLimited = true; 24 | break; 25 | } 26 | 27 | numValidRequests++; 28 | } 29 | 30 | assertTrue(rateLimited); 31 | assertEquals(AUTHENTICATION_LIMIT - 1, numValidRequests); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/ratelimit/NoOpRateLimiter.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.ratelimit; 2 | 3 | import org.littleshoot.proxy.impl.ProxyUtils; 4 | 5 | import io.netty.handler.codec.http.FullHttpResponse; 6 | import io.netty.handler.codec.http.HttpRequest; 7 | import io.netty.handler.codec.http.HttpResponseStatus; 8 | import io.netty.handler.codec.http.HttpVersion; 9 | 10 | /** 11 | * No Op implementation of rate limiter. This is used by default in HttpProxyServer. 12 | */ 13 | public class NoOpRateLimiter implements RateLimiter { 14 | 15 | @Override 16 | public boolean isOverLimit(HttpRequest httpRequest) { 17 | return false; 18 | } 19 | 20 | @Override 21 | public boolean isAuthenticationOverLimit(HttpRequest httpRequest) { 22 | return false; 23 | } 24 | 25 | @Override 26 | public boolean isAuthenticationFailureOverLimit(HttpRequest httpRequest) { 27 | return false; 28 | } 29 | 30 | @Override 31 | public FullHttpResponse limitReachedResponse(HttpRequest request) { 32 | return ProxyUtils.createFullHttpResponse( 33 | HttpVersion.HTTP_1_1, 34 | HttpResponseStatus.TOO_MANY_REQUESTS, 35 | "429 Too Many Requests"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/NoChainedProxiesTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import io.netty.handler.codec.http.HttpRequest; 4 | 5 | import java.util.Queue; 6 | 7 | import org.junit.Test; 8 | 9 | /** 10 | * Tests that when there are no chained proxies, we get a bad gateway. 11 | */ 12 | public class NoChainedProxiesTest extends AbstractProxyTest { 13 | @Override 14 | protected void setUp() { 15 | this.proxyServer = bootstrapProxy() 16 | .withPort(0) 17 | .withChainProxyManager(new ChainedProxyManager() { 18 | @Override 19 | public void lookupChainedProxies(HttpRequest httpRequest, 20 | Queue chainedProxies) { 21 | // Leave list empty 22 | } 23 | }) 24 | .withIdleConnectionTimeout(1) 25 | .start(); 26 | } 27 | 28 | @Test 29 | public void testNoChainedProxy() throws Exception { 30 | ResponseInfo response = httpGetWithApacheClient(webHost, 31 | DEFAULT_RESOURCE, true, false); 32 | assertReceivedBadGateway(response); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/ProxyAuthenticator.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import io.netty.handler.codec.http.FullHttpResponse; 4 | import io.netty.handler.codec.http.HttpRequest; 5 | 6 | /** 7 | * Interface for objects that can authenticate someone for using our Proxy on 8 | * the basis of authorization header. 9 | */ 10 | public interface ProxyAuthenticator { 11 | 12 | /** 13 | * Authenticates the user using the specified proxy authorization header. 14 | * 15 | * @param httpRequest 16 | * http request 17 | * 18 | * @return true if the credential is acceptable, otherwise 19 | * false. 20 | */ 21 | boolean authenticate(HttpRequest httpRequest); 22 | 23 | /** 24 | * The realm value to be used in the request for proxy authentication 25 | * ("Proxy-Authenticate" header). Returning null will cause the string 26 | * "Restricted Files" to be used by default. 27 | * 28 | * @return 29 | */ 30 | String getRealm(); 31 | 32 | /** 33 | * Response that is going to be returned on authentication failure 34 | */ 35 | FullHttpResponse authenticationFailureResponse(HttpRequest request); 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/impl/LogCollectorAppender.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.impl; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashSet; 5 | import java.util.List; 6 | import java.util.Set; 7 | import java.util.stream.Collectors; 8 | import org.apache.log4j.AppenderSkeleton; 9 | import org.apache.log4j.spi.LoggingEvent; 10 | 11 | public class LogCollectorAppender extends AppenderSkeleton { 12 | 13 | List logs = new ArrayList<>(); 14 | 15 | Set threadNames = new HashSet<>(); 16 | 17 | @Override 18 | protected void append(LoggingEvent loggingEvent) { 19 | logs.add((String)loggingEvent.getMessage()); 20 | threadNames.add(loggingEvent.getThreadName()); 21 | } 22 | 23 | 24 | public Set getThreadNames() { 25 | return threadNames; 26 | } 27 | 28 | public Set getLogsContains(String contains) { 29 | return logs.stream().filter((s) -> s.contains(contains)).collect(Collectors.toSet()); 30 | } 31 | 32 | @Override 33 | public void close() { 34 | clear(); 35 | } 36 | 37 | public void clear() { 38 | logs.clear(); 39 | threadNames.clear(); 40 | } 41 | 42 | @Override 43 | public boolean requiresLayout() { 44 | return false; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/io/netty/handler/codec/compression/GeneralHttpContentDecompressor.java: -------------------------------------------------------------------------------- 1 | package io.netty.handler.codec.compression; 2 | 3 | import static io.netty.handler.codec.compression.BrotliHttpContentCompressor.BR; 4 | 5 | import io.netty.channel.embedded.EmbeddedChannel; 6 | import io.netty.handler.codec.http.HttpContent; 7 | import io.netty.handler.codec.http.HttpContentDecoder; 8 | import io.netty.handler.codec.http.HttpContentDecompressor; 9 | import io.netty.handler.codec.http.HttpMessage; 10 | 11 | /** 12 | * Decompresses an {@link HttpMessage} and an {@link HttpContent} compressed in {@code br}, {@code gzip} or 13 | * {@code deflate} encoding. For more information on how this handler modifies the message, please refer to 14 | * {@link HttpContentDecoder}. 15 | */ 16 | public class GeneralHttpContentDecompressor extends HttpContentDecompressor { 17 | 18 | @Override 19 | protected EmbeddedChannel newContentDecoder(String contentEncoding) throws Exception { 20 | 21 | if (BR.contentEqualsIgnoreCase(contentEncoding)) { 22 | return new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(), 23 | ctx.channel().config(), new BrotliDecoder()); 24 | } 25 | 26 | return super.newContentDecoder(contentEncoding); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/ChainedProxyManager.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import io.netty.handler.codec.http.HttpRequest; 4 | 5 | import java.util.Queue; 6 | 7 | /** 8 | *

9 | * Interface for classes that manage chained proxies. 10 | *

11 | */ 12 | public interface ChainedProxyManager { 13 | 14 | /** 15 | *

16 | * Based on the given httpRequest, add any {@link ChainedProxy}s to the list 17 | * that should be used to process the request. The downstream proxy will 18 | * attempt to connect to each of these in the order that they appear until 19 | * it successfully connects to one. 20 | *

21 | * 22 | *

23 | * To allow the proxy to fall back to a direct connection, you can add 24 | * {@link ChainedProxyAdapter#FALLBACK_TO_DIRECT_CONNECTION} to the end of 25 | * the list. 26 | *

27 | * 28 | *

29 | * To keep the proxy from attempting any connection, leave the list blank. 30 | * This will cause the proxy to return a 502 response. 31 | *

32 | * 33 | * @param httpRequest 34 | * @param chainedProxies 35 | */ 36 | void lookupChainedProxies(HttpRequest httpRequest, 37 | Queue chainedProxies); 38 | } -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/ratelimit/RateLimiter.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.ratelimit; 2 | 3 | import io.netty.handler.codec.http.FullHttpResponse; 4 | import io.netty.handler.codec.http.HttpRequest; 5 | 6 | /** 7 | * Interface for rate limiting requests 8 | */ 9 | public interface RateLimiter { 10 | 11 | /** 12 | * Rate Limiting general http requests 13 | * 14 | * @param request 15 | * @return true if http request hit rate limit 16 | * false. 17 | */ 18 | boolean isOverLimit(HttpRequest request); 19 | 20 | /** 21 | * Rate Limiting user authentication requests 22 | * 23 | * @param request 24 | * @return true if user hit rate limit 25 | * false. 26 | */ 27 | boolean isAuthenticationOverLimit(HttpRequest request); 28 | 29 | /** 30 | * Rate Limiting user authentication failures requests 31 | * 32 | * @param request 33 | * @return true if user's authentication failures hit rate limit 34 | * false. 35 | */ 36 | boolean isAuthenticationFailureOverLimit(HttpRequest request); 37 | 38 | /** 39 | * Http response that is going to be returned to user when limit is reached 40 | */ 41 | FullHttpResponse limitReachedResponse(HttpRequest request); 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/FlowContext.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import java.net.InetSocketAddress; 4 | 5 | import javax.net.ssl.SSLEngine; 6 | import javax.net.ssl.SSLSession; 7 | 8 | import org.littleshoot.proxy.impl.ClientToProxyConnection; 9 | 10 | /** 11 | *

12 | * Encapsulates contextual information for flow information that's being 13 | * reported to a {@link ActivityTracker}. 14 | *

15 | */ 16 | public class FlowContext { 17 | private final InetSocketAddress clientAddress; 18 | private final SSLSession clientSslSession; 19 | 20 | public FlowContext(ClientToProxyConnection clientConnection) { 21 | super(); 22 | this.clientAddress = clientConnection.getClientAddress(); 23 | SSLEngine sslEngine = clientConnection.getSslEngine(); 24 | this.clientSslSession = sslEngine != null ? sslEngine.getSession() 25 | : null; 26 | } 27 | 28 | /** 29 | * The address of the client. 30 | * 31 | * @return 32 | */ 33 | public InetSocketAddress getClientAddress() { 34 | return clientAddress; 35 | } 36 | 37 | /** 38 | * If using SSL, this returns the {@link SSLSession} on the client 39 | * connection. 40 | * 41 | * @return 42 | */ 43 | public SSLSession getClientSslSession() { 44 | return clientSslSession; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/ratelimiter/AuthenticationFailureRateLimitTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.ratelimiter; 2 | 3 | import org.junit.Test; 4 | import org.littleshoot.proxy.ResponseInfo; 5 | 6 | import io.netty.handler.codec.http.HttpResponseStatus; 7 | 8 | import static org.junit.Assert.assertEquals; 9 | import static org.junit.Assert.assertTrue; 10 | 11 | public class AuthenticationFailureRateLimitTest extends RateLimitTestBase { 12 | 13 | @Test 14 | public void testAuthenticationFailureLimits() throws Exception { 15 | this.proxyServer = bootstrapProxy() 16 | .withPort(0) 17 | .withProxyAuthenticator(new TestBasicProxyAuthenticator(USERNAME, "invalid password")) 18 | .withRateLimiter(new BaseRateLimiter(100, AUTHENTICATION_FAILURE_LIMIT)) 19 | .start(); 20 | 21 | int numRequests = 0; 22 | 23 | boolean rateLimited = false; 24 | int numValidRequests = 0; 25 | while (numRequests++ < AUTHENTICATION_FAILURE_LIMIT + 1) { 26 | ResponseInfo proxiedResponse = httpGetWithApacheClient(webHost, DEFAULT_RESOURCE, true, false); 27 | 28 | if (proxiedResponse.getStatusCode() == HttpResponseStatus.TOO_MANY_REQUESTS.code()) { 29 | rateLimited = true; 30 | break; 31 | } 32 | 33 | numValidRequests++; 34 | } 35 | 36 | assertTrue(rateLimited); 37 | assertEquals(AUTHENTICATION_FAILURE_LIMIT - 1, numValidRequests); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/ChainedProxyWithFallbackToDirectDueToSSLTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import io.netty.handler.codec.http.HttpRequest; 4 | 5 | import java.util.Queue; 6 | 7 | /** 8 | * Tests a proxy chained to a downstream proxy with an untrusted SSL cert. When 9 | * the downstream proxy is unavailable, the downstream proxy should just fall 10 | * back to a direct connection. 11 | */ 12 | public class ChainedProxyWithFallbackToDirectDueToSSLTest extends 13 | BadServerAuthenticationTCPChainedProxyTest { 14 | @Override 15 | protected boolean isChained() { 16 | // Set this to false since we don't actually expect anything to go 17 | // through the chained proxy 18 | return false; 19 | } 20 | 21 | @Override 22 | protected boolean expectBadGatewayForEverything() { 23 | return false; 24 | } 25 | 26 | protected ChainedProxyManager chainedProxyManager() { 27 | return new ChainedProxyManager() { 28 | @Override 29 | public void lookupChainedProxies(HttpRequest httpRequest, 30 | Queue chainedProxies) { 31 | // This first one has a bad cert 32 | chainedProxies.add(newChainedProxy()); 33 | chainedProxies 34 | .add(ChainedProxyAdapter.FALLBACK_TO_DIRECT_CONNECTION); 35 | } 36 | }; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/config/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/EncryptedTCPChainedProxyTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import static org.littleshoot.proxy.TransportProtocol.*; 4 | 5 | import javax.net.ssl.SSLEngine; 6 | 7 | import org.littleshoot.proxy.extras.SelfSignedSslEngineSource; 8 | 9 | public class EncryptedTCPChainedProxyTest extends BaseChainedProxyTest { 10 | private final SslEngineSource sslEngineSource = new SelfSignedSslEngineSource( 11 | "chain_proxy_keystore_1.jks"); 12 | 13 | @Override 14 | protected HttpProxyServerBootstrap upstreamProxy() { 15 | return super.upstreamProxy() 16 | .withTransportProtocol(TCP) 17 | .withSslEngineSource(sslEngineSource); 18 | } 19 | 20 | @Override 21 | protected ChainedProxy newChainedProxy() { 22 | return new BaseChainedProxy() { 23 | @Override 24 | public TransportProtocol getTransportProtocol() { 25 | return TransportProtocol.TCP; 26 | } 27 | 28 | @Override 29 | public boolean requiresEncryption() { 30 | return true; 31 | } 32 | 33 | @Override 34 | public SSLEngine newSslEngine() { 35 | return sslEngineSource.newSslEngine(); 36 | } 37 | 38 | @Override 39 | public SSLEngine newSslEngine(String peerHost, int peerPort) { 40 | return sslEngineSource.newSslEngine(peerHost, peerPort); 41 | } 42 | }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/EncryptedUDTChainedProxyTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import static org.littleshoot.proxy.TransportProtocol.*; 4 | 5 | import javax.net.ssl.SSLEngine; 6 | 7 | import org.littleshoot.proxy.extras.SelfSignedSslEngineSource; 8 | 9 | public class EncryptedUDTChainedProxyTest extends BaseChainedProxyTest { 10 | private final SslEngineSource sslEngineSource = new SelfSignedSslEngineSource( 11 | "chain_proxy_keystore_1.jks"); 12 | 13 | @Override 14 | protected HttpProxyServerBootstrap upstreamProxy() { 15 | return super.upstreamProxy() 16 | .withTransportProtocol(UDT) 17 | .withSslEngineSource(sslEngineSource); 18 | } 19 | 20 | @Override 21 | protected ChainedProxy newChainedProxy() { 22 | return new BaseChainedProxy() { 23 | @Override 24 | public TransportProtocol getTransportProtocol() { 25 | return TransportProtocol.UDT; 26 | } 27 | 28 | @Override 29 | public boolean requiresEncryption() { 30 | return true; 31 | } 32 | 33 | @Override 34 | public SSLEngine newSslEngine() { 35 | return sslEngineSource.newSslEngine(); 36 | } 37 | 38 | @Override 39 | public SSLEngine newSslEngine(String peerHost, int peerPort) { 40 | return sslEngineSource.newSslEngine(peerHost, peerPort); 41 | } 42 | }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/MitmWithEncryptedTCPChainedProxyTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import static org.littleshoot.proxy.TransportProtocol.*; 4 | 5 | import javax.net.ssl.SSLEngine; 6 | 7 | import org.littleshoot.proxy.extras.SelfSignedSslEngineSource; 8 | 9 | public class MitmWithEncryptedTCPChainedProxyTest extends MitmWithChainedProxyTest { 10 | private final SslEngineSource sslEngineSource = new SelfSignedSslEngineSource( 11 | "chain_proxy_keystore_1.jks"); 12 | 13 | @Override 14 | protected HttpProxyServerBootstrap upstreamProxy() { 15 | return super.upstreamProxy() 16 | .withTransportProtocol(TCP) 17 | .withSslEngineSource(sslEngineSource); 18 | } 19 | 20 | @Override 21 | protected ChainedProxy newChainedProxy() { 22 | return new BaseChainedProxy() { 23 | @Override 24 | public TransportProtocol getTransportProtocol() { 25 | return TransportProtocol.TCP; 26 | } 27 | 28 | @Override 29 | public boolean requiresEncryption() { 30 | return true; 31 | } 32 | 33 | @Override 34 | public SSLEngine newSslEngine() { 35 | return sslEngineSource.newSslEngine(); 36 | } 37 | 38 | @Override 39 | public SSLEngine newSslEngine(String peerHost, int peerPort) { 40 | return sslEngineSource.newSslEngine(peerHost, peerPort); 41 | } 42 | }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/MitmWithEncryptedUDTChainedProxyTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import static org.littleshoot.proxy.TransportProtocol.*; 4 | 5 | import javax.net.ssl.SSLEngine; 6 | 7 | import org.littleshoot.proxy.extras.SelfSignedSslEngineSource; 8 | 9 | public class MitmWithEncryptedUDTChainedProxyTest extends MitmWithChainedProxyTest { 10 | private final SslEngineSource sslEngineSource = new SelfSignedSslEngineSource( 11 | "chain_proxy_keystore_1.jks"); 12 | 13 | @Override 14 | protected HttpProxyServerBootstrap upstreamProxy() { 15 | return super.upstreamProxy() 16 | .withTransportProtocol(UDT) 17 | .withSslEngineSource(sslEngineSource); 18 | } 19 | 20 | @Override 21 | protected ChainedProxy newChainedProxy() { 22 | return new BaseChainedProxy() { 23 | @Override 24 | public TransportProtocol getTransportProtocol() { 25 | return TransportProtocol.UDT; 26 | } 27 | 28 | @Override 29 | public boolean requiresEncryption() { 30 | return true; 31 | } 32 | 33 | @Override 34 | public SSLEngine newSslEngine() { 35 | return sslEngineSource.newSslEngine(); 36 | } 37 | 38 | @Override 39 | public SSLEngine newSslEngine(String peerHost, int peerPort) { 40 | return sslEngineSource.newSslEngine(peerHost, peerPort); 41 | } 42 | }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/ResponseInfo.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | public class ResponseInfo { 4 | private int statusCode; 5 | private String body; 6 | 7 | public ResponseInfo(int statusCode, String body) { 8 | super(); 9 | this.statusCode = statusCode; 10 | this.body = body; 11 | } 12 | 13 | public int getStatusCode() { 14 | return statusCode; 15 | } 16 | 17 | public String getBody() { 18 | return body; 19 | } 20 | 21 | @Override 22 | public int hashCode() { 23 | final int prime = 31; 24 | int result = 1; 25 | result = prime * result + ((body == null) ? 0 : body.hashCode()); 26 | result = prime * result + statusCode; 27 | return result; 28 | } 29 | 30 | @Override 31 | public boolean equals(Object obj) { 32 | if (this == obj) 33 | return true; 34 | if (obj == null) 35 | return false; 36 | if (getClass() != obj.getClass()) 37 | return false; 38 | ResponseInfo other = (ResponseInfo) obj; 39 | if (body == null) { 40 | if (other.body != null) 41 | return false; 42 | } else if (!body.equals(other.body)) 43 | return false; 44 | if (statusCode != other.statusCode) 45 | return false; 46 | return true; 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return "ResponseInfo [statusCode=" + statusCode + ", body=" + body 52 | + "]"; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/impl/ReadLoggingHandler.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.impl; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.ChannelPromise; 5 | import io.netty.handler.logging.LogLevel; 6 | import io.netty.handler.logging.LoggingHandler; 7 | import io.netty.util.internal.logging.InternalLogLevel; 8 | import java.net.SocketAddress; 9 | import org.littleshoot.proxy.RequestTracer; 10 | 11 | public class ReadLoggingHandler extends LoggingHandler { 12 | 13 | private RequestTracer requestTracer; 14 | 15 | public ReadLoggingHandler(RequestTracer requestTracer) { 16 | super(LogLevel.DEBUG); 17 | this.requestTracer = requestTracer; 18 | } 19 | 20 | @Override 21 | public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, 22 | ChannelPromise promise) throws Exception { 23 | this.logger.log(InternalLogLevel.INFO, this.format(ctx, "Connected")); 24 | ctx.connect(remoteAddress, localAddress, promise); 25 | } 26 | 27 | @Override 28 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 29 | this.logger.log(InternalLogLevel.INFO, 30 | this.format(ctx, "Read message. trace id " + requestTracer.getCurrentTrace(ctx.channel()))); 31 | ctx.fireChannelRead(msg); 32 | } 33 | 34 | @Override 35 | public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 36 | this.logger.log(InternalLogLevel.INFO, 37 | this.format(ctx, "Read Complete. trace id " + requestTracer.getCurrentTrace(ctx.channel()))); 38 | ctx.fireChannelReadComplete(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/impl/DefaultHttpProxyServerTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.impl; 2 | 3 | import io.netty.handler.codec.http.FullHttpResponse; 4 | import io.netty.handler.codec.http.HttpRequest; 5 | import org.junit.Test; 6 | import org.littleshoot.proxy.DefaultFailureHttpResponseComposer; 7 | import org.littleshoot.proxy.FailureHttpResponseComposer; 8 | 9 | import static org.junit.Assert.assertTrue; 10 | 11 | public class DefaultHttpProxyServerTest { 12 | 13 | @Test 14 | public void testDefaultUnrecoverableFailureHttpResponseComposer() { 15 | DefaultHttpProxyServer httpProxyServer = (DefaultHttpProxyServer) DefaultHttpProxyServer.bootstrap().start(); 16 | assertTrue(httpProxyServer.getUnrecoverableFailureHttpResponseComposer() instanceof DefaultFailureHttpResponseComposer); 17 | httpProxyServer.stop(); 18 | } 19 | 20 | @Test 21 | public void testCustomUnrecoverableFailureHttpResponseComposer() { 22 | 23 | class CustomUnrecoverableFailureHttpResponseComposer implements FailureHttpResponseComposer { 24 | @Override 25 | public FullHttpResponse compose(HttpRequest httpRequest, Throwable cause) { 26 | return null; 27 | } 28 | } 29 | 30 | DefaultHttpProxyServer httpProxyServer = (DefaultHttpProxyServer) DefaultHttpProxyServer 31 | .bootstrap() 32 | .withUnrecoverableFailureHttpResponseComposer(new CustomUnrecoverableFailureHttpResponseComposer()) 33 | .start(); 34 | assertTrue(httpProxyServer.getUnrecoverableFailureHttpResponseComposer() instanceof CustomUnrecoverableFailureHttpResponseComposer); 35 | httpProxyServer.stop(); 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/ChainedProxyAdapter.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import io.netty.handler.codec.http.HttpObject; 4 | 5 | import java.net.InetSocketAddress; 6 | 7 | import javax.net.ssl.SSLEngine; 8 | 9 | /** 10 | * Convenience base class for implementations of {@link ChainedProxy}. 11 | */ 12 | public class ChainedProxyAdapter implements ChainedProxy { 13 | /** 14 | * {@link ChainedProxy} that simply has the downstream proxy make a direct 15 | * connection to the upstream server. 16 | */ 17 | public static ChainedProxy FALLBACK_TO_DIRECT_CONNECTION = new ChainedProxyAdapter(); 18 | 19 | @Override 20 | public InetSocketAddress getChainedProxyAddress() { 21 | return null; 22 | } 23 | 24 | @Override 25 | public InetSocketAddress getLocalAddress() { 26 | return null; 27 | } 28 | 29 | @Override 30 | public TransportProtocol getTransportProtocol() { 31 | return TransportProtocol.TCP; 32 | } 33 | 34 | @Override 35 | public boolean requiresEncryption() { 36 | return false; 37 | } 38 | 39 | @Override 40 | public SSLEngine newSslEngine() { 41 | return null; 42 | } 43 | 44 | @Override 45 | public void filterRequest(HttpObject httpObject) { 46 | } 47 | 48 | @Override 49 | public void connectionSucceeded() { 50 | } 51 | 52 | @Override 53 | public void connectionFailed(Throwable cause) { 54 | } 55 | 56 | @Override 57 | public void disconnected() { 58 | } 59 | 60 | @Override 61 | public SSLEngine newSslEngine(String peerHost, int peerPort) { 62 | return null; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/PerformanceServer.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import org.eclipse.jetty.server.Handler; 4 | import org.eclipse.jetty.server.Server; 5 | import org.eclipse.jetty.server.handler.DefaultHandler; 6 | import org.eclipse.jetty.server.handler.HandlerList; 7 | import org.eclipse.jetty.server.handler.ResourceHandler; 8 | import org.eclipse.jetty.server.nio.SelectChannelConnector; 9 | 10 | /** 11 | * This launches a Jetty-based HTTP server that serves static files from the 12 | * perfsite folder. 13 | */ 14 | public class PerformanceServer { 15 | public void run(int port) throws Exception { 16 | Server server = new Server(); 17 | SelectChannelConnector connector = new SelectChannelConnector(); 18 | connector.setPort(port); 19 | server.addConnector(connector); 20 | 21 | ResourceHandler resource_handler = new ResourceHandler(); 22 | resource_handler.setDirectoriesListed(true); 23 | resource_handler.setWelcomeFiles(new String[] { "index.html" }); 24 | 25 | resource_handler.setResourceBase("./performance/site/"); 26 | 27 | HandlerList handlers = new HandlerList(); 28 | handlers.setHandlers(new Handler[] { resource_handler, 29 | new DefaultHandler() }); 30 | server.setHandler(handlers); 31 | 32 | server.start(); 33 | System.out.println("Started performance file server at port: " + port); 34 | server.join(); 35 | } 36 | 37 | public static void main(String[] args) throws Exception { 38 | int port = args.length > 0 ? Integer.parseInt(args[0]) : 9000; 39 | new PerformanceServer().run(port); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/impl/TracingAwareHttpRequestEncoder.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.impl; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.handler.codec.http.HttpHeaders; 5 | import io.netty.handler.codec.http.HttpRequest; 6 | import io.netty.handler.codec.http.HttpRequestEncoder; 7 | import io.netty.util.AttributeKey; 8 | 9 | public class TracingAwareHttpRequestEncoder extends HttpRequestEncoder { 10 | 11 | private ChannelHandlerContext ctx; 12 | 13 | public static final AttributeKey IS_FORWARD_PROXY_TRANSPARENT_ATTRIBUTE = AttributeKey 14 | .valueOf("isForwardProxyTransparent"); 15 | 16 | @Override 17 | public void handlerAdded(ChannelHandlerContext ctx) throws Exception { 18 | this.ctx = ctx; 19 | } 20 | 21 | @Override 22 | protected void sanitizeHeadersBeforeEncode(HttpRequest msg, boolean isAlwaysEmpty) { 23 | HttpHeaders headers = msg.headers(); 24 | 25 | if (isForwardProxyTransparent()) { 26 | // W3C Trace Context 27 | headers.remove(TraceUtils.HEADER_TRACEPARENT); 28 | 29 | // B3 Tracing - we don't use these anymore but during the migration we are going to keep both 30 | headers.remove(TraceUtils.HEADER_X_B3_TRACEID); 31 | headers.remove(TraceUtils.HEADER_X_B3_SPANID); 32 | headers.remove(TraceUtils.HEADER_X_B3_SAMPLED); 33 | headers.remove(TraceUtils.HEADER_X_B3_PARENTSPANID); 34 | 35 | // VGS Request ID 36 | headers.remove(TraceUtils.HEADER_VGS_REQUEST_ID); 37 | } 38 | } 39 | 40 | private boolean isForwardProxyTransparent() { 41 | String isTransparent = ctx.channel().attr(IS_FORWARD_PROXY_TRANSPARENT_ATTRIBUTE).get(); 42 | return "true".equalsIgnoreCase(isTransparent); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/CustomProxyToServerExHandlerTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Ignore; 5 | import org.junit.Test; 6 | import org.littleshoot.proxy.extras.SelfSignedMitmManagerFactory; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | public class CustomProxyToServerExHandlerTest extends MitmWithBadServerAuthenticationTCPChainedProxyTest { 12 | 13 | private final List customExHandlerEntered = new ArrayList<>(); 14 | 15 | @Override 16 | protected void setUp() { 17 | this.upstreamProxy = upstreamProxy().start(); 18 | 19 | this.proxyServer = bootstrapProxy() 20 | .withPort(0) 21 | .withChainProxyManager(chainedProxyManager()) 22 | .plusActivityTracker(DOWNSTREAM_TRACKER) 23 | .withManInTheMiddle(new SelfSignedMitmManagerFactory()) 24 | .withProxyToServerExHandler(new ExceptionHandler() { 25 | @Override 26 | public void handle(Throwable cause) { 27 | customExHandlerEntered.add(cause); 28 | } 29 | }) 30 | .start(); 31 | } 32 | 33 | @Override 34 | protected void tearDown() throws Exception { 35 | this.upstreamProxy.abort(); 36 | } 37 | 38 | @Test 39 | @Ignore // this test is flack, needs to be fixed 40 | public void testCustomProxyToServerExHandler() throws Exception { 41 | super.testSimpleGetRequestOverHTTPS(); 42 | Assert.assertFalse("Custom ex handler was not called", customExHandlerEntered.isEmpty()); 43 | Assert.assertEquals("Incorrect exception was passed to custom ex handles", 44 | customExHandlerEntered.get(0).getMessage(), "javax.net.ssl.SSLHandshakeException: General SSLEngine problem"); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/FullFlowContext.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import org.littleshoot.proxy.impl.ClientToProxyConnection; 4 | import org.littleshoot.proxy.impl.ProxyToServerConnection; 5 | 6 | /** 7 | * Extension of {@link FlowContext} that provides additional information (which 8 | * we know after actually processing the request from the client). 9 | */ 10 | public class FullFlowContext extends FlowContext { 11 | private final String serverHostAndPort; 12 | private final ChainedProxy chainedProxy; 13 | private final ClientToProxyConnection clientConnection; 14 | private final ProxyToServerConnection serverConnection; 15 | 16 | public FullFlowContext(ClientToProxyConnection clientConnection, 17 | ProxyToServerConnection serverConnection) { 18 | super(clientConnection); 19 | this.serverHostAndPort = serverConnection.getServerHostAndPort(); 20 | this.chainedProxy = serverConnection.getChainedProxy(); 21 | this.clientConnection = clientConnection; 22 | this.serverConnection = serverConnection; 23 | } 24 | 25 | /** 26 | * The host and port for the server (i.e. the ultimate endpoint). 27 | * 28 | * @return 29 | */ 30 | public String getServerHostAndPort() { 31 | return serverHostAndPort; 32 | } 33 | 34 | /** 35 | * The chained proxy (if proxy chaining). 36 | * 37 | * @return 38 | */ 39 | public ChainedProxy getChainedProxy() { 40 | return chainedProxy; 41 | } 42 | 43 | public ClientToProxyConnection getClientConnection() { 44 | return clientConnection; 45 | } 46 | 47 | public ProxyToServerConnection getServerConnection() { 48 | return serverConnection; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/resources/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/UsernamePasswordAuthenticatingProxyTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import org.littleshoot.proxy.authenticator.BasicProxyAuthenticator; 4 | 5 | import io.netty.handler.codec.http.HttpRequest; 6 | 7 | /** 8 | * Tests a single proxy that requires username/password authentication. 9 | */ 10 | public class UsernamePasswordAuthenticatingProxyTest extends BaseProxyTest { 11 | public final static String USERNAME = "user1"; 12 | public final static String PASSWORD = "password"; 13 | 14 | @Override 15 | protected void setUp() { 16 | this.proxyServer = bootstrapProxy() 17 | .withPort(0) 18 | .withProxyAuthenticator(new TestBasicProxyAuthenticator(USERNAME, PASSWORD)) 19 | .start(); 20 | } 21 | 22 | @Override 23 | protected String getUsername() { 24 | return USERNAME; 25 | } 26 | 27 | @Override 28 | protected String getPassword() { 29 | return PASSWORD; 30 | } 31 | 32 | @Override 33 | protected boolean isAuthenticating() { 34 | return true; 35 | } 36 | 37 | public static class TestBasicProxyAuthenticator extends BasicProxyAuthenticator { 38 | 39 | private final String username; 40 | private final String password; 41 | 42 | public TestBasicProxyAuthenticator(String username, String password) { 43 | this.username = username; 44 | this.password = password; 45 | } 46 | 47 | @Override 48 | public String getRealm() { 49 | return null; 50 | } 51 | 52 | @Override 53 | public boolean authenticate(String username, String password, HttpRequest request) { 54 | return this.username.equals(username) && this.password.equals(password); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/io/netty/handler/codec/compression/AbstractCompressionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.handler.codec.compression; 17 | 18 | import java.util.Random; 19 | 20 | /* 21 | This is a helper class that Netty uses in their test sources for building the sample data buffers 22 | to test their compression and decompression codecs. The latest version can be found here: 23 | 24 | https://github.com/netty/netty/blob/4.1/codec/src/test/java/io/netty/handler/codec/compression/AbstractCompressionTest.java 25 | 26 | */ 27 | public abstract class AbstractCompressionTest { 28 | 29 | protected static final Random rand; 30 | 31 | protected static final byte[] BYTES_SMALL = new byte[256]; 32 | protected static final byte[] BYTES_LARGE = new byte[256 * 1024]; 33 | 34 | static { 35 | rand = new Random(); 36 | fillArrayWithCompressibleData(BYTES_SMALL); 37 | fillArrayWithCompressibleData(BYTES_LARGE); 38 | } 39 | 40 | private static void fillArrayWithCompressibleData(byte[] array) { 41 | for (int i = 0; i < array.length; i++) { 42 | array[i] = i % 4 != 0 ? 0 : (byte) rand.nextInt(); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/authenticator/AbstractProxyAuthenticator.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.authenticator; 2 | 3 | import org.littleshoot.proxy.ProxyAuthenticator; 4 | import org.littleshoot.proxy.impl.ProxyUtils; 5 | 6 | import java.util.Date; 7 | 8 | import io.netty.handler.codec.http.FullHttpResponse; 9 | import io.netty.handler.codec.http.HttpHeaders; 10 | import io.netty.handler.codec.http.HttpRequest; 11 | import io.netty.handler.codec.http.HttpResponseStatus; 12 | import io.netty.handler.codec.http.HttpVersion; 13 | 14 | public abstract class AbstractProxyAuthenticator implements ProxyAuthenticator { 15 | 16 | @Override 17 | public abstract boolean authenticate(HttpRequest request); 18 | 19 | @Override 20 | public abstract String getRealm(); 21 | 22 | @Override 23 | public FullHttpResponse authenticationFailureResponse(HttpRequest request) { 24 | String body = "\n" 25 | + "\n" 26 | + "407 Proxy Authentication Required\n" 27 | + "\n" 28 | + "

Proxy Authentication Required

\n" 29 | + "

This server could not verify that you\n" 30 | + "are authorized to access the document\n" 31 | + "requested. Either you supplied the wrong\n" 32 | + "credentials (e.g., bad password), or your\n" 33 | + "browser doesn't understand how to supply\n" 34 | + "the credentials required.

\n" + "\n"; 35 | FullHttpResponse response = ProxyUtils.createFullHttpResponse(HttpVersion.HTTP_1_1, 36 | HttpResponseStatus.PROXY_AUTHENTICATION_REQUIRED, body); 37 | HttpHeaders.setDate(response, new Date()); 38 | response.headers().set("Proxy-Authenticate", "Basic realm=\"" + (getRealm() == null ? "Restricted Files" : getRealm()) + "\""); 39 | return response; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/ClientAuthenticationNotRequiredTCPChainedProxyTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import static org.littleshoot.proxy.TransportProtocol.*; 4 | 5 | import javax.net.ssl.SSLEngine; 6 | 7 | import org.littleshoot.proxy.extras.SelfSignedSslEngineSource; 8 | 9 | /** 10 | * Tests that when client authentication is not required, it doesn't matter what 11 | * certs the client sends. 12 | */ 13 | public class ClientAuthenticationNotRequiredTCPChainedProxyTest extends 14 | BaseChainedProxyTest { 15 | private final SslEngineSource serverSslEngineSource = new SelfSignedSslEngineSource( 16 | "chain_proxy_keystore_1.jks"); 17 | 18 | private final SslEngineSource clientSslEngineSource = new SelfSignedSslEngineSource( 19 | "chain_proxy_keystore_1.jks", false, false); 20 | 21 | @Override 22 | protected HttpProxyServerBootstrap upstreamProxy() { 23 | return super.upstreamProxy() 24 | .withTransportProtocol(TCP) 25 | .withSslEngineSource(serverSslEngineSource) 26 | .withAuthenticateSslClients(false); 27 | } 28 | 29 | @Override 30 | protected ChainedProxy newChainedProxy() { 31 | return new BaseChainedProxy() { 32 | @Override 33 | public TransportProtocol getTransportProtocol() { 34 | return TransportProtocol.TCP; 35 | } 36 | 37 | @Override 38 | public boolean requiresEncryption() { 39 | return true; 40 | } 41 | 42 | @Override 43 | public SSLEngine newSslEngine() { 44 | return clientSslEngineSource.newSslEngine(); 45 | } 46 | 47 | @Override 48 | public SSLEngine newSslEngine(String peerHost, int peerPort) { 49 | return clientSslEngineSource.newSslEngine(peerHost, peerPort); 50 | } 51 | }; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/MitmWithClientAuthenticationNotRequiredTCPChainedProxyTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import static org.littleshoot.proxy.TransportProtocol.*; 4 | 5 | import javax.net.ssl.SSLEngine; 6 | 7 | import org.littleshoot.proxy.extras.SelfSignedSslEngineSource; 8 | 9 | /** 10 | * Tests that when client authentication is not required, it doesn't matter what 11 | * certs the client sends. 12 | */ 13 | public class MitmWithClientAuthenticationNotRequiredTCPChainedProxyTest extends 14 | MitmWithChainedProxyTest { 15 | private final SslEngineSource serverSslEngineSource = new SelfSignedSslEngineSource( 16 | "chain_proxy_keystore_1.jks"); 17 | 18 | private final SslEngineSource clientSslEngineSource = new SelfSignedSslEngineSource( 19 | "chain_proxy_keystore_1.jks", false, false); 20 | 21 | @Override 22 | protected HttpProxyServerBootstrap upstreamProxy() { 23 | return super.upstreamProxy() 24 | .withTransportProtocol(TCP) 25 | .withSslEngineSource(serverSslEngineSource) 26 | .withAuthenticateSslClients(false); 27 | } 28 | 29 | @Override 30 | protected ChainedProxy newChainedProxy() { 31 | return new BaseChainedProxy() { 32 | @Override 33 | public TransportProtocol getTransportProtocol() { 34 | return TransportProtocol.TCP; 35 | } 36 | 37 | @Override 38 | public boolean requiresEncryption() { 39 | return true; 40 | } 41 | 42 | @Override 43 | public SSLEngine newSslEngine() { 44 | return clientSslEngineSource.newSslEngine(); 45 | } 46 | 47 | @Override 48 | public SSLEngine newSslEngine(String peerHost, int peerPort) { 49 | return clientSslEngineSource.newSslEngine(peerHost, peerPort); 50 | } 51 | }; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/BadServerAuthenticationTCPChainedProxyTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import static org.littleshoot.proxy.TransportProtocol.*; 4 | 5 | import javax.net.ssl.SSLEngine; 6 | 7 | import org.littleshoot.proxy.extras.SelfSignedSslEngineSource; 8 | 9 | /** 10 | * Tests that servers are authenticated and that if they're missing certs, we 11 | * get an error. 12 | */ 13 | public class BadServerAuthenticationTCPChainedProxyTest extends 14 | BaseChainedProxyTest { 15 | protected final SslEngineSource serverSslEngineSource = new SelfSignedSslEngineSource( 16 | "chain_proxy_keystore_1.jks"); 17 | 18 | protected final SslEngineSource clientSslEngineSource = new SelfSignedSslEngineSource( 19 | "chain_proxy_keystore_2.jks"); 20 | 21 | @Override 22 | protected boolean expectBadGatewayForEverything() { 23 | return true; 24 | } 25 | 26 | @Override 27 | protected HttpProxyServerBootstrap upstreamProxy() { 28 | return super.upstreamProxy() 29 | .withTransportProtocol(TCP) 30 | .withSslEngineSource(serverSslEngineSource); 31 | } 32 | 33 | @Override 34 | protected ChainedProxy newChainedProxy() { 35 | return new BaseChainedProxy() { 36 | @Override 37 | public TransportProtocol getTransportProtocol() { 38 | return TransportProtocol.TCP; 39 | } 40 | 41 | @Override 42 | public boolean requiresEncryption() { 43 | return true; 44 | } 45 | 46 | @Override 47 | public SSLEngine newSslEngine() { 48 | return clientSslEngineSource.newSslEngine(); 49 | } 50 | 51 | @Override 52 | public SSLEngine newSslEngine(String peerHost, int peerPort) { 53 | return clientSslEngineSource.newSslEngine(peerHost, peerPort); 54 | } 55 | }; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/BadClientAuthenticationTCPChainedProxyTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import static org.littleshoot.proxy.TransportProtocol.*; 4 | 5 | import javax.net.ssl.SSLEngine; 6 | 7 | import org.littleshoot.proxy.extras.SelfSignedSslEngineSource; 8 | 9 | /** 10 | * Tests that clients are authenticated and that if they're missing certs, we 11 | * get an error. 12 | */ 13 | public class BadClientAuthenticationTCPChainedProxyTest extends 14 | BaseChainedProxyTest { 15 | private final SslEngineSource serverSslEngineSource = new SelfSignedSslEngineSource( 16 | "chain_proxy_keystore_1.jks"); 17 | 18 | private final SslEngineSource clientSslEngineSource = new SelfSignedSslEngineSource( 19 | "chain_proxy_keystore_1.jks", false, false); 20 | 21 | @Override 22 | protected boolean expectBadGatewayForEverything() { 23 | return true; 24 | } 25 | 26 | @Override 27 | protected HttpProxyServerBootstrap upstreamProxy() { 28 | return super.upstreamProxy() 29 | .withTransportProtocol(TCP) 30 | .withSslEngineSource(serverSslEngineSource); 31 | } 32 | 33 | @Override 34 | protected ChainedProxy newChainedProxy() { 35 | return new BaseChainedProxy() { 36 | @Override 37 | public TransportProtocol getTransportProtocol() { 38 | return TransportProtocol.TCP; 39 | } 40 | 41 | @Override 42 | public boolean requiresEncryption() { 43 | return true; 44 | } 45 | 46 | @Override 47 | public SSLEngine newSslEngine() { 48 | return clientSslEngineSource.newSslEngine(); 49 | } 50 | 51 | @Override 52 | public SSLEngine newSslEngine(String peerHost, int peerPort) { 53 | return clientSslEngineSource.newSslEngine(peerHost, peerPort); 54 | } 55 | }; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/CustomClientToProxyExHandlerTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.handler.codec.http.HttpRequest; 5 | import org.apache.http.NoHttpResponseException; 6 | import org.junit.Assert; 7 | import org.junit.Test; 8 | import org.littleshoot.proxy.extras.SelfSignedMitmManagerFactory; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | public class CustomClientToProxyExHandlerTest extends AbstractProxyTest { 14 | 15 | private final List customExHandlerEntered = new ArrayList<>(); 16 | 17 | private static final String EXCEPTION_MESSAGE = "Error occurred in client to proxy connection"; 18 | 19 | @Override 20 | protected void setUp() { 21 | this.proxyServer = bootstrapProxy() 22 | .withPort(0) 23 | .withManInTheMiddle(new SelfSignedMitmManagerFactory()) 24 | .withClientToProxyExHandler(new ExceptionHandler() { 25 | @Override 26 | public void handle(Throwable cause) { 27 | customExHandlerEntered.add(cause); 28 | } 29 | }) 30 | .withFiltersSource(new HttpFiltersSourceAdapter() { 31 | @Override 32 | public HttpFilters filterRequest(HttpRequest originalRequest, 33 | ChannelHandlerContext ctx) { 34 | throw new RuntimeException(EXCEPTION_MESSAGE); 35 | } 36 | }) 37 | .start(); 38 | } 39 | 40 | @Test 41 | public void testCustomClientToProxyExHandler() throws Exception { 42 | try { 43 | httpGetWithApacheClient(webHost, DEFAULT_RESOURCE, true, true); 44 | } catch (NoHttpResponseException e) { 45 | // expected 46 | } 47 | Assert.assertFalse("Custom ex handler was not called", customExHandlerEntered.isEmpty()); 48 | Assert.assertEquals("Incorrect exception was passed to custom ex handles", 49 | customExHandlerEntered.get(0).getMessage(), EXCEPTION_MESSAGE); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/impl/NetworkUtils.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.impl; 2 | 3 | import java.net.InetAddress; 4 | import java.net.InterfaceAddress; 5 | import java.net.NetworkInterface; 6 | import java.net.SocketException; 7 | import java.net.UnknownHostException; 8 | import java.util.Enumeration; 9 | 10 | /** 11 | * @deprecated This class is no longer used by LittleProxy and may be removed in a future release. 12 | */ 13 | @Deprecated 14 | public class NetworkUtils { 15 | /** 16 | * @deprecated This method is no longer used by LittleProxy and may be removed in a future release. 17 | */ 18 | @Deprecated 19 | public static InetAddress getLocalHost() throws UnknownHostException { 20 | return InetAddress.getLocalHost(); 21 | } 22 | 23 | /** 24 | * @deprecated This method is no longer used by LittleProxy and may be removed in a future release. 25 | */ 26 | @Deprecated 27 | public static InetAddress firstLocalNonLoopbackIpv4Address() { 28 | try { 29 | Enumeration networkInterfaces = NetworkInterface 30 | .getNetworkInterfaces(); 31 | while (networkInterfaces.hasMoreElements()) { 32 | NetworkInterface networkInterface = networkInterfaces 33 | .nextElement(); 34 | if (networkInterface.isUp()) { 35 | for (InterfaceAddress ifAddress : networkInterface 36 | .getInterfaceAddresses()) { 37 | if (ifAddress.getNetworkPrefixLength() > 0 38 | && ifAddress.getNetworkPrefixLength() <= 32 39 | && !ifAddress.getAddress().isLoopbackAddress()) { 40 | return ifAddress.getAddress(); 41 | } 42 | } 43 | } 44 | } 45 | return null; 46 | } catch (SocketException se) { 47 | return null; 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/MitmWithBadServerAuthenticationTCPChainedProxyTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import static org.littleshoot.proxy.TransportProtocol.*; 4 | 5 | import javax.net.ssl.SSLEngine; 6 | 7 | import org.junit.Ignore; 8 | import org.littleshoot.proxy.extras.SelfSignedSslEngineSource; 9 | 10 | /** 11 | * Tests that servers are authenticated and that if they're missing certs, we 12 | * get an error. 13 | */ 14 | public class MitmWithBadServerAuthenticationTCPChainedProxyTest extends 15 | MitmWithChainedProxyTest { 16 | protected final SslEngineSource serverSslEngineSource = new SelfSignedSslEngineSource( 17 | "chain_proxy_keystore_1.jks"); 18 | 19 | protected final SslEngineSource clientSslEngineSource = new SelfSignedSslEngineSource( 20 | "chain_proxy_keystore_2.jks"); 21 | 22 | @Override 23 | protected boolean expectBadGatewayForEverything() { 24 | return true; 25 | } 26 | 27 | @Override 28 | protected HttpProxyServerBootstrap upstreamProxy() { 29 | return super.upstreamProxy() 30 | .withTransportProtocol(TCP) 31 | .withSslEngineSource(serverSslEngineSource); 32 | } 33 | 34 | @Override 35 | protected ChainedProxy newChainedProxy() { 36 | return new BaseChainedProxy() { 37 | @Override 38 | public TransportProtocol getTransportProtocol() { 39 | return TransportProtocol.TCP; 40 | } 41 | 42 | @Override 43 | public boolean requiresEncryption() { 44 | return true; 45 | } 46 | 47 | @Override 48 | public SSLEngine newSslEngine() { 49 | return clientSslEngineSource.newSslEngine(); 50 | } 51 | 52 | @Override 53 | public SSLEngine newSslEngine(String peerHost, int peerPort) { 54 | return clientSslEngineSource.newSslEngine(peerHost, peerPort); 55 | } 56 | }; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Netty_4_Upgrade_Notes.md: -------------------------------------------------------------------------------- 1 | Resources 2 | --------- 3 | 4 | http://netty.io/news/2013/06/18/4-0-0-CR5.html 5 | http://netty.io/4.0/api 6 | http://docs.jboss.org/netty/3.2/api/ 7 | 8 | 9 | Relevant Changes 10 | ---------------- 11 | * ServerBootstrap no longer takes a ServerSocketChannelFactory in its 12 | constructor. We used to use this to configure the thread pools. Looks like 13 | EventLoopGroup or its relatives are the place to look. 14 | 15 | * Same thing as #1 is true for ClientSocketChannelFactory. 16 | 17 | * ChannelPipelineFactory is gone. Looks like one uses .childHandler() with a 18 | ChannelInitializer instead. 19 | 20 | * What's up with ThreadRenamingRunnable in DefaultHttpProxyServer? 21 | 22 | * DefaultChannelGroup? 23 | 24 | * SimpleChannelUpstreamHandler -> SimpleChanneInboundHandler 25 | 26 | * InterestOps is gone - what does this mean to setReadable() and 27 | channelInterestChanged() ? 28 | 29 | * IdleStateHandler no longer users Timer. This may be a problem, because it's 30 | just scheduling things on an EventExecutor obtained from the underlying 31 | EventLoopGroup. That infrastructure doesn't appear to use Timers at all. 32 | 33 | * messageReceived() -> channelRead0 with POJO typed message 34 | 35 | * HttpChunk -> HttpObject 36 | 37 | * HttpChunkAggregator -> HttpObjectAggregator 38 | 39 | * Channel lifecycle is different: 40 | * old: open -> bound -> connected 41 | * new: open -> registered -> active ?? 42 | 43 | * channelOpen() -> channelRegistered() ? note that we added call to super 44 | 45 | * channelClosed() -> channelUnregistered() ? note that we added call to super 46 | 47 | * lifecycle callbacks no longer get a ChannelStateEvent. We often grabbed the 48 | channel from the event, we now grab it from the ChannelHandlerContext. 49 | 50 | * IdleStateAwareChannelHandler -> ChannelDuplexHandler 51 | 52 | * ChannelBuffer -> ByteBuf 53 | 54 | * Headers on HttpObject are no longer accessed directly, but first by getting 55 | the headers object using .headers() 56 | 57 | * ChannelBuffers -> Unpooled -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/MitmWithBadClientAuthenticationTCPChainedProxyTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import static org.littleshoot.proxy.TransportProtocol.*; 4 | 5 | import javax.net.ssl.SSLEngine; 6 | 7 | import org.junit.Ignore; 8 | import org.littleshoot.proxy.extras.SelfSignedSslEngineSource; 9 | 10 | /** 11 | * Tests that clients are authenticated and that if they're missing certs, we 12 | * get an error. 13 | */ 14 | public class MitmWithBadClientAuthenticationTCPChainedProxyTest extends 15 | MitmWithChainedProxyTest { 16 | private final SslEngineSource serverSslEngineSource = new SelfSignedSslEngineSource( 17 | "chain_proxy_keystore_1.jks"); 18 | 19 | private final SslEngineSource clientSslEngineSource = new SelfSignedSslEngineSource( 20 | "chain_proxy_keystore_1.jks", false, false); 21 | 22 | @Override 23 | protected boolean expectBadGatewayForEverything() { 24 | return true; 25 | } 26 | 27 | @Override 28 | protected HttpProxyServerBootstrap upstreamProxy() { 29 | return super.upstreamProxy() 30 | .withTransportProtocol(TCP) 31 | .withSslEngineSource(serverSslEngineSource); 32 | } 33 | 34 | @Override 35 | protected ChainedProxy newChainedProxy() { 36 | return new BaseChainedProxy() { 37 | @Override 38 | public TransportProtocol getTransportProtocol() { 39 | return TransportProtocol.TCP; 40 | } 41 | 42 | @Override 43 | public boolean requiresEncryption() { 44 | return true; 45 | } 46 | 47 | @Override 48 | public SSLEngine newSslEngine() { 49 | return clientSslEngineSource.newSslEngine(); 50 | } 51 | 52 | @Override 53 | public SSLEngine newSslEngine(String peerHost, int peerPort) { 54 | return clientSslEngineSource.newSslEngine(peerHost, peerPort); 55 | } 56 | }; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/ActivityTrackerAdapter.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import io.netty.handler.codec.http.HttpRequest; 4 | import io.netty.handler.codec.http.HttpResponse; 5 | 6 | import java.net.InetSocketAddress; 7 | 8 | import javax.net.ssl.SSLSession; 9 | 10 | /** 11 | * Adapter of {@link ActivityTracker} interface that provides default no-op 12 | * implementations of all methods. 13 | */ 14 | public class ActivityTrackerAdapter implements ActivityTracker { 15 | 16 | @Override 17 | public void bytesReceivedFromClient(FlowContext flowContext, 18 | int numberOfBytes) { 19 | } 20 | 21 | @Override 22 | public void requestReceivedFromClient(FlowContext flowContext, 23 | HttpRequest httpRequest) { 24 | } 25 | 26 | @Override 27 | public void bytesSentToServer(FullFlowContext flowContext, int numberOfBytes) { 28 | } 29 | 30 | @Override 31 | public void requestSentToServer(FullFlowContext flowContext, 32 | HttpRequest httpRequest) { 33 | } 34 | 35 | @Override 36 | public void bytesReceivedFromServer(FullFlowContext flowContext, 37 | int numberOfBytes) { 38 | } 39 | 40 | @Override 41 | public void responseReceivedFromServer(FullFlowContext flowContext, 42 | HttpResponse httpResponse) { 43 | } 44 | 45 | @Override 46 | public void bytesSentToClient(FlowContext flowContext, 47 | int numberOfBytes) { 48 | } 49 | 50 | @Override 51 | public void responseSentToClient(FlowContext flowContext, 52 | HttpResponse httpResponse) { 53 | } 54 | 55 | @Override 56 | public void clientConnected(InetSocketAddress clientAddress) { 57 | } 58 | 59 | @Override 60 | public void clientSSLHandshakeSucceeded(InetSocketAddress clientAddress, 61 | SSLSession sslSession) { 62 | } 63 | 64 | @Override 65 | public void clientDisconnected(FlowContext flowContext, 66 | InetSocketAddress clientAddress, 67 | SSLSession sslSession) { 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/BaseProxyTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import org.apache.http.HttpHost; 4 | import org.junit.Test; 5 | 6 | /** 7 | * Base for tests that test the proxy. This base class encapsulates all of the 8 | * tests and test conditions. Sub-classes should provide different 9 | * {@link #setUp()} and {@link #tearDown()} methods for testing different 10 | * configurations of the proxy (e.g. single versus chained, tunneling, etc.). 11 | */ 12 | public abstract class BaseProxyTest extends AbstractProxyTest { 13 | @Test 14 | public void testSimpleGetRequest() throws Exception { 15 | lastResponse = 16 | compareProxiedAndUnproxiedGET(webHost, DEFAULT_RESOURCE); 17 | } 18 | 19 | @Test 20 | public void testSimpleGetRequestOverHTTPS() throws Exception { 21 | lastResponse = 22 | compareProxiedAndUnproxiedGET(httpsWebHost, DEFAULT_RESOURCE); 23 | } 24 | 25 | @Test 26 | public void testSimplePostRequest() throws Exception { 27 | lastResponse = 28 | compareProxiedAndUnproxiedPOST(webHost, DEFAULT_RESOURCE); 29 | } 30 | 31 | @Test 32 | public void testSimplePostRequestOverHTTPS() throws Exception { 33 | lastResponse = 34 | compareProxiedAndUnproxiedPOST(httpsWebHost, DEFAULT_RESOURCE); 35 | } 36 | 37 | /** 38 | * This test tests a HEAD followed by a GET for the same resource, making 39 | * sure that the requests complete and that the Content-Length matches. 40 | * 41 | * @throws Exception 42 | */ 43 | @Test 44 | public void testHeadRequestFollowedByGet() throws Exception { 45 | httpGetWithApacheClient(webHost, DEFAULT_RESOURCE, true, true); 46 | } 47 | 48 | @Test 49 | public void testProxyWithBadAddress() 50 | throws Exception { 51 | ResponseInfo response = 52 | httpPostWithApacheClient(new HttpHost("test.localhost"), 53 | DEFAULT_RESOURCE, true); 54 | assertReceivedBadGateway(response); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/ChainedProxyWithFallbackToOtherChainedProxyDueToSSLTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import io.netty.handler.codec.http.HttpRequest; 4 | 5 | import java.util.Queue; 6 | 7 | import javax.net.ssl.SSLEngine; 8 | 9 | /** 10 | * Tests a proxy chained to a downstream proxy with an untrusted SSL cert. When 11 | * the downstream proxy is unavailable, the downstream proxy should just fall 12 | * back to a the next chained proxy. 13 | */ 14 | public class ChainedProxyWithFallbackToOtherChainedProxyDueToSSLTest extends 15 | BadServerAuthenticationTCPChainedProxyTest { 16 | @Override 17 | protected boolean expectBadGatewayForEverything() { 18 | return false; 19 | } 20 | 21 | protected ChainedProxyManager chainedProxyManager() { 22 | return new ChainedProxyManager() { 23 | @Override 24 | public void lookupChainedProxies(HttpRequest httpRequest, 25 | Queue chainedProxies) { 26 | // This first one has a bad cert 27 | chainedProxies.add(newChainedProxy()); 28 | // This 2nd one should work 29 | chainedProxies.add(new BaseChainedProxy() { 30 | @Override 31 | public TransportProtocol getTransportProtocol() { 32 | return TransportProtocol.TCP; 33 | } 34 | 35 | @Override 36 | public boolean requiresEncryption() { 37 | return true; 38 | } 39 | 40 | @Override 41 | public SSLEngine newSslEngine() { 42 | return serverSslEngineSource.newSslEngine(); 43 | } 44 | 45 | @Override 46 | public SSLEngine newSslEngine(String peerHost, int peerPort) { 47 | return serverSslEngineSource.newSslEngine(peerHost, peerPort); 48 | } 49 | }); 50 | } 51 | }; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/DefaultFailureHttpResponseComposer.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import io.netty.handler.codec.http.FullHttpResponse; 4 | import io.netty.handler.codec.http.HttpRequest; 5 | import io.netty.handler.codec.http.HttpResponseStatus; 6 | import io.netty.handler.codec.http.HttpVersion; 7 | import org.littleshoot.proxy.impl.ProxyUtils; 8 | 9 | public class DefaultFailureHttpResponseComposer implements FailureHttpResponseComposer { 10 | 11 | /** 12 | * Tells the client that something went wrong trying to proxy its request. If the Bad Gateway is a response to 13 | * an HTTP HEAD request, the response will contain no body, but the Content-Length header will be set to the 14 | * value it would have been if this 502 Bad Gateway were in response to a GET. 15 | * 16 | * @param httpRequest the HttpRequest that is resulting in the Bad Gateway response 17 | * @param cause raised exception 18 | * @return true if the connection will be kept open, or false if it will be disconnected 19 | */ 20 | @Override 21 | public FullHttpResponse compose(HttpRequest httpRequest, Throwable cause) { 22 | String body = provideCustomMessage(httpRequest, cause); 23 | HttpResponseStatus status = provideCustomStatus(httpRequest, cause); 24 | 25 | FullHttpResponse response = ProxyUtils.createFullHttpResponse(HttpVersion.HTTP_1_1, status, body); 26 | 27 | if (ProxyUtils.isHEAD(httpRequest)) { 28 | // don't allow any body content in response to a HEAD request 29 | response.content().clear(); 30 | } 31 | return response; 32 | } 33 | 34 | /** 35 | * The method can be overridden to provide a custom message along with 502 code 36 | * @param httpRequest initial request 37 | * @param cause an exception thrown on a failure 38 | * @return custom message 39 | */ 40 | protected String provideCustomMessage(HttpRequest httpRequest, Throwable cause) { 41 | return "Bad Gateway: " + httpRequest.getUri(); 42 | } 43 | 44 | protected HttpResponseStatus provideCustomStatus(HttpRequest httpRequest, Throwable cause) { 45 | return HttpResponseStatus.BAD_GATEWAY; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/impl/CategorizedThreadFactory.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.impl; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.util.concurrent.ThreadFactory; 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | 9 | /** 10 | * A ThreadFactory that adds LittleProxy-specific information to the threads' names. 11 | */ 12 | public class CategorizedThreadFactory implements ThreadFactory { 13 | private static final Logger log = LoggerFactory.getLogger(CategorizedThreadFactory.class); 14 | 15 | private final String name; 16 | private final String category; 17 | private final int uniqueServerGroupId; 18 | 19 | private AtomicInteger threadCount = new AtomicInteger(0); 20 | 21 | /** 22 | * Exception handler for proxy threads. Logs the name of the thread and the exception that was caught. 23 | */ 24 | private static final Thread.UncaughtExceptionHandler UNCAUGHT_EXCEPTION_HANDLER = new Thread.UncaughtExceptionHandler() { 25 | @Override 26 | public void uncaughtException(Thread t, Throwable e) { 27 | log.error("Uncaught throwable in thread: {}", t.getName(), e); 28 | } 29 | }; 30 | 31 | 32 | /** 33 | * @param name the user-supplied name of this proxy 34 | * @param category the type of threads this factory is creating (acceptor, client-to-proxy worker, proxy-to-server worker) 35 | * @param uniqueServerGroupId a unique number for the server group creating this thread factory, to differentiate multiple proxy instances with the same name 36 | */ 37 | public CategorizedThreadFactory(String name, String category, int uniqueServerGroupId) { 38 | this.category = category; 39 | this.name = name; 40 | this.uniqueServerGroupId = uniqueServerGroupId; 41 | } 42 | 43 | @Override 44 | public Thread newThread(Runnable r) { 45 | Thread t = new Thread(r, name + "-" + uniqueServerGroupId + "-" + category + "-" + threadCount.getAndIncrement()); 46 | 47 | t.setUncaughtExceptionHandler(UNCAUGHT_EXCEPTION_HANDLER); 48 | 49 | return t; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/MitmManager.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import io.netty.handler.codec.http.HttpRequest; 4 | 5 | import javax.net.ssl.SSLEngine; 6 | import javax.net.ssl.SSLSession; 7 | 8 | /** 9 | * MITMManagers encapsulate the logic required for letting LittleProxy act as a 10 | * man in the middle for HTTPS requests. 11 | */ 12 | public interface MitmManager { 13 | /** 14 | * Creates an {@link SSLEngine} for encrypting the server connection. The SSLEngine created by this method 15 | * may use the given peer information to send SNI information when connecting to the upstream host. 16 | * 17 | * @param peerHost to start a client connection to the server. 18 | * @param peerPort to start a client connection to the server. 19 | * 20 | * @return an SSLEngine used to connect to an upstream server 21 | */ 22 | SSLEngine serverSslEngine(String peerHost, int peerPort); 23 | 24 | /** 25 | * Creates an {@link SSLEngine} for encrypting the server connection. 26 | * 27 | * @return an SSLEngine used to connect to an upstream server 28 | */ 29 | SSLEngine serverSslEngine(); 30 | 31 | /** 32 | *

33 | * Creates an {@link SSLEngine} for encrypting the client connection based 34 | * on the given serverSslSession. 35 | *

36 | * 37 | *

38 | * The serverSslSession is provided in case this method needs to inspect the 39 | * server's certificates or something else about the encryption on the way 40 | * to the server. 41 | *

42 | * 43 | *

44 | * This is the place where one would implement impersonation of the server 45 | * by issuing replacement certificates signed by the proxy's own 46 | * certificate. 47 | *

48 | * 49 | * @param httpRequest the HTTP CONNECT request that is being man-in-the-middled 50 | * @param serverSslSession the {@link SSLSession} that's been established with the server 51 | * @return the SSLEngine used to connect to the client 52 | */ 53 | SSLEngine clientSslEngineFor(HttpRequest httpRequest, SSLSession serverSslSession); 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/impl/ProxyToServerConnectionUtilsTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.impl; 2 | 3 | import org.junit.Test; 4 | import org.littleshoot.proxy.HostResolver; 5 | 6 | import java.net.UnknownHostException; 7 | 8 | import static org.mockito.Mockito.mock; 9 | import static org.mockito.Mockito.verify; 10 | import static org.mockito.Mockito.when; 11 | 12 | /** 13 | * Unit tests for static helper methods in {@link ProxyToServerConnection}. 14 | */ 15 | public class ProxyToServerConnectionUtilsTest { 16 | @Test 17 | public void testParseAddresses() throws UnknownHostException { 18 | // mock out the proxy server and resolver; this test only verifies the addresses parse correctly 19 | DefaultHttpProxyServer mockProxyServer = mock(DefaultHttpProxyServer.class); 20 | HostResolver mockHostResolver = mock(HostResolver.class); 21 | 22 | when(mockProxyServer.getServerResolver()).thenReturn(mockHostResolver); 23 | 24 | ProxyToServerConnection.addressFor("192.168.1.1", mockProxyServer); 25 | verify(mockHostResolver).resolve("192.168.1.1", 80); 26 | 27 | ProxyToServerConnection.addressFor("192.168.1.1:72", mockProxyServer); 28 | verify(mockHostResolver).resolve("192.168.1.1", 72); 29 | 30 | ProxyToServerConnection.addressFor("www.google.com", mockProxyServer); 31 | verify(mockHostResolver).resolve("www.google.com", 80); 32 | 33 | ProxyToServerConnection.addressFor("www.google.com:19650", mockProxyServer); 34 | verify(mockHostResolver).resolve("www.google.com", 19650); 35 | 36 | ProxyToServerConnection.addressFor("[::1]", mockProxyServer); 37 | verify(mockHostResolver).resolve("::1", 80); 38 | 39 | ProxyToServerConnection.addressFor("[::1]:56500", mockProxyServer); 40 | verify(mockHostResolver).resolve("::1", 56500); 41 | 42 | ProxyToServerConnection.addressFor("[a:b:c:d::1]", mockProxyServer); 43 | verify(mockHostResolver).resolve("a:b:c:d::1", 80); 44 | 45 | ProxyToServerConnection.addressFor("[a:b:c:d::1]:8650", mockProxyServer); 46 | verify(mockHostResolver).resolve("a:b:c:d::1", 8650); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/HttpFiltersSource.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.handler.codec.http.FullHttpRequest; 5 | import io.netty.handler.codec.http.FullHttpResponse; 6 | import io.netty.handler.codec.http.HttpRequest; 7 | import io.netty.handler.codec.http.HttpResponse; 8 | 9 | /** 10 | * Factory for {@link HttpFilters}. 11 | */ 12 | public interface HttpFiltersSource { 13 | /** 14 | * Return an {@link HttpFilters} object for this request if and only if we 15 | * want to filter the request and/or its responses. 16 | * 17 | * @param originalRequest 18 | * @return 19 | */ 20 | HttpFilters filterRequest(HttpRequest originalRequest, 21 | ChannelHandlerContext ctx); 22 | 23 | /** 24 | * Indicate how many (if any) bytes to buffer for incoming 25 | * {@link HttpRequest}s. A value of 0 or less indicates that no buffering 26 | * should happen and that messages will be passed to the {@link HttpFilters} 27 | * request filtering methods chunk by chunk. A positive value will cause 28 | * LittleProxy to try an create a {@link FullHttpRequest} using the data 29 | * received from the client, with its content already decompressed (in case 30 | * the client was compressing it). If the request size exceeds the maximum 31 | * buffer size, the request will fail. 32 | * 33 | * @return 34 | */ 35 | int getMaximumRequestBufferSizeInBytes(); 36 | 37 | /** 38 | * Indicate how many (if any) bytes to buffer for incoming 39 | * {@link HttpResponse}s. A value of 0 or less indicates that no buffering 40 | * should happen and that messages will be passed to the {@link HttpFilters} 41 | * response filtering methods chunk by chunk. A positive value will cause 42 | * LittleProxy to try an create a {@link FullHttpResponse} using the data 43 | * received from the server, with its content already decompressed (in case 44 | * the server was compressing it). If the response size exceeds the maximum 45 | * buffer size, the response will fail. 46 | * 47 | * @return 48 | */ 49 | int getMaximumResponseBufferSizeInBytes(); 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/ChainedProxy.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import io.netty.handler.codec.http.HttpObject; 4 | 5 | import java.net.InetSocketAddress; 6 | 7 | /** 8 | *

9 | * Encapsulates information needed to connect to a chained proxy. 10 | *

11 | * 12 | *

13 | * Sub-classes may wish to extend {@link ChainedProxyAdapter} for sensible 14 | * defaults. 15 | *

16 | */ 17 | public interface ChainedProxy extends SslEngineSource { 18 | /** 19 | * Return the {@link InetSocketAddress} for connecting to the chained proxy. 20 | * Returning null indicates that we won't chain. 21 | * 22 | * @return The Chain Proxy with Host and Port. 23 | */ 24 | InetSocketAddress getChainedProxyAddress(); 25 | 26 | /** 27 | * (Optional) ensure that the connection is opened from a specific local 28 | * address (useful when doing NAT traversal). 29 | * 30 | * @return 31 | */ 32 | InetSocketAddress getLocalAddress(); 33 | 34 | /** 35 | * Tell LittleProxy what kind of TransportProtocol to use to communicate 36 | * with the chained proxy. 37 | * 38 | * @return 39 | */ 40 | TransportProtocol getTransportProtocol(); 41 | 42 | /** 43 | * Implement this method to tell LittleProxy whether or not to encrypt 44 | * connections to the chained proxy for the given request. If true, 45 | * LittleProxy will call {@link SslEngineSource#newSslEngine()} to obtain an 46 | * SSLContext used by the downstream proxy. 47 | * 48 | * @return true of the connection to the chained proxy should be encrypted 49 | */ 50 | boolean requiresEncryption(); 51 | 52 | /** 53 | * Filters requests on their way to the chained proxy. 54 | * 55 | * @param httpObject 56 | */ 57 | void filterRequest(HttpObject httpObject); 58 | 59 | /** 60 | * Called to let us know that connecting to this proxy succeeded. 61 | */ 62 | void connectionSucceeded(); 63 | 64 | /** 65 | * Called to let us know that connecting to this proxy failed. 66 | * 67 | * @param cause 68 | * exception that caused this failure (may be null) 69 | */ 70 | void connectionFailed(Throwable cause); 71 | 72 | /** 73 | * Called to let us know that we were disconnected. 74 | */ 75 | void disconnected(); 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/impl/HttpPipeliningBlocker.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.impl; 2 | 3 | import io.netty.channel.ChannelDuplexHandler; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.ChannelPromise; 6 | import io.netty.handler.codec.http.FullHttpRequest; 7 | import io.netty.handler.codec.http.FullHttpResponse; 8 | import java.util.LinkedList; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | 13 | public class HttpPipeliningBlocker extends ChannelDuplexHandler { 14 | private static final Logger logger = LoggerFactory.getLogger(HttpPipeliningBlocker.class); 15 | 16 | private final LinkedList requestQueue = new LinkedList<>(); 17 | private boolean requestIsBeingProcessed = false; 18 | 19 | @Override 20 | public void channelRead(ChannelHandlerContext ctx, Object msg) { 21 | if (!(msg instanceof FullHttpRequest)) { 22 | String messageType = msg != null ? msg.getClass().getName() : "null"; 23 | logger.warn("Unexpected message type: {}. Ignoring the message.", messageType); 24 | return; 25 | } 26 | 27 | FullHttpRequest request = (FullHttpRequest) msg; 28 | if (!requestIsBeingProcessed && requestQueue.isEmpty()) { 29 | processRequest(ctx, request); 30 | } else { 31 | requestQueue.addLast(request); 32 | logger.info("One of the previous requests is already being processed. Added current request to the queue. " 33 | + "Queue size: {}", requestQueue.size()); 34 | } 35 | } 36 | 37 | private void processRequest(ChannelHandlerContext ctx, FullHttpRequest request) { 38 | requestIsBeingProcessed = true; 39 | ctx.fireChannelRead(request); 40 | } 41 | 42 | @Override 43 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) { 44 | ctx.write(msg, promise); 45 | if (!(msg instanceof FullHttpResponse)) { 46 | return; 47 | } 48 | requestIsBeingProcessed = false; 49 | if (!requestQueue.isEmpty()) { 50 | ctx.executor().execute(() -> { 51 | if (!requestIsBeingProcessed && !requestQueue.isEmpty()) { 52 | FullHttpRequest request = requestQueue.pollFirst(); 53 | logger.info("Polled next request from the queue for processing. Queue size: {}", requestQueue.size()); 54 | processRequest(ctx, request); 55 | } 56 | }); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/impl/ConnectionState.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.impl; 2 | 3 | enum ConnectionState { 4 | /** 5 | * Connection attempting to connect. 6 | */ 7 | CONNECTING(true), 8 | 9 | /** 10 | * In the middle of doing an SSL handshake. 11 | */ 12 | HANDSHAKING(true), 13 | 14 | /** 15 | * In the process of negotiating an HTTP CONNECT from the client. 16 | */ 17 | NEGOTIATING_CONNECT(true), 18 | 19 | /** 20 | * When forwarding a CONNECT to a chained proxy, we await the CONNECTION_OK 21 | * message from the proxy. 22 | */ 23 | AWAITING_CONNECT_OK(true), 24 | 25 | /** 26 | * Connected but waiting for proxy authentication. 27 | */ 28 | AWAITING_PROXY_AUTHENTICATION, 29 | 30 | /** 31 | * Connected and awaiting initial message (e.g. HttpRequest or 32 | * HttpResponse). 33 | */ 34 | AWAITING_INITIAL, 35 | 36 | /** 37 | * Connected and awaiting HttpContent chunk. 38 | */ 39 | AWAITING_CHUNK, 40 | 41 | /** 42 | * We've asked the client to disconnect, but it hasn't yet. 43 | */ 44 | DISCONNECT_REQUESTED(), 45 | 46 | /** 47 | * Disconnected 48 | */ 49 | DISCONNECTED(); 50 | 51 | private final boolean partOfConnectionFlow; 52 | 53 | ConnectionState(boolean partOfConnectionFlow) { 54 | this.partOfConnectionFlow = partOfConnectionFlow; 55 | } 56 | 57 | ConnectionState() { 58 | this(false); 59 | } 60 | 61 | /** 62 | * Indicates whether this ConnectionState corresponds to a step in a 63 | * {@link ConnectionFlow}. This is useful to distinguish so that we know 64 | * whether or not we're in the process of establishing a connection. 65 | * 66 | * @return true if part of connection flow, otherwise false 67 | */ 68 | public boolean isPartOfConnectionFlow() { 69 | return partOfConnectionFlow; 70 | } 71 | 72 | /** 73 | * Indicates whether this ConnectionState is no longer waiting for messages and is either in the process of disconnecting 74 | * or is already disconnected. 75 | * 76 | * @return true if the connection state is {@link #DISCONNECT_REQUESTED} or {@link #DISCONNECTED}, otherwise false 77 | */ 78 | public boolean isDisconnectingOrDisconnected() { 79 | return this == DISCONNECT_REQUESTED || this == DISCONNECTED; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/HttpProxyServer.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import java.net.InetSocketAddress; 4 | 5 | /** 6 | * Interface for the top-level proxy server class. 7 | */ 8 | public interface HttpProxyServer { 9 | 10 | int getIdleConnectionTimeout(); 11 | 12 | void setIdleConnectionTimeout(int idleConnectionTimeout); 13 | 14 | /** 15 | * Returns the maximum time to wait, in milliseconds, to connect to a server. 16 | */ 17 | int getConnectTimeout(); 18 | 19 | /** 20 | * Returns the ssl handshake timeout in milliseconds. 21 | */ 22 | int getSslHandshakeTimeout(); 23 | 24 | /** 25 | * Sets the maximum time to wait, in milliseconds, to connect to a server. 26 | */ 27 | void setConnectTimeout(int connectTimeoutMs); 28 | 29 | /** 30 | *

31 | * Clone the existing server, with a port 1 higher and everything else the 32 | * same. If the proxy was started with port 0 (JVM-assigned port), the cloned proxy will also use a JVM-assigned 33 | * port. 34 | *

35 | * 36 | *

37 | * The new server will share event loops with the original server. The event 38 | * loops will use whatever name was given to the first server in the clone 39 | * group. The server group will not terminate until the original server and all clones terminate. 40 | *

41 | * 42 | * @return a bootstrap that allows customizing and starting the cloned 43 | * server 44 | */ 45 | HttpProxyServerBootstrap clone(); 46 | 47 | /** 48 | * Stops the server and all related clones. Waits for traffic to stop before shutting down. 49 | */ 50 | void stop(); 51 | 52 | /** 53 | * Stops the server and all related clones immediately, without waiting for traffic to stop. 54 | */ 55 | void abort(); 56 | 57 | /** 58 | * Return the address on which this proxy is listening. 59 | * 60 | * @return 61 | */ 62 | InetSocketAddress getListenAddress(); 63 | 64 | /** 65 | *

66 | * Set the read/write throttle bandwidths (in bytes/second) for this proxy. 67 | *

68 | * @param readThrottleBytesPerSecond 69 | * @param writeThrottleBytesPerSecond 70 | */ 71 | void setThrottle(long readThrottleBytesPerSecond, long writeThrottleBytesPerSecond); 72 | } 73 | -------------------------------------------------------------------------------- /performance/other_proxies/node-proxy.js: -------------------------------------------------------------------------------- 1 | /* 2 | basic-proxy.js: Basic example of proxying over HTTP 3 | 4 | Copyright (c) 2010 Charlie Robbins, Mikeal Rogers, Fedor Indutny, & Marak Squires. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | */ 26 | 27 | var util = require('util'), 28 | http = require('http'), 29 | httpProxy = require('http-proxy'); 30 | 31 | var welcome = [ 32 | '# # ##### ##### ##### ##### ##### #### # # # #', 33 | '# # # # # # # # # # # # # # # # ', 34 | '###### # # # # ##### # # # # # # ## # ', 35 | '# # # # ##### ##### ##### # # ## # ', 36 | '# # # # # # # # # # # # # ', 37 | '# # # # # # # # #### # # # ' 38 | ].join('\n'); 39 | 40 | // 41 | // Basic Http Proxy Server 42 | // 43 | httpProxy.createServer(9000, 'localhost').listen(8080); 44 | util.puts('http proxy server' + ' started ' + 'on port ' + '8080'); 45 | 46 | //// 47 | //// Target Http Server 48 | //// 49 | //http.createServer(function (req, res) { 50 | // res.writeHead(200, { 'Content-Type': 'text/plain' }); 51 | // res.write('request successfully proxied to: ' + req.url + '\n' + JSON.stringify(req.headers, true, 2)); 52 | // res.end(); 53 | //}).listen(8090); 54 | // 55 | //util.puts('http server ' + 'started ' + 'on port ' + '8090 '); -------------------------------------------------------------------------------- /src/main/java/io/netty/handler/codec/compression/BrotliHttpContentCompressor.java: -------------------------------------------------------------------------------- 1 | package io.netty.handler.codec.compression; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.embedded.EmbeddedChannel; 5 | import io.netty.handler.codec.http.HttpContent; 6 | import io.netty.handler.codec.http.HttpContentEncoder; 7 | import io.netty.handler.codec.http.HttpHeaderNames; 8 | import io.netty.handler.codec.http.HttpMessage; 9 | import io.netty.handler.codec.http.HttpResponse; 10 | import io.netty.util.AsciiString; 11 | import io.netty.util.AttributeKey; 12 | import org.apache.commons.lang3.StringUtils; 13 | 14 | /** 15 | * Compresses an {@link HttpMessage} and an {@link HttpContent} in {@code brotli} encoding while respecting the {@code 16 | * "Accept-Encoding"} header. If there is no matching encoding, no compression is done. For more information on how 17 | * this handler modifies the message, please refer to {@link HttpContentEncoder}. 18 | */ 19 | public class BrotliHttpContentCompressor extends HttpContentEncoder { 20 | 21 | private ChannelHandlerContext ctx; 22 | 23 | public static final AttributeKey CONTENT_COMPRESSION_ATTRIBUTE = AttributeKey.valueOf("contentCompression"); 24 | 25 | public static final AsciiString BR = AsciiString.cached("br"); 26 | 27 | @Override 28 | public void handlerAdded(ChannelHandlerContext ctx) throws Exception { 29 | this.ctx = ctx; 30 | } 31 | 32 | @Override 33 | protected Result beginEncode(HttpResponse response, String acceptEncoding) throws Exception { 34 | 35 | String contentEncoding = response.headers().get(HttpHeaderNames.CONTENT_ENCODING); 36 | if (contentEncoding != null) { 37 | // Content-Encoding was set, either as something specific or as the IDENTITY encoding 38 | // Therefore, we should NOT encode here 39 | return null; 40 | } 41 | 42 | if (acceptEncoding.contains(BR) && brotliCompressionEnabled()) { 43 | return new Result( 44 | acceptEncoding, 45 | new EmbeddedChannel(ctx.channel().id(), ctx.channel().metadata().hasDisconnect(), 46 | ctx.channel().config(), new BrotliEncoder())); 47 | } 48 | // 'identity' or unsupported 49 | return null; 50 | } 51 | 52 | private boolean brotliCompressionEnabled() { 53 | String compressionAttribute = ctx.channel().attr(CONTENT_COMPRESSION_ATTRIBUTE).get(); 54 | if (StringUtils.isEmpty(compressionAttribute)) { 55 | return false; 56 | } 57 | return compressionAttribute.contains(BR.toString()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/ChainedProxyWithFallbackTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import io.netty.handler.codec.http.HttpRequest; 4 | 5 | import java.net.InetAddress; 6 | import java.net.InetSocketAddress; 7 | import java.net.UnknownHostException; 8 | import java.util.Queue; 9 | import java.util.concurrent.atomic.AtomicBoolean; 10 | 11 | import org.junit.Assert; 12 | 13 | /** 14 | * Tests a proxy chained to a missing downstream proxy. When the downstream 15 | * proxy is unavailable, the downstream proxy should just fall back to a direct 16 | * connection. 17 | */ 18 | public class ChainedProxyWithFallbackTest extends BaseProxyTest { 19 | private AtomicBoolean unableToConnect = new AtomicBoolean(false); 20 | 21 | @Override 22 | protected void setUp() { 23 | unableToConnect.set(false); 24 | this.proxyServer = bootstrapProxy() 25 | .withName("Downstream") 26 | .withPort(0) 27 | .withChainProxyManager(new ChainedProxyManager() { 28 | @Override 29 | public void lookupChainedProxies(HttpRequest httpRequest, 30 | Queue chainedProxies) { 31 | chainedProxies.add(new ChainedProxyAdapter() { 32 | @Override 33 | public InetSocketAddress getChainedProxyAddress() { 34 | try { 35 | // using unconnectable port 0 36 | return new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0); 37 | } catch (UnknownHostException uhe) { 38 | throw new RuntimeException( 39 | "Unable to resolve 127.0.0.1?!"); 40 | } 41 | } 42 | 43 | @Override 44 | public void connectionFailed(Throwable cause) { 45 | unableToConnect.set(true); 46 | } 47 | 48 | }); 49 | 50 | chainedProxies 51 | .add(ChainedProxyAdapter.FALLBACK_TO_DIRECT_CONNECTION); 52 | } 53 | }) 54 | .start(); 55 | } 56 | 57 | @Override 58 | protected void tearDown() throws Exception { 59 | super.tearDown(); 60 | Assert.assertTrue( 61 | "We should have been told that we were unable to connect", 62 | unableToConnect.get()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/io/netty/handler/codec/compression/GeneralHttpContentDecompressorTest.java: -------------------------------------------------------------------------------- 1 | package io.netty.handler.codec.compression; 2 | 3 | import com.tngtech.java.junit.dataprovider.DataProvider; 4 | import com.tngtech.java.junit.dataprovider.DataProviderRunner; 5 | import com.tngtech.java.junit.dataprovider.UseDataProvider; 6 | import io.netty.channel.Channel; 7 | import io.netty.channel.ChannelConfig; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import io.netty.channel.ChannelMetadata; 10 | import io.netty.channel.embedded.EmbeddedChannel; 11 | import org.hamcrest.Matcher; 12 | import org.junit.Before; 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | 16 | import static org.hamcrest.CoreMatchers.notNullValue; 17 | import static org.hamcrest.CoreMatchers.nullValue; 18 | import static org.junit.Assert.*; 19 | import static org.mockito.Mockito.mock; 20 | import static org.mockito.Mockito.when; 21 | 22 | @RunWith(DataProviderRunner.class) 23 | public class GeneralHttpContentDecompressorTest { 24 | 25 | private GeneralHttpContentDecompressor decompressor; 26 | 27 | @Before 28 | public void setUp() throws Exception { 29 | decompressor = new GeneralHttpContentDecompressor(); 30 | ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); 31 | Channel channel = mock(Channel.class); 32 | when(channel.metadata()).thenReturn(new ChannelMetadata(false)); 33 | ChannelConfig channelConfig = mock(ChannelConfig.class); 34 | when(channel.config()).thenReturn(channelConfig); 35 | when(ctx.channel()).thenReturn(channel); 36 | decompressor.handlerAdded(ctx); 37 | } 38 | 39 | 40 | @DataProvider 41 | public static Object[][] validCases() { 42 | return new Object[][]{ 43 | {"br", notNullValue(), nullValue()}, 44 | {"bR", notNullValue(), nullValue()}, 45 | {"BR", notNullValue(), nullValue()}, 46 | {"gzip", nullValue(), notNullValue()}, 47 | {"x-gzip", nullValue(), notNullValue()}, 48 | {"x-deflate", nullValue(), notNullValue()}, 49 | {"deflate", nullValue(), notNullValue()}, 50 | }; 51 | } 52 | 53 | @Test 54 | @UseDataProvider("validCases") 55 | public void testNewContentDecoder(String encoding, Matcher brotli, Matcher gzip) throws Exception { 56 | EmbeddedChannel embeddedChannel = decompressor.newContentDecoder(encoding); 57 | assertThat(embeddedChannel.pipeline().get(BrotliDecoder.class), brotli); 58 | assertThat(embeddedChannel.pipeline().get(JdkZlibDecoder.class), gzip); 59 | } 60 | 61 | @DataProvider 62 | public static Object[][] invalidCases() { 63 | return new Object[][]{ 64 | {"ddd", nullValue()}, 65 | {"br, gzip", nullValue()}, 66 | {"deflate,gzip", nullValue()}, 67 | }; 68 | } 69 | 70 | @Test 71 | @UseDataProvider("invalidCases") 72 | public void testNewContentDecoderInvalid(String encoding, Matcher nullValue) throws Exception { 73 | EmbeddedChannel embeddedChannel = decompressor.newContentDecoder(encoding); 74 | assertThat(embeddedChannel, nullValue); 75 | } 76 | 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/ProxyHeadersTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import org.apache.http.Header; 4 | import org.apache.http.HttpResponse; 5 | import org.apache.http.client.HttpClient; 6 | import org.apache.http.client.methods.HttpGet; 7 | import org.apache.http.util.EntityUtils; 8 | import org.junit.After; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.littleshoot.proxy.impl.DefaultHttpProxyServer; 12 | import org.mockserver.integration.ClientAndServer; 13 | import org.mockserver.matchers.Times; 14 | import org.mockserver.model.ConnectionOptions; 15 | 16 | import static org.hamcrest.Matchers.emptyArray; 17 | import static org.junit.Assert.assertThat; 18 | import static org.mockserver.model.HttpRequest.request; 19 | import static org.mockserver.model.HttpResponse.response; 20 | 21 | /** 22 | * Tests the proxy's handling and manipulation of headers. 23 | */ 24 | public class ProxyHeadersTest { 25 | private HttpProxyServer proxyServer; 26 | 27 | private ClientAndServer mockServer; 28 | private int mockServerPort; 29 | 30 | @Before 31 | public void setUp() throws Exception { 32 | mockServer = new ClientAndServer(0); 33 | mockServerPort = mockServer.getPort(); 34 | } 35 | 36 | @After 37 | public void tearDown() throws Exception { 38 | try { 39 | if (proxyServer != null) { 40 | proxyServer.abort(); 41 | } 42 | } finally { 43 | if (mockServer != null) { 44 | mockServer.stop(); 45 | } 46 | } 47 | } 48 | 49 | @Test 50 | public void testProxyRemovesConnectionHeadersFromServer() throws Exception { 51 | // the proxy should remove all Connection headers, since all values in the Connection header are hop-by-hop headers. 52 | mockServer.when(request() 53 | .withMethod("GET") 54 | .withPath("/connectionheaders"), 55 | Times.exactly(1)) 56 | .respond(response() 57 | .withStatusCode(200) 58 | .withBody("success") 59 | .withHeader("Connection", "Dummy-Header") 60 | .withHeader("Dummy-Header", "dummy-value") 61 | .withConnectionOptions(new ConnectionOptions() 62 | .withSuppressConnectionHeader(true)) 63 | ); 64 | 65 | this.proxyServer = DefaultHttpProxyServer.bootstrap() 66 | .withPort(0) 67 | .start(); 68 | 69 | HttpClient httpClient = TestUtils.createProxiedHttpClient(proxyServer.getListenAddress().getPort()); 70 | HttpResponse response = httpClient.execute(new HttpGet("http://localhost:" + mockServerPort + "/connectionheaders")); 71 | EntityUtils.consume(response.getEntity()); 72 | 73 | Header[] dummyHeaders = response.getHeaders("Dummy-Header"); 74 | assertThat("Expected proxy to remove the Dummy-Header specified in the Connection header", dummyHeaders, emptyArray()); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/HttpFiltersAdapter.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.handler.codec.http.HttpObject; 5 | import io.netty.handler.codec.http.HttpRequest; 6 | import io.netty.handler.codec.http.HttpResponse; 7 | 8 | import java.net.InetSocketAddress; 9 | 10 | /** 11 | * Convenience base class for implementations of {@link HttpFilters}. 12 | */ 13 | public class HttpFiltersAdapter implements HttpFilters { 14 | /** 15 | * A default, stateless, no-op {@link HttpFilters} instance. 16 | */ 17 | public static final HttpFiltersAdapter NOOP_FILTER = new HttpFiltersAdapter(null); 18 | 19 | protected final HttpRequest originalRequest; 20 | protected final ChannelHandlerContext ctx; 21 | 22 | public HttpFiltersAdapter(HttpRequest originalRequest, 23 | ChannelHandlerContext ctx) { 24 | this.originalRequest = originalRequest; 25 | this.ctx = ctx; 26 | } 27 | 28 | public HttpFiltersAdapter(HttpRequest originalRequest) { 29 | this(originalRequest, null); 30 | } 31 | 32 | @Override 33 | public HttpResponse clientToProxyRequest(HttpObject httpObject) { 34 | return null; 35 | } 36 | 37 | @Override 38 | public HttpResponse proxyToServerRequest(HttpObject httpObject) { 39 | return null; 40 | } 41 | 42 | @Override 43 | public void proxyToServerRequestSending() { 44 | } 45 | 46 | @Override 47 | public void proxyToServerRequestSent() { 48 | } 49 | 50 | @Override 51 | public HttpObject serverToProxyResponse(HttpObject httpObject) { 52 | return httpObject; 53 | } 54 | 55 | @Override 56 | public void serverToProxyResponseTimedOut() { 57 | } 58 | 59 | @Override 60 | public void serverToProxyResponseReceiving() { 61 | } 62 | 63 | @Override 64 | public void serverToProxyResponseReceived() { 65 | } 66 | 67 | @Override 68 | public HttpObject proxyToClientResponse(HttpObject httpObject) { 69 | return httpObject; 70 | } 71 | 72 | @Override 73 | public void proxyToServerConnectionQueued() { 74 | } 75 | 76 | @Override 77 | public InetSocketAddress proxyToServerResolutionStarted( 78 | String resolvingServerHostAndPort) { 79 | return null; 80 | } 81 | 82 | @Override 83 | public void proxyToServerResolutionFailed(String hostAndPort) { 84 | } 85 | 86 | @Override 87 | public void proxyToServerResolutionSucceeded(String serverHostAndPort, 88 | InetSocketAddress resolvedRemoteAddress) { 89 | } 90 | 91 | @Override 92 | public void proxyToServerConnectionStarted() { 93 | } 94 | 95 | @Override 96 | public void proxyToServerConnectionSSLHandshakeStarted() { 97 | } 98 | 99 | @Override 100 | public void proxyToServerConnectionFailed() { 101 | } 102 | 103 | @Override 104 | public void proxyToServerConnectionSucceeded(ChannelHandlerContext serverCtx) { 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/ratelimiter/RateLimitTestBase.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.ratelimiter; 2 | 3 | import org.junit.Ignore; 4 | import org.littleshoot.proxy.UsernamePasswordAuthenticatingProxyTest; 5 | import org.littleshoot.proxy.authenticator.BasicCredentials; 6 | import org.littleshoot.proxy.impl.ProxyUtils; 7 | import org.littleshoot.proxy.ratelimit.RateLimiter; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | import io.netty.handler.codec.http.FullHttpResponse; 13 | import io.netty.handler.codec.http.HttpRequest; 14 | import io.netty.handler.codec.http.HttpResponseStatus; 15 | import io.netty.handler.codec.http.HttpVersion; 16 | 17 | @Ignore 18 | public class RateLimitTestBase extends UsernamePasswordAuthenticatingProxyTest { 19 | 20 | static final int AUTHENTICATION_LIMIT = 10; 21 | static final int AUTHENTICATION_FAILURE_LIMIT = 20; 22 | 23 | @Override 24 | protected void setUp() { 25 | this.proxyServer = bootstrapProxy() 26 | .withPort(0) 27 | .withProxyAuthenticator(new TestBasicProxyAuthenticator(USERNAME, PASSWORD)) 28 | .withRateLimiter(new BaseRateLimiter(AUTHENTICATION_LIMIT, 100)) 29 | .start(); 30 | } 31 | 32 | static class BaseRateLimiter implements RateLimiter { 33 | 34 | private Map attemptsPerUser = new HashMap<>(); 35 | private Map failureAttemptsPerUser = new HashMap<>(); 36 | private int authenticationLimit; 37 | private int authenticationFailureLimit; 38 | 39 | public BaseRateLimiter(int authenticationLimit, int authenticationFailureLimit) { 40 | this.authenticationLimit = authenticationLimit; 41 | this.authenticationFailureLimit = authenticationFailureLimit; 42 | } 43 | 44 | @Override 45 | public boolean isOverLimit(HttpRequest httpRequest) { 46 | return false; 47 | } 48 | 49 | @Override 50 | public boolean isAuthenticationOverLimit(HttpRequest request) { 51 | BasicCredentials credentials = ProxyUtils.getBasicCredentials(request); 52 | 53 | if (credentials == null) { 54 | return false; 55 | } 56 | 57 | attemptsPerUser.put(credentials.getUsername(), 58 | attemptsPerUser.containsKey(credentials.getUsername()) ? attemptsPerUser.get(credentials.getUsername()) + 1 : 1); 59 | return attemptsPerUser.get(credentials.getUsername()) >= authenticationLimit; 60 | } 61 | 62 | @Override 63 | public boolean isAuthenticationFailureOverLimit(HttpRequest request) { 64 | BasicCredentials credentials = ProxyUtils.getBasicCredentials(request); 65 | 66 | if (credentials == null) { 67 | return false; 68 | } 69 | 70 | failureAttemptsPerUser.put(credentials.getUsername(), 71 | failureAttemptsPerUser.containsKey(credentials.getUsername()) ? failureAttemptsPerUser.get(credentials.getUsername()) + 1 : 1); 72 | return failureAttemptsPerUser.get(credentials.getUsername()) >= authenticationFailureLimit; 73 | } 74 | 75 | @Override 76 | public FullHttpResponse limitReachedResponse(HttpRequest request) { 77 | return ProxyUtils.createFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.TOO_MANY_REQUESTS, "429 Too Many Requests"); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Yuml_Class_Diagram.md: -------------------------------------------------------------------------------- 1 | // This code generates a UML diagram on yuml.me. The diagram's purpose is mostly to understand dependencies 2 | // in order to facilitate the upgrade from Netty 3.x to 4.x. It is neither a precise, complete nor accurate representation 3 | // of the object model. 4 | // Green background - Netty classes that appear to be carrying over from 3.x to 4.x 5 | // Red background - Netty classes that were lost in 4.x 6 | // Orange background - Netty classes introduced in 4.x that replace a class from 3.x 7 | 8 | 9 | 10 | 11 | [<>]^-.-[DefaultHttpProxyServer] 12 | [DefaultHttpProxyServer]creates ->[<>] 13 | [<>]^-.-[DefaultProxyAuthorizationManager] 14 | [DefaultHttpProxyServer]creates ->[<>] 15 | [DefaultHttpProxyServer]creates ->[<>] 16 | [<>]^-.-[SslHandshakeHandlerFactory] 17 | [<>]^-.-[SelfSignedSslHandshakeHandlerFactory] 18 | [DefaultHttpProxyServer]creates ->[<>] 19 | [<>]^-.-[PublicIpsOnlyRequestFilter] 20 | [<>]^-.-[RegexHttpRequestFilter] 21 | [<>]^-.-[ProxyUtils.PASS_THROUGH_REQUEST_FILTER] 22 | [DefaultHttpProxyServer]creates ->[<>] 23 | [DefaultHttpProxyServer]creates ->[<>] 24 | [<>]^-.-[DefaultProxyCacheManager] 25 | [<>]^-.-[SimpleProxyCacheManager] 26 | [<>]^-.-[ProxyUtils.Noop Cache Manager] 27 | [DefaultHttpProxyServer]creates ->[<>{bg:green}] 28 | [<>]^-.-[DefaultChannelGroup] 29 | [DefaultHttpProxyServer]creates ->[<>{bg:green}] 30 | [<>]^-.-[HashedWheelTimer{bg:green}] 31 | [DefaultHttpProxyServer]creates ->[<>{bg:red}] 32 | [<>]^-.-[NioServerSocketChannelFactory{bg:red}] 33 | [DefaultHttpProxyServer]creates ->[<>{bg:red}] 34 | [<>]^-.-[NioClientSocketChannelFactory{bg:red}] 35 | [<>{bg:green}]^-.-[<>] 36 | [<>{bg:green}]^-.-[<>] 37 | [DefaultHttpProxyServer]creates ->[ServerBootstrap{bg:green}] 38 | [ServerBootstrap]->[<>] 39 | [<>]superseded by ->[<>{bg:orange}] 40 | [<>]superseded by ->[<>{bg:orange}] 41 | [ServerBootstrap]->[<>{bg:red}] 42 | [<>]^-.-[HttpServerPipelineFactory] 43 | [<>]superseded by ->[<>{bg:orange}] 44 | // Next section - HttpServerPipelineFactory 45 | [HttpServerPipelineFactory]->[<>{bg:orange}] 46 | [HttpServerPipelineFactory]->[<>] 47 | [HttpServerPipelineFactory]->[<>] 48 | [HttpServerPipelineFactory]->[<>] 49 | [HttpServerPipelineFactory]->[<>] 50 | [<>]^-.-[DefaultRelayPipelineFactory] 51 | [<[<>] 52 | [HttpServerPipelineFactory]->[<>] 53 | [HttpServerPipelineFactory]->[<>{bg:orange}] -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/DefaultFailureHttpResponseComposerTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy; 2 | 3 | import io.netty.handler.codec.http.FullHttpResponse; 4 | import io.netty.handler.codec.http.HttpMethod; 5 | import io.netty.handler.codec.http.HttpRequest; 6 | import io.netty.handler.codec.http.HttpResponseStatus; 7 | import org.junit.Test; 8 | 9 | import java.io.IOException; 10 | 11 | import static org.junit.Assert.*; 12 | import static org.mockito.Mockito.*; 13 | 14 | public class DefaultFailureHttpResponseComposerTest { 15 | 16 | private static final String REQUEST_URI = "https://localhost/hi"; 17 | 18 | @Test 19 | public void testDefault() throws IOException { 20 | FailureHttpResponseComposer badGatewayResponseComposer = new DefaultFailureHttpResponseComposer(); 21 | 22 | HttpRequest initialRequest = mock(HttpRequest.class); 23 | when(initialRequest.getUri()).thenReturn(REQUEST_URI); 24 | 25 | FullHttpResponse response = badGatewayResponseComposer.compose(initialRequest, new RuntimeException()); 26 | 27 | assertEquals(502, response.getStatus().code()); 28 | assertEquals("Bad Gateway", response.getStatus().reasonPhrase()); 29 | assertEquals("Bad Gateway: " + REQUEST_URI, new String(response.content().array())); 30 | } 31 | 32 | @Test 33 | public void testCustomMessageAndStatus() throws IOException { 34 | FailureHttpResponseComposer badGatewayResponseComposer = new DefaultFailureHttpResponseComposer() { 35 | @Override 36 | protected String provideCustomMessage(HttpRequest httpRequest, Throwable cause) { 37 | return "Invalid certificate: " + httpRequest.getUri(); 38 | } 39 | 40 | @Override 41 | protected HttpResponseStatus provideCustomStatus(HttpRequest httpRequest, Throwable cause) { 42 | return new HttpResponseStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), "Something is wrong"); 43 | } 44 | }; 45 | 46 | HttpRequest initialRequest = mock(HttpRequest.class); 47 | when(initialRequest.getUri()).thenReturn(REQUEST_URI); 48 | 49 | FullHttpResponse response = badGatewayResponseComposer.compose(initialRequest, new RuntimeException()); 50 | 51 | assertEquals(500, response.getStatus().code()); 52 | assertEquals("Something is wrong", response.getStatus().reasonPhrase()); 53 | assertEquals("Invalid certificate: " + REQUEST_URI, new String(response.content().array())); 54 | } 55 | 56 | @Test 57 | public void testClearedContent() throws IOException { 58 | FailureHttpResponseComposer badGatewayResponseComposer = new DefaultFailureHttpResponseComposer(); 59 | 60 | HttpRequest initialRequest = mock(HttpRequest.class); 61 | when(initialRequest.getUri()).thenReturn(REQUEST_URI); 62 | 63 | FullHttpResponse response = badGatewayResponseComposer.compose(initialRequest, new RuntimeException()); 64 | 65 | assertEquals(502, response.getStatus().code()); 66 | 67 | assertEquals(0, response.content().readerIndex()); 68 | assertNotEquals(0, response.content().writerIndex()); 69 | 70 | when(initialRequest.getMethod()).thenReturn(HttpMethod.HEAD); 71 | 72 | response = badGatewayResponseComposer.compose(initialRequest, new RuntimeException()); 73 | 74 | assertEquals(502, response.getStatus().code()); 75 | 76 | assertEquals(0, response.content().readerIndex()); 77 | assertEquals(0, response.content().writerIndex()); 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /src/main/java/io/netty/handler/codec/compression/BrotliDecoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.handler.codec.compression; 17 | 18 | import com.nixxcode.jvmbrotli.common.BrotliLoader; 19 | import com.nixxcode.jvmbrotli.dec.BrotliInputStream; 20 | import io.netty.buffer.ByteBuf; 21 | import io.netty.buffer.ByteBufInputStream; 22 | import io.netty.buffer.ByteBufOutputStream; 23 | import io.netty.channel.ChannelHandlerContext; 24 | import io.netty.handler.codec.ByteToMessageDecoder; 25 | import java.io.IOException; 26 | import java.io.InputStream; 27 | import java.io.OutputStream; 28 | import java.util.List; 29 | import org.slf4j.Logger; 30 | import org.slf4j.LoggerFactory; 31 | 32 | /** 33 | * Decompress a {@link ByteBuf} using the inflate algorithm. 34 | */ 35 | public class BrotliDecoder extends ByteToMessageDecoder { 36 | 37 | private static final Logger log = LoggerFactory.getLogger(BrotliDecoder.class); 38 | 39 | private static final int EOF = -1; 40 | 41 | public BrotliDecoder() { 42 | // https://github.com/nixxcode/jvm-brotli#loading-jvm-brotli 43 | if (!BrotliLoader.isBrotliAvailable()) { 44 | throw new DecompressionException("Brotli decoding is not supported"); 45 | } 46 | } 47 | 48 | @Override 49 | protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { 50 | ByteBuf result = in.alloc().buffer(); 51 | boolean success = true; 52 | try ( 53 | ByteBufOutputStream bbout = new ByteBufOutputStream(result); 54 | ByteBufInputStream bbin = new ByteBufInputStream(in); 55 | ) { 56 | decompress(bbin, bbout); 57 | if (!result.isReadable()) { 58 | success = false; 59 | log.warn("Decoded result is not readable"); 60 | return; 61 | } 62 | out.add(result); 63 | } catch (IOException ioe) { 64 | // stream is corrupted or not ready (retriable) 65 | // exit condition will be reached when Netty has nothing more to add to the buffer 66 | // and this function is not able to decode any messages to "out" 67 | log.warn("Brotli stream was not ready for consumption", ioe); 68 | success = false; 69 | } catch (Exception e) { 70 | success = false; 71 | throw e; 72 | } finally { 73 | if (!success) { 74 | in.resetReaderIndex(); 75 | result.release(); 76 | } 77 | } 78 | } 79 | 80 | public static void decompress(InputStream in, OutputStream out) throws IOException { 81 | try (BrotliInputStream brotliInputStream = new BrotliInputStream(in)) { 82 | int bytesRead; 83 | byte[] decompressBuffer = new byte[8192]; 84 | while ((bytesRead = brotliInputStream.read(decompressBuffer)) != EOF) { 85 | out.write(decompressBuffer, 0, bytesRead); 86 | } 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/test/ServerErrorTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.test; 2 | 3 | import org.junit.After; 4 | import org.junit.Test; 5 | import org.littleshoot.proxy.HttpProxyServer; 6 | import org.littleshoot.proxy.impl.DefaultHttpProxyServer; 7 | 8 | import java.io.BufferedReader; 9 | import java.io.IOException; 10 | import java.io.InputStreamReader; 11 | import java.io.PrintWriter; 12 | import java.net.ServerSocket; 13 | import java.net.Socket; 14 | 15 | import static org.junit.Assert.assertEquals; 16 | 17 | public class ServerErrorTest { 18 | private HttpProxyServer proxyServer; 19 | 20 | @Test 21 | public void testInvalidServerResponse() throws IOException { 22 | proxyServer = DefaultHttpProxyServer.bootstrap() 23 | .withPort(0) 24 | .start(); 25 | 26 | // we have to create our own socket here, since any proper http server (jetty, mockserver, etc.) won't allow us to 27 | // send invalid responses. 28 | try (ServerSocket socket = createServerWithBadResponse()) { 29 | org.apache.http.HttpResponse response = HttpClientUtil.performHttpGet("http://localhost:" + socket.getLocalPort(), proxyServer); 30 | 31 | assertEquals("Expected to receive a 502 Bad Gateway after server responded with invalid response", 502, response.getStatusLine().getStatusCode()); 32 | } 33 | } 34 | 35 | @After 36 | public void tearDown() { 37 | if (proxyServer != null) { 38 | proxyServer.abort(); 39 | } 40 | } 41 | 42 | /** 43 | * Creates a ServerSocket that will read an HTTP request and response with an invalid response. 44 | * NOTE: the ServerSocket must be closed after the response is consumed. 45 | */ 46 | private static ServerSocket createServerWithBadResponse() { 47 | final ServerSocket serverSocket; 48 | try { 49 | serverSocket = new ServerSocket(0); 50 | } catch (IOException e) { 51 | throw new RuntimeException(e); 52 | } 53 | 54 | Runnable server = new Runnable() { 55 | @Override 56 | public void run() { 57 | try { 58 | Socket socket = serverSocket.accept(); 59 | 60 | try (PrintWriter out = new PrintWriter(socket.getOutputStream(), true); 61 | BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) { 62 | while (!in.readLine().isEmpty()) { 63 | // read the request up to the double-CRLF 64 | } 65 | 66 | // write a a response with an invalid HTTP version 67 | out.write("HTTP/1.12312312312312411231231231 200 OK\r\n" + 68 | "Connection: close\r\n" + 69 | "Content-Length: 0\r\n" + 70 | "\r\n"); 71 | out.flush(); 72 | } 73 | } catch (IOException e) { 74 | throw new RuntimeException(e); 75 | } 76 | } 77 | }; 78 | 79 | // start the server in a separate thread 80 | Thread serverThread = new Thread(server); 81 | serverThread.setDaemon(true); 82 | serverThread.start(); 83 | 84 | // wait for the server to start 85 | try { 86 | Thread.sleep(500); 87 | } catch (InterruptedException e) { 88 | throw new RuntimeException(e); 89 | } 90 | 91 | return serverSocket; 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/impl/ConnectionFlowStep.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.impl; 2 | 3 | import io.netty.util.concurrent.Future; 4 | 5 | /** 6 | * Represents a phase in a {@link ConnectionFlow}. 7 | */ 8 | abstract class ConnectionFlowStep { 9 | private final ProxyConnectionLogger LOG; 10 | private final ProxyConnection connection; 11 | private final ConnectionState state; 12 | 13 | /** 14 | * Construct a new step in a connection flow. 15 | * 16 | * @param connection 17 | * the connection that we're working on 18 | * @param state 19 | * the state that the connection will show while we're processing 20 | * this step 21 | */ 22 | ConnectionFlowStep(ProxyConnection connection, 23 | ConnectionState state) { 24 | super(); 25 | this.connection = connection; 26 | this.state = state; 27 | this.LOG = connection.getLOG(); 28 | } 29 | 30 | ProxyConnection getConnection() { 31 | return connection; 32 | } 33 | 34 | ConnectionState getState() { 35 | return state; 36 | } 37 | 38 | /** 39 | * Indicates whether or not to suppress the initial request. Defaults to 40 | * false, can be overridden. 41 | * 42 | * @return 43 | */ 44 | boolean shouldSuppressInitialRequest() { 45 | return false; 46 | } 47 | 48 | /** 49 | *

50 | * Indicates whether or not this step should be executed on the channel's 51 | * event loop. Defaults to true, can be overridden. 52 | *

53 | * 54 | *

55 | * If this step modifies the pipeline, for example by adding/removing 56 | * handlers, it's best to make it execute on the event loop. 57 | *

58 | * 59 | * 60 | * @return 61 | */ 62 | boolean shouldExecuteOnEventLoop() { 63 | return true; 64 | } 65 | 66 | /** 67 | * Implement this method to actually do the work involved in this step of 68 | * the flow. 69 | * 70 | * @return 71 | */ 72 | protected abstract Future execute(); 73 | 74 | /** 75 | * When the flow determines that this step was successful, it calls into 76 | * this method. The default implementation simply continues with the flow. 77 | * Other implementations may choose to not continue and instead wait for a 78 | * message or something like that. 79 | * 80 | * @param flow 81 | */ 82 | void onSuccess(ConnectionFlow flow) { 83 | flow.advance(); 84 | } 85 | 86 | /** 87 | *

88 | * Any messages that are read from the underlying connection while we're at 89 | * this step of the connection flow are passed to this method. 90 | *

91 | * 92 | *

93 | * The default implementation ignores the message and logs this, since we 94 | * weren't really expecting a message here. 95 | *

96 | * 97 | *

98 | * Some {@link ConnectionFlowStep}s do need to read the messages, so they 99 | * override this method as appropriate. 100 | *

101 | * 102 | * @param flow 103 | * our {@link ConnectionFlow} 104 | * @param msg 105 | * the message read from the underlying connection 106 | */ 107 | void read(ConnectionFlow flow, Object msg) { 108 | LOG.debug("Received message while in the middle of connecting: {}", msg); 109 | } 110 | 111 | @Override 112 | public String toString() { 113 | return state.toString(); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/io/netty/handler/codec/compression/BrotliEncoder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | package io.netty.handler.codec.compression; 17 | 18 | import com.nixxcode.jvmbrotli.common.BrotliLoader; 19 | import com.nixxcode.jvmbrotli.enc.BrotliOutputStream; 20 | import com.nixxcode.jvmbrotli.enc.Encoder; 21 | 22 | import io.netty.buffer.ByteBuf; 23 | import io.netty.buffer.ByteBufOutputStream; 24 | import io.netty.buffer.ByteBufUtil; 25 | import io.netty.channel.ChannelHandlerContext; 26 | import io.netty.handler.codec.MessageToByteEncoder; 27 | import org.slf4j.Logger; 28 | import org.slf4j.LoggerFactory; 29 | 30 | 31 | public class BrotliEncoder extends MessageToByteEncoder { 32 | 33 | private static final Logger log = LoggerFactory.getLogger(BrotliEncoder.class); 34 | 35 | private final boolean preferDirect; 36 | private final int compressionQuality; 37 | private final int windowSize; 38 | 39 | /* 40 | If the Brotli encoding is being used to compress streams in real-time, 41 | it is not advisable to have a quality setting above 4 due to performance. 42 | */ 43 | private static final int DEFAULT_COMPRESSION_QUALITY = 4; 44 | 45 | private static final int DEFAULT_WINDOW_SIZE = -1; 46 | 47 | 48 | public BrotliEncoder() { 49 | this(true, DEFAULT_COMPRESSION_QUALITY, DEFAULT_WINDOW_SIZE); 50 | } 51 | 52 | public BrotliEncoder(int quality) { 53 | this(true, quality, DEFAULT_WINDOW_SIZE); 54 | } 55 | 56 | public BrotliEncoder(int quality, int windowSize) { 57 | this(true, quality, windowSize); 58 | } 59 | 60 | public BrotliEncoder(boolean preferDirect) { 61 | this(preferDirect, DEFAULT_COMPRESSION_QUALITY, DEFAULT_WINDOW_SIZE); 62 | } 63 | 64 | public BrotliEncoder(boolean preferDirect, int compressionQuality, int windowSize) { 65 | super(preferDirect); 66 | this.preferDirect = preferDirect; 67 | this.compressionQuality = compressionQuality; 68 | this.windowSize = windowSize; 69 | // https://github.com/nixxcode/jvm-brotli#loading-jvm-brotli 70 | if (!BrotliLoader.isBrotliAvailable()) { 71 | throw new CompressionException("Brotli encoding is not supported"); 72 | } 73 | } 74 | 75 | @Override 76 | protected void encode(ChannelHandlerContext ctx, ByteBuf uncompressed, ByteBuf out) throws Exception { 77 | if (!uncompressed.isReadable() || uncompressed.readableBytes() == 0) { 78 | return; 79 | } 80 | try (ByteBufOutputStream dst = new ByteBufOutputStream(uncompressed.alloc().buffer())) { 81 | Encoder.Parameters params = new Encoder.Parameters().setQuality(this.compressionQuality); 82 | try (BrotliOutputStream brotliOutputStream = new BrotliOutputStream(dst, params)) { 83 | brotliOutputStream 84 | .write(ByteBufUtil.getBytes(uncompressed, uncompressed.readerIndex(), uncompressed.readableBytes(), false)); 85 | brotliOutputStream.flush(); 86 | } 87 | out.writeBytes(dst.buffer()); 88 | } 89 | } 90 | 91 | @Override 92 | public boolean isPreferDirect() { 93 | return preferDirect; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/org/littleshoot/proxy/impl/ProtocolHeadersRequestDecoder.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.impl; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.ChannelInboundHandlerAdapter; 6 | import io.netty.util.AttributeKey; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | public class ProtocolHeadersRequestDecoder extends ChannelInboundHandlerAdapter { 11 | 12 | public static final AttributeKey SOURCE_IP_ATTRIBUTE = AttributeKey.valueOf("sourceIp"); 13 | public static final AttributeKey CONNECTION_ID_ATTRIBUTE = AttributeKey.valueOf("connectionId"); 14 | 15 | // Pattern: 16 | // PROXY_STRING + single space + INET_PROTOCOL + single space + CLIENT_IP + single space + PROXY_IP + single space + CLIENT_PORT + single space + PROXY_PORT + "\r\n" 17 | // Example: 18 | // PROXY TCP4 198.51.100.22 203.0.113.7 35646 80\r\n 19 | // or 20 | // PROXY TCP6 2001:DB8::21f:5bff:febf:ce22:8a2e 2001:DB8::12f:8baa:eafc:ce29:6b2e 35646 80 21 | // Source: 22 | // https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/enable-proxy-protocol.html 23 | private static final Pattern TCP4_PROXY_PROTOCOL_HEADER_PATTERN = 24 | Pattern.compile("^PROXY TCP4 (((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.)?){4}) ((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.)?){4} \\d+ \\d+\\r\\n"); 25 | 26 | private static final Pattern TCP6_PROXY_PROTOCOL_HEADER_PATTERN = 27 | Pattern.compile("^PROXY TCP6 (([0-9a-f]{1,4}:){7}([0-9a-f]){1,4}) ([0-9a-f]{1,4}:){7}([0-9a-f]){1,4} \\d+ \\d+\\r\\n", Pattern.CASE_INSENSITIVE); 28 | 29 | // Pattern: 30 | // TRACE <32HEX>\r\n 31 | // Example: 32 | // TRACE 01234567890abcdef01234567890abcdef\r\n 33 | // Source: 34 | // https://github.com/verygoodsecurity/nginx/pull/1 35 | // https://github.com/opentracing/specification/issues/150 36 | private static final Pattern TRACE_HEADER_PATTERN = Pattern.compile("^TRACE ([0-9a-f]{32})\\r\\n"); 37 | 38 | @Override 39 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 40 | ByteBuf buf = ((ByteBuf) msg); 41 | 42 | String body = bufToString(buf); 43 | 44 | Matcher tcp4Matcher = TCP4_PROXY_PROTOCOL_HEADER_PATTERN.matcher(body); 45 | Matcher tcp6Matcher = TCP6_PROXY_PROTOCOL_HEADER_PATTERN.matcher(body); 46 | 47 | String sourceIp; 48 | 49 | if (tcp4Matcher.find()) { 50 | sourceIp = tcp4Matcher.group(1); 51 | } else if (tcp6Matcher.find()) { 52 | sourceIp = tcp6Matcher.group(1); 53 | } else { 54 | ctx.fireChannelRead(msg); 55 | return; // no proxy protocol header found, proceed 56 | } 57 | 58 | ctx.channel().attr(SOURCE_IP_ATTRIBUTE).set(sourceIp); 59 | 60 | int proxyProtocolHeaderIndex = body.indexOf("\r\n"); 61 | String stripped = body.substring(proxyProtocolHeaderIndex + 2); // +2 for \r\n 62 | 63 | String traceId; 64 | 65 | Matcher traceHeaderMatcher = TRACE_HEADER_PATTERN.matcher(stripped); 66 | if (traceHeaderMatcher.find()) { 67 | traceId = traceHeaderMatcher.group(1); 68 | } else { 69 | buf.clear().writeBytes(stripped.getBytes()); 70 | ctx.fireChannelRead(buf); 71 | return; 72 | } 73 | 74 | ctx.channel().attr(CONNECTION_ID_ATTRIBUTE).set(traceId); 75 | 76 | stripped = stripped.substring(6 + 32 + 2); // TRACE 32HEX\r\n 77 | buf.clear().writeBytes(stripped.getBytes()); 78 | 79 | ctx.fireChannelRead(buf); 80 | } 81 | 82 | private String bufToString(ByteBuf content) { 83 | final byte[] bytes = new byte[content.readableBytes()]; 84 | 85 | content.getBytes(content.readerIndex(), bytes); 86 | 87 | return new String(bytes); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/test/java/org/littleshoot/proxy/impl/HttpPipeliningBlockerTest.java: -------------------------------------------------------------------------------- 1 | package org.littleshoot.proxy.impl; 2 | 3 | import static org.mockito.ArgumentMatchers.any; 4 | import static org.mockito.Mockito.doAnswer; 5 | import static org.mockito.Mockito.mock; 6 | import static org.mockito.Mockito.times; 7 | import static org.mockito.Mockito.verify; 8 | import static org.mockito.Mockito.verifyNoMoreInteractions; 9 | import static org.mockito.Mockito.when; 10 | 11 | import io.netty.channel.ChannelHandlerContext; 12 | import io.netty.channel.ChannelPromise; 13 | import io.netty.handler.codec.http.FullHttpRequest; 14 | import io.netty.handler.codec.http.FullHttpResponse; 15 | import io.netty.util.concurrent.EventExecutor; 16 | import org.junit.Test; 17 | 18 | public class HttpPipeliningBlockerTest { 19 | private final HttpPipeliningBlocker httpPipeliningBlocker = new HttpPipeliningBlocker(); 20 | 21 | private final ChannelHandlerContext ctx = mock(ChannelHandlerContext.class); 22 | private final EventExecutor eventExecutor = mock(EventExecutor.class); 23 | private final ChannelPromise promise = mock(ChannelPromise.class); 24 | private final FullHttpRequest request1 = mock(FullHttpRequest.class, "request1"); 25 | private final FullHttpRequest request2 = mock(FullHttpRequest.class, "request2"); 26 | private final FullHttpResponse response1 = mock(FullHttpResponse.class, "response1"); 27 | 28 | @Test 29 | public void doesntStartProcessingOfTheNextRequestIfThePreviousOneHasntBeenProcessedYet() { 30 | httpPipeliningBlocker.channelRead(ctx, request1); 31 | httpPipeliningBlocker.channelRead(ctx, request2); 32 | 33 | verify(ctx, times(1)).fireChannelRead(request1); 34 | verify(ctx, times(0)).fireChannelRead(request2); 35 | verifyNoMoreInteractions(ctx); 36 | } 37 | 38 | @Test 39 | public void ignoresMessageIfItIsNotFullHttpRequest() { 40 | httpPipeliningBlocker.channelRead(ctx, "a string"); 41 | verify(ctx, times(0)).fireChannelRead(any()); 42 | } 43 | 44 | @Test 45 | public void ignoresMessageIfItIsNull() { 46 | httpPipeliningBlocker.channelRead(ctx, null); 47 | verify(ctx, times(0)).fireChannelRead(any()); 48 | } 49 | 50 | @Test 51 | public void triggersProcessingOfTheNextRequestInTheQueueIfThereIsAny() { 52 | when(ctx.executor()).thenReturn(eventExecutor); 53 | doAnswer(invocation -> { 54 | Runnable task = invocation.getArgument(0); 55 | task.run(); 56 | return null; 57 | }).when(eventExecutor).execute(any(Runnable.class)); 58 | 59 | httpPipeliningBlocker.channelRead(ctx, request1); 60 | httpPipeliningBlocker.channelRead(ctx, request2); 61 | httpPipeliningBlocker.write(ctx, response1, promise); 62 | 63 | verify(ctx, times(1)).executor(); 64 | verify(ctx, times(1)).fireChannelRead(request2); 65 | } 66 | 67 | @Test 68 | public void doesntUseEventExecutorIfQueueIsEmpty() { 69 | httpPipeliningBlocker.channelRead(ctx, request1); 70 | httpPipeliningBlocker.write(ctx, response1, promise); 71 | 72 | verify(ctx, times(0)).executor(); 73 | } 74 | 75 | @Test 76 | public void doesntTriggerProcessingOfTheNextRequestInTheQueueIfResponseMessageIsNotInstanceOfFullHttpResponse() { 77 | httpPipeliningBlocker.channelRead(ctx, request1); 78 | httpPipeliningBlocker.write(ctx, "a string", promise); 79 | 80 | verify(ctx, times(0)).executor(); 81 | } 82 | 83 | @Test 84 | public void doesntAllowProcessingOfTheNextReceivedRequestIfResponseMessageIsNotInstanceOfFullHttpResponse() { 85 | httpPipeliningBlocker.channelRead(ctx, request1); 86 | httpPipeliningBlocker.write(ctx, "a string", promise); 87 | 88 | httpPipeliningBlocker.channelRead(ctx, request2); 89 | verify(ctx, times(0)).fireChannelRead(request2); 90 | } 91 | } --------------------------------------------------------------------------------