├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .project ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── browserup-proxy-core ├── .classpath ├── .project ├── Mitmproxy_Integration_Notes.txt ├── build.gradle └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── browserup │ │ │ ├── bup │ │ │ ├── BrowserUpProxy.java │ │ │ ├── BrowserUpProxyServer.java │ │ │ ├── MitmProxyServer.java │ │ │ ├── assertion │ │ │ │ ├── HarEntryAssertion.java │ │ │ │ ├── ResponseTimeLessThanOrEqualAssertion.java │ │ │ │ ├── error │ │ │ │ │ └── HarEntryAssertionError.java │ │ │ │ ├── field │ │ │ │ │ ├── FieldPassesPredicateAssertion.java │ │ │ │ │ ├── HarEntryAssertionFieldSupplier.java │ │ │ │ │ ├── HarEntryPredicate.java │ │ │ │ │ ├── content │ │ │ │ │ │ ├── ContentContainsStringAssertion.java │ │ │ │ │ │ ├── ContentDoesNotContainStringAssertion.java │ │ │ │ │ │ ├── ContentMatchesAssertion.java │ │ │ │ │ │ ├── ContentPassesPredicateAssertion.java │ │ │ │ │ │ ├── ContentSizeLessThanOrEqualAssertion.java │ │ │ │ │ │ └── ContentSizePassesPredicateAssertion.java │ │ │ │ │ ├── header │ │ │ │ │ │ ├── FilteredHeadersContainStringAssertion.java │ │ │ │ │ │ ├── FilteredHeadersDoNotContainStringAssertion.java │ │ │ │ │ │ ├── FilteredHeadersMatchAssertion.java │ │ │ │ │ │ ├── HeadersContainStringAssertion.java │ │ │ │ │ │ ├── HeadersDoNotContainStringAssertion.java │ │ │ │ │ │ ├── HeadersMatchAssertion.java │ │ │ │ │ │ └── HeadersPassPredicateAssertion.java │ │ │ │ │ └── status │ │ │ │ │ │ ├── StatusBelongsToClassAssertion.java │ │ │ │ │ │ ├── StatusEqualsAssertion.java │ │ │ │ │ │ └── StatusPassesPredicateAssertion.java │ │ │ │ ├── model │ │ │ │ │ ├── AssertionEntryResult.java │ │ │ │ │ ├── AssertionResult.java │ │ │ │ │ └── filter │ │ │ │ │ │ ├── AssertionFilterInfo.java │ │ │ │ │ │ └── AssertionUrlFilterInfo.java │ │ │ │ └── supplier │ │ │ │ │ ├── CurrentStepHarEntriesSupplier.java │ │ │ │ │ ├── HarEntriesSupplier.java │ │ │ │ │ ├── MostRecentHarEntrySupplier.java │ │ │ │ │ ├── MostRecentUrlFilteredHarEntrySupplier.java │ │ │ │ │ └── UrlFilteredHarEntriesSupplier.java │ │ │ ├── client │ │ │ │ └── ClientUtil.java │ │ │ ├── exception │ │ │ │ ├── DecompressionException.java │ │ │ │ └── UnsupportedCharsetException.java │ │ │ ├── filters │ │ │ │ ├── AddHeadersFilter.java │ │ │ │ ├── AllowlistFilter.java │ │ │ │ ├── AutoBasicAuthFilter.java │ │ │ │ ├── BlocklistFilter.java │ │ │ │ ├── BrowserUpHttpFilterChain.java │ │ │ │ ├── ClientRequestCaptureFilter.java │ │ │ │ ├── HarCaptureFilter.java │ │ │ │ ├── HttpConnectHarCaptureFilter.java │ │ │ │ ├── HttpsAwareFiltersAdapter.java │ │ │ │ ├── HttpsHostCaptureFilter.java │ │ │ │ ├── HttpsOriginalHostCaptureFilter.java │ │ │ │ ├── LatencyFilter.java │ │ │ │ ├── ModifiedRequestAwareFilter.java │ │ │ │ ├── RegisterRequestFilter.java │ │ │ │ ├── RequestFilter.java │ │ │ │ ├── RequestFilterAdapter.java │ │ │ │ ├── ResolvedHostnameCacheFilter.java │ │ │ │ ├── ResponseFilter.java │ │ │ │ ├── ResponseFilterAdapter.java │ │ │ │ ├── RewriteUrlFilter.java │ │ │ │ ├── ServerResponseCaptureFilter.java │ │ │ │ ├── UnregisterRequestFilter.java │ │ │ │ ├── support │ │ │ │ │ └── HttpConnectTiming.java │ │ │ │ └── util │ │ │ │ │ └── HarCaptureUtil.java │ │ │ ├── mitmproxy │ │ │ │ ├── MitmProxyProcessManager.java │ │ │ │ ├── NetworkUtils.java │ │ │ │ ├── addons │ │ │ │ │ ├── AbstractAddon.java │ │ │ │ │ ├── AdditionalHeadersAddOn.java │ │ │ │ │ ├── AddonsManagerAddOn.java │ │ │ │ │ ├── AllowListAddOn.java │ │ │ │ │ ├── AuthBasicAddOn.java │ │ │ │ │ ├── BlockListAddOn.java │ │ │ │ │ ├── HarCaptureAddOn.java │ │ │ │ │ ├── HttpConnectCaptureAddOn.java │ │ │ │ │ ├── InitFlowAddOn.java │ │ │ │ │ ├── LatencyAddOn.java │ │ │ │ │ ├── ProxyManagerAddOn.java │ │ │ │ │ └── RewriteUrlAddOn.java │ │ │ │ └── management │ │ │ │ │ ├── AdditionalHeadersManager.java │ │ │ │ │ ├── AddonsManagerClient.java │ │ │ │ │ ├── AllowListManager.java │ │ │ │ │ ├── AuthBasicManager.java │ │ │ │ │ ├── BlockListManager.java │ │ │ │ │ ├── HarCaptureManager.java │ │ │ │ │ ├── LatencyManager.java │ │ │ │ │ ├── ProxyManager.java │ │ │ │ │ └── RewriteUrlManager.java │ │ │ ├── proxy │ │ │ │ ├── ActivityMonitor.java │ │ │ │ ├── Allowlist.java │ │ │ │ ├── BlocklistEntry.java │ │ │ │ ├── CaptureType.java │ │ │ │ ├── RewriteRule.java │ │ │ │ ├── auth │ │ │ │ │ └── AuthType.java │ │ │ │ └── dns │ │ │ │ │ ├── AbstractHostNameRemapper.java │ │ │ │ │ ├── AdvancedHostResolver.java │ │ │ │ │ ├── BasicHostResolver.java │ │ │ │ │ ├── ChainedHostResolver.java │ │ │ │ │ ├── DelegatingHostResolver.java │ │ │ │ │ ├── DnsJavaResolver.java │ │ │ │ │ ├── HostResolver.java │ │ │ │ │ ├── NativeCacheManipulatingResolver.java │ │ │ │ │ └── NativeResolver.java │ │ │ └── util │ │ │ │ ├── BrowserUpHttpUtil.java │ │ │ │ ├── BrowserUpProxyUtil.java │ │ │ │ ├── HttpMessageContents.java │ │ │ │ ├── HttpMessageInfo.java │ │ │ │ ├── HttpObjectUtil.java │ │ │ │ └── HttpStatusClass.java │ │ │ └── harreader │ │ │ ├── HarReader.java │ │ │ ├── HarReaderException.java │ │ │ ├── HarReaderMode.java │ │ │ ├── LICENSE.MD │ │ │ ├── README.md │ │ │ ├── filter │ │ │ ├── HarEntriesFilter.java │ │ │ └── HarEntriesUrlPatternFilter.java │ │ │ ├── jackson │ │ │ ├── DefaultMapperFactory.java │ │ │ ├── ExceptionIgnoringDateDeserializer.java │ │ │ ├── ExceptionIgnoringIntegerDeserializer.java │ │ │ └── MapperFactory.java │ │ │ └── model │ │ │ ├── Har.java │ │ │ ├── HarCache.java │ │ │ ├── HarContent.java │ │ │ ├── HarCookie.java │ │ │ ├── HarCreatorBrowser.java │ │ │ ├── HarEntry.java │ │ │ ├── HarHeader.java │ │ │ ├── HarLog.java │ │ │ ├── HarPage.java │ │ │ ├── HarPageTiming.java │ │ │ ├── HarPostData.java │ │ │ ├── HarPostDataParam.java │ │ │ ├── HarQueryParam.java │ │ │ ├── HarRequest.java │ │ │ ├── HarResponse.java │ │ │ ├── HarTiming.java │ │ │ ├── HttpMethod.java │ │ │ └── HttpStatus.java │ └── resources │ │ ├── com │ │ └── browserup │ │ │ └── bup │ │ │ └── version │ │ ├── mitmproxy │ │ ├── additional_headers.py │ │ ├── allow_list.py │ │ ├── auth_basic.py │ │ ├── block_list.py │ │ ├── bu_addons_manager.py │ │ ├── har_dump.py │ │ ├── http_connect_capture.py │ │ ├── init_flow.py │ │ ├── latency.py │ │ ├── proxy_manager.py │ │ └── rewrite_url.py │ │ └── sslSupport │ │ ├── ca-certificate-ec.cer │ │ ├── ca-certificate-rsa.cer │ │ ├── ca-keystore-ec.p12 │ │ └── ca-keystore-rsa.p12 │ └── test │ ├── groovy │ └── com │ │ └── browserup │ │ └── bup │ │ ├── MitmProxyProcessManagerTest.groovy │ │ ├── assertion │ │ ├── ResponseTimeLessThanOrEqualAssertionTest.groovy │ │ └── supplier │ │ │ ├── MostRecentUrlFilteredHarEntrySupplierTest.groovy │ │ │ └── UrlFilteredHarEntrySupplierTest.groovy │ │ ├── filters │ │ └── RewriteUrlFilterTest.groovy │ │ ├── mitmproxy │ │ ├── AbsentHarTest.groovy │ │ ├── AdditionalHeadersTest.groovy │ │ ├── AllowlistTest.groovy │ │ ├── AutoAuthTest.groovy │ │ ├── BlocklistTest.groovy │ │ ├── ChainedProxyAuthTest.groovy │ │ ├── DefaultHarPageTest.groovy │ │ ├── DefaultStepIdTest.groovy │ │ ├── GetHarTest.groovy │ │ ├── HarValidationTest.groovy │ │ ├── LatencyTest.groovy │ │ ├── NewHarTest.groovy │ │ ├── RewriteUrlFilterTest.groovy │ │ └── UnbalancedHarEntriesTest.groovy │ │ ├── proxy │ │ ├── AbsentHarTest.groovy │ │ ├── AutoAuthTest.groovy │ │ ├── BindAddressTest.groovy │ │ ├── BlacklistTest.groovy │ │ ├── ChainedProxyAuthTest.groovy │ │ ├── DefaultStepIdTest.groovy │ │ ├── FilterChainTest.groovy │ │ ├── GetHarTest.groovy │ │ ├── HarValidationTest.groovy │ │ ├── NewHarTest.groovy │ │ ├── NonProxyChainTest.groovy │ │ ├── RemapHostsTest.groovy │ │ ├── RewriteRuleTest.groovy │ │ ├── WhitelistTest.groovy │ │ └── assertion │ │ │ ├── AllUrlResponsesTimeWithinTest.groovy │ │ │ ├── BaseAssertionsTest.groovy │ │ │ ├── MostRecentUrlResponseTimeLessThanOrEqualTest.groovy │ │ │ └── field │ │ │ ├── content │ │ │ ├── ContentBaseTest.groovy │ │ │ ├── ContentSizeLessThanOrEqualTest.groovy │ │ │ ├── filtered │ │ │ │ ├── ContentContainsTest.groovy │ │ │ │ ├── ContentDoesNotContainTest.groovy │ │ │ │ ├── ContentLengthLessThanOrEqualTest.groovy │ │ │ │ ├── ContentMatchesTest.groovy │ │ │ │ └── FilteredContentBaseTest.groovy │ │ │ └── mostrecent │ │ │ │ ├── ContentContainsTest.groovy │ │ │ │ ├── ContentDoesNotContainTest.groovy │ │ │ │ ├── ContentMatchesTest.groovy │ │ │ │ └── MostRecentContentBaseTest.groovy │ │ │ ├── header │ │ │ ├── HeaderBaseTest.groovy │ │ │ ├── filtered │ │ │ │ ├── FilteredHeaderBaseTest.groovy │ │ │ │ ├── HeaderContainsTest.groovy │ │ │ │ ├── HeaderDoesNotContainTest.groovy │ │ │ │ └── HeaderMatchesTest.groovy │ │ │ └── mostrecent │ │ │ │ ├── HeaderContainsTest.groovy │ │ │ │ ├── HeaderDoesNotContainTest.groovy │ │ │ │ └── HeaderMatchesTest.groovy │ │ │ └── status │ │ │ ├── AllCurrentStepUrlsStatusAssertionsTest.groovy │ │ │ ├── FilteredUrlsStatusAssertionsTest.groovy │ │ │ └── mostrecent │ │ │ ├── FilteredMostRecentUrlStatusAssertionsTest.groovy │ │ │ └── MostRecentUrlStatusAssertionsTest.groovy │ │ └── util │ │ └── BrowserUpHttpUtilTest.groovy │ ├── java │ └── com │ │ └── browserup │ │ ├── bup │ │ └── proxy │ │ │ ├── InterceptorTest.java │ │ │ ├── NetworkTest.java │ │ │ ├── QuiescenceTest.java │ │ │ ├── dns │ │ │ ├── AdvancedHostResolverCacheTest.java │ │ │ ├── AdvancedHostResolverTest.java │ │ │ └── ChainedHostResolverTest.java │ │ │ └── test │ │ │ └── util │ │ │ ├── MockServerTest.java │ │ │ ├── NewProxyServerTest.java │ │ │ ├── NewProxyServerTestUtil.java │ │ │ └── TestConstants.java │ │ └── harreader │ │ ├── HarReaderTest.java │ │ ├── filter │ │ └── HarEntryFilterTest.java │ │ └── model │ │ ├── AbstractMapperTest.java │ │ ├── HarCacheTest.java │ │ ├── HarContentTest.java │ │ ├── HarCookieTest.java │ │ ├── HarCreatorBrowserTest.java │ │ ├── HarEntryTest.java │ │ ├── HarHeaderTest.java │ │ ├── HarLogTest.java │ │ ├── HarPageTest.java │ │ ├── HarPageTimingTest.java │ │ ├── HarPostDataParamTest.java │ │ ├── HarPostDataTest.java │ │ ├── HarQueryParamTest.java │ │ ├── HarRequestTest.java │ │ ├── HarResponseTest.java │ │ ├── HarTest.java │ │ ├── HarTimingTest.java │ │ └── HttpStatusTest.java │ └── resources │ ├── log4j2-test.json │ ├── sstoehr.har │ ├── sstoehr.invalid-date.har │ └── sstoehr.invalid-integer.har ├── browserup-proxy-dist ├── .classpath ├── .project ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── browserup │ │ └── bup │ │ ├── exception │ │ └── JettyException.java │ │ ├── proxy │ │ └── Main.java │ │ └── util │ │ └── DeleteDirectoryTask.java │ └── resources │ └── bup-logging.yaml ├── browserup-proxy-mitm ├── .classpath ├── .project ├── README.md ├── build.gradle └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── browserup │ │ │ └── bup │ │ │ ├── mitm │ │ │ ├── CertificateAndKey.java │ │ │ ├── CertificateAndKeySource.java │ │ │ ├── CertificateInfo.java │ │ │ ├── CertificateInfoGenerator.java │ │ │ ├── ExistingCertificateSource.java │ │ │ ├── HostnameCertificateInfoGenerator.java │ │ │ ├── KeyStoreCertificateSource.java │ │ │ ├── KeyStoreFileCertificateSource.java │ │ │ ├── PemFileCertificateSource.java │ │ │ ├── RootCertificateGenerator.java │ │ │ ├── TrustSource.java │ │ │ ├── exception │ │ │ │ ├── CertificateCreationException.java │ │ │ │ ├── CertificateSourceException.java │ │ │ │ ├── ExportException.java │ │ │ │ ├── ImportException.java │ │ │ │ ├── KeyGeneratorException.java │ │ │ │ ├── KeyStoreAccessException.java │ │ │ │ ├── MitmException.java │ │ │ │ ├── SslContextInitializationException.java │ │ │ │ ├── TrustSourceException.java │ │ │ │ └── UncheckedIOException.java │ │ │ ├── keys │ │ │ │ ├── ECKeyGenerator.java │ │ │ │ ├── KeyGenerator.java │ │ │ │ └── RSAKeyGenerator.java │ │ │ ├── manager │ │ │ │ └── ImpersonatingMitmManager.java │ │ │ ├── stats │ │ │ │ └── CertificateGenerationStatistics.java │ │ │ ├── tools │ │ │ │ ├── BouncyCastleSecurityProviderTool.java │ │ │ │ ├── DefaultSecurityProviderTool.java │ │ │ │ └── SecurityProviderTool.java │ │ │ ├── trustmanager │ │ │ │ ├── InsecureExtendedTrustManager.java │ │ │ │ └── InsecureTrustManagerFactory.java │ │ │ └── util │ │ │ │ ├── EncryptionUtil.java │ │ │ │ ├── KeyStoreUtil.java │ │ │ │ ├── MitmConstants.java │ │ │ │ ├── SslUtil.java │ │ │ │ └── TrustUtil.java │ │ │ └── util │ │ │ ├── ClasspathResourceUtil.java │ │ │ └── HttpUtil.java │ └── resources │ │ ├── cacerts.pem │ │ └── default-ciphers.txt │ └── test │ ├── groovy │ └── com │ │ └── browserup │ │ └── bup │ │ └── mitm │ │ ├── ExistingCertificateSourceTest.groovy │ │ ├── ImpersonatingMitmManagerTest.groovy │ │ ├── KeyStoreCertificateSourceTest.groovy │ │ ├── KeyStoreFileCertificateSourceTest.groovy │ │ ├── PemFileCertificateSourceTest.groovy │ │ ├── RootCertificateGeneratorTest.groovy │ │ ├── TrustSourceTest.groovy │ │ └── tools │ │ ├── ECKeyGeneratorTest.groovy │ │ └── RSAKeyGeneratorTest.groovy │ ├── java │ └── com │ │ └── browserup │ │ └── bup │ │ └── mitm │ │ ├── ImpersonationPerformanceTests.java │ │ ├── example │ │ ├── CustomCAKeyStoreExample.java │ │ ├── CustomCAPemFileExample.java │ │ ├── EllipticCurveCAandServerExample.java │ │ ├── LittleProxyDefaultConfigExample.java │ │ └── SaveGeneratedCAExample.java │ │ ├── integration │ │ └── LittleProxyIntegrationTest.java │ │ └── test │ │ └── util │ │ └── CertificateTestUtil.java │ └── resources │ ├── com │ └── browserup │ │ └── bup │ │ └── mitm │ │ ├── certificate.crt │ │ ├── encrypted-private-key.key │ │ ├── keystore.jks │ │ ├── keystore.p12 │ │ ├── trusted-cert.jks │ │ └── unencrypted-private-key.key │ └── log4j2-test.json ├── browserup-proxy-rest-clients ├── build.gradle └── src │ ├── main │ └── resources │ │ └── openapi-config.json │ └── test │ ├── groovy │ └── com │ │ └── browserup │ │ └── bup │ │ ├── WithRunningProxyRestTest.groovy │ │ ├── javascript │ │ └── JavaScriptClientTest.groovy │ │ ├── python │ │ └── PythonTestClient.groovy │ │ └── ruby │ │ └── RubyClientTest.groovy │ ├── javascript │ ├── .gitignore │ ├── Dockerfile │ └── test │ │ └── javascript_test.js │ ├── python │ ├── .gitignore │ ├── Dockerfile │ └── test │ │ └── python_test.py │ ├── resources │ └── log4j2-test.json │ └── ruby │ ├── .gitignore │ ├── .rspec │ ├── Dockerfile │ ├── Gemfile │ ├── Gemfile.lock │ └── spec │ └── test_client_spec.rb ├── browserup-proxy-rest ├── .classpath ├── .project ├── build.gradle └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── browserup │ │ │ └── bup │ │ │ ├── exception │ │ │ ├── JavascriptCompilationException.java │ │ │ ├── ProxyExistsException.java │ │ │ └── ProxyPortsExhaustedException.java │ │ │ ├── filters │ │ │ └── JavascriptRequestResponseFilter.java │ │ │ ├── proxy │ │ │ ├── MitmProxyManager.java │ │ │ ├── ProxyManager.java │ │ │ ├── bricks │ │ │ │ └── ProxyResource.java │ │ │ └── guice │ │ │ │ ├── ConfigModule.java │ │ │ │ ├── JettyModule.java │ │ │ │ ├── JettyServerProvider.java │ │ │ │ └── NamedImpl.java │ │ │ └── rest │ │ │ ├── filter │ │ │ └── LoggingFilter.java │ │ │ ├── openapi │ │ │ ├── CustomOpenApiReader.java │ │ │ └── DocConstants.java │ │ │ ├── resource │ │ │ ├── entries │ │ │ │ └── EntriesProxyResource.java │ │ │ └── mostrecent │ │ │ │ └── MostRecentEntryProxyResource.java │ │ │ └── validation │ │ │ ├── HttpStatusCodeConstraint.java │ │ │ ├── LongPositiveConstraint.java │ │ │ ├── NotBlankConstraint.java │ │ │ ├── NotNullConstraint.java │ │ │ ├── PatternConstraint.java │ │ │ ├── PortWithExistingProxyConstraint.java │ │ │ ├── mapper │ │ │ ├── ConstraintViolationExceptionMapper.java │ │ │ └── model │ │ │ │ ├── ArgumentConstraintsErrors.java │ │ │ │ └── ConstraintsErrors.java │ │ │ └── util │ │ │ └── MessageSanitizer.java │ └── resources │ │ └── swagger-config.yaml │ └── test │ ├── groovy │ └── com │ │ └── browserup │ │ └── bup │ │ └── proxy │ │ ├── FilterTest.groovy │ │ ├── mitmproxy │ │ ├── BaseRestTest.groovy │ │ ├── FindHarEntriesRestTest.groovy │ │ ├── FindMostRecentEntryRestTest.groovy │ │ ├── ProxyManagerTest.groovy │ │ ├── ProxyPortAssignmentTest.java │ │ ├── ProxyResourceTest.groovy │ │ ├── ValidateHarRestTest.groovy │ │ ├── WithRunningProxyRestTest.groovy │ │ ├── assertion │ │ │ ├── entries │ │ │ │ ├── EntriesAssertTimeLessThanOrEqualRestTest.groovy │ │ │ │ ├── content │ │ │ │ │ ├── BaseEntriesAssertContentRestTest.groovy │ │ │ │ │ ├── EntriesAssertContentContainsRestTest.groovy │ │ │ │ │ ├── EntriesAssertContentDoesNotContainRestTest.groovy │ │ │ │ │ ├── EntriesAssertContentLengthLessThanOrEqualRestTest.groovy │ │ │ │ │ └── EntriesAssertContentMatchesRestTest.groovy │ │ │ │ ├── header │ │ │ │ │ ├── BaseEntriesAssertHeaderRestTest.groovy │ │ │ │ │ ├── EntriesAssertHeaderContainsRestTest.groovy │ │ │ │ │ ├── EntriesAssertHeaderDoesNotContainRestTest.groovy │ │ │ │ │ └── EntriesAssertHeaderMatchesRestTest.groovy │ │ │ │ └── status │ │ │ │ │ ├── EntriesAssertStatusClientErrorRestTest.groovy │ │ │ │ │ ├── EntriesAssertStatusEqualsRestTest.groovy │ │ │ │ │ ├── EntriesAssertStatusRedirectionRestTest.groovy │ │ │ │ │ ├── EntriesAssertStatusServerErrorRestTest.groovy │ │ │ │ │ └── EntriesAssertStatusSuccessRestTest.groovy │ │ │ └── mostrecent │ │ │ │ ├── MostRecentEntryAssertTimeLessThanOrEqualRestTest.groovy │ │ │ │ ├── content │ │ │ │ ├── MostRecentEntryAssertContentContainsRestTest.groovy │ │ │ │ ├── MostRecentEntryAssertContentDoesNotContainRestTest.groovy │ │ │ │ └── MostRecentEntryAssertContentLengthLessThanOrEqualRestTest.groovy │ │ │ │ ├── header │ │ │ │ ├── MostRecentEntryAssertHeaderContainsRestTest.groovy │ │ │ │ ├── MostRecentEntryAssertHeaderDoesNotContainRestTest.groovy │ │ │ │ └── MostRecentEntryAssertHeaderMatchesRestTest.groovy │ │ │ │ └── status │ │ │ │ ├── MostRecentEntryAssertStatusClientErrorRestTest.groovy │ │ │ │ ├── MostRecentEntryAssertStatusEqualsRestTest.groovy │ │ │ │ ├── MostRecentEntryAssertStatusRedirectionRestTest.groovy │ │ │ │ ├── MostRecentEntryAssertStatusServerErrorRestTest.groovy │ │ │ │ └── MostRecentEntryAssertStatusSuccessRestTest.groovy │ │ └── validation │ │ │ ├── HttpStatusCodeConstraintTest.groovy │ │ │ ├── LongPositiveConstraintTest.groovy │ │ │ ├── PatternConstraintTest.groovy │ │ │ └── PortWithExistingProxyConstraintTest.groovy │ │ └── test │ │ └── util │ │ └── ProxyResourceTest.groovy │ ├── java │ └── com │ │ └── browserup │ │ └── bup │ │ └── proxy │ │ ├── ExpiringProxyTest.java │ │ ├── ProxyPortAssignmentTest.java │ │ └── test │ │ └── util │ │ └── ProxyManagerTest.java │ └── resources │ └── log4j2-test.json ├── browserup-proxy.iml ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── nohup.out └── settings.gradle /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Add Proxy with these settings, in this manner 16 | 2. Go to '...' 17 | 3. Look in '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Please complete the following information:** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari, firefox] 29 | - Version [e.g. 22] 30 | 31 | 32 | 33 | **Additional context** 34 | Add any other context about the problem here. 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[enhancement]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | browserup-proxy 4 | Project browserup-proxy created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.buildship.core.gradleprojectbuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.buildship.core.gradleprojectnature 16 | 17 | 18 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | How to Contribute 2 | 3 | Find a bug? 4 | 5 | Ensure the bug was not already reported by searching on GitHub under Issues. 6 | 7 | If you're unable to find an open issue addressing the problem, open a new one. Be sure to include a title and clear description, as much relevant information as possible, and a code sample or an executable test case demonstrating the expected behavior that is not occurring. 8 | 9 | Did you write a patch that fixes a bug? 10 | 11 | Open a new GitHub pull request with the patch. We may choose not to accept pull requests without tests, or for other reasons. 12 | 13 | Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. 14 | 15 | Contributions will be Apache 2 Licensed. Contributions welcomed! 16 | 17 | Thanks! ❤️ ❤️ ❤️ 18 | 19 | BrowserUp Team 20 | -------------------------------------------------------------------------------- /browserup-proxy-core/.classpath: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /browserup-proxy-core/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | browserup-proxy-core 4 | Project browserup-proxy-core created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.buildship.core.gradleprojectnature 22 | 23 | 24 | -------------------------------------------------------------------------------- /browserup-proxy-core/Mitmproxy_Integration_Notes.txt: -------------------------------------------------------------------------------- 1 | 1. com.browserup.bup.MitmProxyServer.setChainedProxyNonProxyHosts 2 | 3 | Mitmproxy doesn't provide 'upstream' proxy exception hosts API: 4 | https://github.com/mitmproxy/mitmproxy/issues/1821 5 | 6 | Possible workaround might be using approach from addon 'change_upstream_proxy.py' (using flow.live.change_upstream_proxy_server), 7 | but it doesn't allow to avoid upstream proxy at all - only switch between upstream proxies. 8 | We could add transparent pass-through proxy for user and redirect request there in case request host is matched by non-upstream-proxy host filters, provided by user, but that doesn't sound acceptable, we'd still have upstream proxy. 9 | Related issue: 10 | https://github.com/mitmproxy/mitmproxy/issues/2123 11 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/HarEntryAssertion.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.assertion; 6 | 7 | import com.browserup.bup.assertion.error.HarEntryAssertionError; 8 | import com.browserup.harreader.model.HarEntry; 9 | 10 | import java.util.Optional; 11 | 12 | @FunctionalInterface 13 | public interface HarEntryAssertion { 14 | 15 | Optional assertion(HarEntry entry); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/ResponseTimeLessThanOrEqualAssertion.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.assertion; 6 | 7 | import com.browserup.bup.assertion.error.HarEntryAssertionError; 8 | import com.browserup.harreader.model.HarEntry; 9 | 10 | import java.util.Optional; 11 | 12 | public class ResponseTimeLessThanOrEqualAssertion implements HarEntryAssertion { 13 | private final Long time; 14 | 15 | public ResponseTimeLessThanOrEqualAssertion(Long time) { 16 | this.time = time; 17 | } 18 | 19 | @Override 20 | public Optional assertion(HarEntry entry) { 21 | if (entry.getTime() > time) { 22 | return Optional.of(new HarEntryAssertionError("Time exceeded", time, entry.getTime())); 23 | } 24 | return Optional.empty(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/error/HarEntryAssertionError.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.assertion.error; 6 | 7 | public class HarEntryAssertionError { 8 | private final String message; 9 | 10 | public HarEntryAssertionError(String message) { 11 | this.message = message; 12 | } 13 | 14 | public HarEntryAssertionError(Object expected, Object actual) { 15 | this.message = String.format("Assertion failed, expected: %s, actual: %s", 16 | String.valueOf(expected), String.valueOf(actual)); 17 | } 18 | 19 | public HarEntryAssertionError(String prefixMessage, Object expected, Object actual) { 20 | this.message = String.format("%s. Expected: %s, actual: %s", 21 | prefixMessage, String.valueOf(expected), String.valueOf(actual)); 22 | } 23 | 24 | public String getMessage() { 25 | return message; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/field/FieldPassesPredicateAssertion.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.assertion.field; 2 | 3 | import com.browserup.bup.assertion.HarEntryAssertion; 4 | import com.browserup.bup.assertion.error.HarEntryAssertionError; 5 | import com.browserup.harreader.model.HarEntry; 6 | 7 | import java.util.Optional; 8 | 9 | public abstract class FieldPassesPredicateAssertion implements HarEntryAssertion { 10 | 11 | public abstract HarEntryAssertionFieldSupplier getFieldSupplier(); 12 | 13 | public abstract HarEntryPredicate getHarEntryPredicate(); 14 | 15 | @Override 16 | public Optional assertion(HarEntry entry) { 17 | FieldType input = getFieldSupplier().apply(entry); 18 | 19 | return getHarEntryPredicate().test(input).map(HarEntryAssertionError::new); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/field/HarEntryAssertionFieldSupplier.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.assertion.field; 2 | 3 | import com.browserup.harreader.model.HarEntry; 4 | 5 | import java.util.function.Function; 6 | 7 | @FunctionalInterface 8 | public interface HarEntryAssertionFieldSupplier extends Function { 9 | } 10 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/field/HarEntryPredicate.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.assertion.field; 2 | 3 | import java.util.Optional; 4 | 5 | public interface HarEntryPredicate { 6 | 7 | Optional test(T entry); 8 | } 9 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/field/content/ContentContainsStringAssertion.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.assertion.field.content; 2 | 3 | import com.browserup.bup.assertion.field.HarEntryPredicate; 4 | import org.apache.commons.lang3.StringUtils; 5 | 6 | import java.util.Optional; 7 | 8 | public class ContentContainsStringAssertion extends ContentPassesPredicateAssertion { 9 | private final String text; 10 | 11 | public ContentContainsStringAssertion(String text) { 12 | this.text = text; 13 | } 14 | 15 | @Override 16 | public HarEntryPredicate getHarEntryPredicate() { 17 | return content -> { 18 | Optional result = Optional.empty(); 19 | if (!StringUtils.contains(content, text)) { 20 | result = Optional.of( 21 | String.format( 22 | "Expected to find string in content. Search string: '%s', content: '%s'", 23 | text, content 24 | )); 25 | } 26 | return result; 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/field/content/ContentDoesNotContainStringAssertion.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.assertion.field.content; 2 | 3 | import com.browserup.bup.assertion.field.HarEntryPredicate; 4 | import org.apache.commons.lang3.StringUtils; 5 | 6 | import java.util.Optional; 7 | 8 | public class ContentDoesNotContainStringAssertion extends ContentPassesPredicateAssertion { 9 | private final String text; 10 | 11 | public ContentDoesNotContainStringAssertion(String text) { 12 | this.text = text; 13 | } 14 | 15 | @Override 16 | public HarEntryPredicate getHarEntryPredicate() { 17 | return content -> { 18 | Optional result = Optional.empty(); 19 | if (StringUtils.contains(content, text)) { 20 | result = Optional.of( 21 | String.format( 22 | "Expected to find no string with specified value in content. Search string: '%s', content: '%s'", 23 | text, content 24 | )); 25 | } 26 | return result; 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/field/content/ContentMatchesAssertion.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.assertion.field.content; 2 | 3 | import com.browserup.bup.assertion.field.HarEntryPredicate; 4 | 5 | import java.util.Optional; 6 | import java.util.regex.Pattern; 7 | 8 | public class ContentMatchesAssertion extends ContentPassesPredicateAssertion { 9 | private final Pattern contentPattern; 10 | 11 | public ContentMatchesAssertion(Pattern contentPattern) { 12 | this.contentPattern = contentPattern; 13 | } 14 | 15 | @Override 16 | public HarEntryPredicate getHarEntryPredicate() { 17 | return content -> { 18 | Optional result = Optional.empty(); 19 | if (content == null || !contentPattern.matcher(content).matches()) { 20 | result = Optional.of( 21 | String.format("Expected content to match pattern. Pattern: '%s', content: '%s'", 22 | contentPattern.pattern(), 23 | content 24 | )); 25 | } 26 | return result; 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/field/content/ContentPassesPredicateAssertion.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.assertion.field.content; 2 | 3 | import com.browserup.bup.assertion.field.FieldPassesPredicateAssertion; 4 | import com.browserup.bup.assertion.field.HarEntryAssertionFieldSupplier; 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | public abstract class ContentPassesPredicateAssertion extends FieldPassesPredicateAssertion { 8 | 9 | @Override 10 | public HarEntryAssertionFieldSupplier getFieldSupplier() { 11 | return entry -> StringUtils.defaultString(entry.getResponse().getContent().getText()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/field/content/ContentSizeLessThanOrEqualAssertion.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.assertion.field.content; 2 | 3 | import com.browserup.bup.assertion.field.HarEntryPredicate; 4 | 5 | import java.util.Optional; 6 | 7 | public class ContentSizeLessThanOrEqualAssertion extends ContentSizePassesPredicateAssertion { 8 | private final Long size; 9 | 10 | public ContentSizeLessThanOrEqualAssertion(Long size) { 11 | this.size = size; 12 | } 13 | 14 | @Override 15 | public HarEntryPredicate getHarEntryPredicate() { 16 | return s -> { 17 | Optional result = Optional.empty(); 18 | if (s > size) { 19 | result = Optional.of(String.format( 20 | "Expected content length not to exceed max value. Max value: %d, content length: %d", size, s)); 21 | } 22 | return result; 23 | }; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/field/content/ContentSizePassesPredicateAssertion.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.assertion.field.content; 2 | 3 | import com.browserup.bup.assertion.field.FieldPassesPredicateAssertion; 4 | import com.browserup.bup.assertion.field.HarEntryAssertionFieldSupplier; 5 | 6 | public abstract class ContentSizePassesPredicateAssertion extends FieldPassesPredicateAssertion { 7 | 8 | @Override 9 | public HarEntryAssertionFieldSupplier getFieldSupplier() { 10 | return entry -> entry.getResponse().getContent().getSize(); 11 | } 12 | } -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/field/header/FilteredHeadersContainStringAssertion.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.assertion.field.header; 2 | 3 | import com.browserup.bup.assertion.field.HarEntryPredicate; 4 | import com.browserup.harreader.model.HarHeader; 5 | 6 | import java.util.List; 7 | import java.util.Optional; 8 | 9 | import static org.apache.commons.lang3.StringUtils.contains; 10 | 11 | public class FilteredHeadersContainStringAssertion extends HeadersPassPredicateAssertion { 12 | private final String name; 13 | private final String value; 14 | 15 | public FilteredHeadersContainStringAssertion(String name, String value) { 16 | this.name = name; 17 | this.value = value; 18 | } 19 | 20 | @Override 21 | public HarEntryPredicate> getHarEntryPredicate() { 22 | return harHeaders -> { 23 | Optional result = Optional.empty(); 24 | boolean contains = harHeaders.stream() 25 | .filter(NONEMPTY_HEADER_FILTER) 26 | .filter(h -> h.getName().equals(name)) 27 | .anyMatch(h -> contains(h.getValue(), value)); 28 | if (!contains) { 29 | result = Optional.of(String.format( 30 | "Expected to find header with name: '%s' and value containing string: '%s'", name, value 31 | )); 32 | } 33 | return result; 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/field/header/FilteredHeadersDoNotContainStringAssertion.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.assertion.field.header; 2 | 3 | import com.browserup.bup.assertion.field.HarEntryPredicate; 4 | import com.browserup.harreader.model.HarHeader; 5 | 6 | import java.util.List; 7 | import java.util.Optional; 8 | 9 | import static org.apache.commons.lang3.StringUtils.contains; 10 | 11 | public class FilteredHeadersDoNotContainStringAssertion extends HeadersPassPredicateAssertion { 12 | private final String value; 13 | private final String name; 14 | 15 | public FilteredHeadersDoNotContainStringAssertion(String name, String value) { 16 | this.value = value; 17 | this.name = name; 18 | } 19 | 20 | @Override 21 | public HarEntryPredicate> getHarEntryPredicate() { 22 | return harHeaders -> { 23 | Optional found = harHeaders.stream() 24 | .filter(NONEMPTY_HEADER_FILTER) 25 | .filter(h -> h.getName().equals(name)) 26 | .filter(h -> contains(h.getValue(), value)) 27 | .findFirst(); 28 | 29 | return found.map(h -> String.format( 30 | "Expected to find no header with name '%s' and value containing string '%s'", h.getName(), value)); 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/field/header/FilteredHeadersMatchAssertion.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.assertion.field.header; 2 | 3 | import com.browserup.bup.assertion.field.HarEntryPredicate; 4 | import com.browserup.harreader.model.HarHeader; 5 | 6 | import java.util.List; 7 | import java.util.Optional; 8 | import java.util.regex.Pattern; 9 | import java.util.stream.Collectors; 10 | 11 | public class FilteredHeadersMatchAssertion extends HeadersPassPredicateAssertion { 12 | private final Pattern namePattern; 13 | private final Pattern valuePattern; 14 | 15 | public FilteredHeadersMatchAssertion(Pattern namePattern, Pattern valuePattern) { 16 | this.namePattern = namePattern; 17 | this.valuePattern = valuePattern; 18 | } 19 | 20 | @Override 21 | public HarEntryPredicate> getHarEntryPredicate() { 22 | return harHeaders -> { 23 | Optional result = Optional.empty(); 24 | 25 | List notMatchingHeaders = harHeaders.stream() 26 | .filter(NONEMPTY_HEADER_FILTER) 27 | .filter(h -> namePattern.matcher(h.getName()).matches()) 28 | .filter(h -> !valuePattern.matcher(h.getValue()).matches()) 29 | .collect(Collectors.toList()); 30 | 31 | if (notMatchingHeaders.size() > 0) { 32 | String notMatchingHeadersNames = notMatchingHeaders.stream() 33 | .map(HarHeader::getName) 34 | .collect(Collectors.joining(",")); 35 | 36 | result = Optional.of(String.format( 37 | "Expected headers with names matching pattern: '%s' to have values matching pattern: '%s'. " + 38 | "Headers names not matching value pattern: '%s'", 39 | namePattern.pattern(), valuePattern.pattern(), notMatchingHeadersNames 40 | )); 41 | } 42 | 43 | return result; 44 | }; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/field/header/HeadersContainStringAssertion.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.assertion.field.header; 2 | 3 | import com.browserup.bup.assertion.field.HarEntryPredicate; 4 | import com.browserup.harreader.model.HarHeader; 5 | 6 | import java.util.List; 7 | import java.util.Optional; 8 | 9 | import static org.apache.commons.lang3.StringUtils.contains; 10 | 11 | public class HeadersContainStringAssertion extends HeadersPassPredicateAssertion { 12 | private final String value; 13 | 14 | public HeadersContainStringAssertion(String value) { 15 | this.value = value; 16 | } 17 | 18 | @Override 19 | public HarEntryPredicate> getHarEntryPredicate() { 20 | return harHeaders -> { 21 | Optional result = Optional.empty(); 22 | boolean contains = harHeaders.stream() 23 | .filter(NONEMPTY_HEADER_FILTER) 24 | .map(HarHeader::getValue) 25 | .anyMatch(hv -> contains(hv, value)); 26 | if (!contains) { 27 | result = Optional.of(String.format( 28 | "Expected to find one or more headers containing string: '%s'", value 29 | )); 30 | } 31 | return result; 32 | }; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/field/header/HeadersDoNotContainStringAssertion.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.assertion.field.header; 2 | 3 | import com.browserup.bup.assertion.field.HarEntryPredicate; 4 | import com.browserup.harreader.model.HarHeader; 5 | 6 | import java.util.List; 7 | import java.util.Optional; 8 | 9 | import static org.apache.commons.lang3.StringUtils.contains; 10 | 11 | public class HeadersDoNotContainStringAssertion extends HeadersPassPredicateAssertion { 12 | private final String value; 13 | 14 | public HeadersDoNotContainStringAssertion(String value) { 15 | this.value = value; 16 | } 17 | 18 | @Override 19 | public HarEntryPredicate> getHarEntryPredicate() { 20 | return harHeaders -> { 21 | Optional found = harHeaders.stream() 22 | .filter(NONEMPTY_HEADER_FILTER) 23 | .filter(hv -> contains(hv.getValue(), value)) 24 | .findFirst(); 25 | 26 | return found.map(h -> String.format( 27 | "Expected to find no headers containing string '%s'. Found header with name: '%s' containing string: '%s'", 28 | value, h.getName(), value 29 | )); 30 | }; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/field/header/HeadersMatchAssertion.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.assertion.field.header; 2 | 3 | import com.browserup.bup.assertion.field.HarEntryPredicate; 4 | import com.browserup.harreader.model.HarHeader; 5 | 6 | import java.util.List; 7 | import java.util.Optional; 8 | import java.util.regex.Pattern; 9 | import java.util.stream.Collectors; 10 | 11 | public class HeadersMatchAssertion extends HeadersPassPredicateAssertion { 12 | private final Pattern valuePattern; 13 | 14 | public HeadersMatchAssertion(Pattern valuePattern) { 15 | this.valuePattern = valuePattern; 16 | } 17 | 18 | @Override 19 | public HarEntryPredicate> getHarEntryPredicate() { 20 | return harHeaders -> { 21 | Optional result = Optional.empty(); 22 | 23 | List notMatchingHeaders = harHeaders.stream() 24 | .filter(NONEMPTY_HEADER_FILTER) 25 | .filter(h -> !valuePattern.matcher(h.getValue()).matches()) 26 | .collect(Collectors.toList()); 27 | 28 | if (notMatchingHeaders.size() > 0) { 29 | String notMatchingHeadersNames = notMatchingHeaders.stream() 30 | .map(HarHeader::getName) 31 | .collect(Collectors.joining(",")); 32 | 33 | result = Optional.of(String.format( 34 | "Expected headers values to match pattern: '%s'. Headers names which values don't match value pattern: %s", 35 | valuePattern.pattern(), notMatchingHeadersNames 36 | )); 37 | } 38 | 39 | return result; 40 | }; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/field/header/HeadersPassPredicateAssertion.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.assertion.field.header; 2 | 3 | import com.browserup.bup.assertion.field.FieldPassesPredicateAssertion; 4 | import com.browserup.bup.assertion.field.HarEntryAssertionFieldSupplier; 5 | import com.browserup.harreader.model.HarHeader; 6 | 7 | import java.util.List; 8 | import java.util.function.Predicate; 9 | 10 | public abstract class HeadersPassPredicateAssertion extends FieldPassesPredicateAssertion> { 11 | protected static final Predicate NONEMPTY_HEADER_FILTER = h -> h.getName() != null && h.getValue() != null; 12 | 13 | @Override 14 | public HarEntryAssertionFieldSupplier> getFieldSupplier() { 15 | return entry -> entry.getResponse().getHeaders(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/field/status/StatusBelongsToClassAssertion.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.assertion.field.status; 2 | 3 | import com.browserup.bup.assertion.field.HarEntryPredicate; 4 | import com.browserup.bup.util.HttpStatusClass; 5 | 6 | import java.util.Optional; 7 | 8 | public class StatusBelongsToClassAssertion extends StatusPassesPredicateAssertion { 9 | private final HttpStatusClass statusClass; 10 | 11 | 12 | public StatusBelongsToClassAssertion(HttpStatusClass statusClass) { 13 | this.statusClass = statusClass; 14 | } 15 | 16 | @Override 17 | public HarEntryPredicate getHarEntryPredicate() { 18 | return s -> { 19 | Optional result = Optional.empty(); 20 | 21 | if (!statusClass.contains(s)) { 22 | result = Optional.of(String.format( 23 | "Expected response status to belong to class: %s, but was: %d (belongs to %s)", 24 | statusClass.name(), s, HttpStatusClass.valueOf(s) 25 | )); 26 | } 27 | 28 | return result; 29 | }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/field/status/StatusEqualsAssertion.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.assertion.field.status; 2 | 3 | import com.browserup.bup.assertion.field.HarEntryPredicate; 4 | 5 | import java.util.Optional; 6 | 7 | public class StatusEqualsAssertion extends StatusPassesPredicateAssertion { 8 | private final Integer status; 9 | 10 | public StatusEqualsAssertion(Integer status) { 11 | this.status = status; 12 | } 13 | 14 | @Override 15 | public HarEntryPredicate getHarEntryPredicate() { 16 | return s -> { 17 | Optional result = Optional.empty(); 18 | 19 | if (!s.equals(status)) { 20 | return Optional.of(String.format( 21 | "Expected response status to be: '%d', but was: '%d'", status, s)); 22 | } 23 | 24 | return result; 25 | }; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/field/status/StatusPassesPredicateAssertion.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.assertion.field.status; 2 | 3 | import com.browserup.bup.assertion.field.FieldPassesPredicateAssertion; 4 | import com.browserup.bup.assertion.field.HarEntryAssertionFieldSupplier; 5 | 6 | public abstract class StatusPassesPredicateAssertion extends FieldPassesPredicateAssertion { 7 | 8 | @Override 9 | public HarEntryAssertionFieldSupplier getFieldSupplier() { 10 | return entry -> entry.getResponse().getStatus(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/model/AssertionEntryResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.assertion.model; 6 | 7 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 8 | import com.fasterxml.jackson.annotation.JsonInclude; 9 | import org.apache.commons.lang3.StringUtils; 10 | 11 | @JsonInclude(JsonInclude.Include.NON_NULL) 12 | @JsonIgnoreProperties(ignoreUnknown = true) 13 | public class AssertionEntryResult { 14 | private String url; 15 | private String message; 16 | private Boolean failed; 17 | 18 | public AssertionEntryResult() { 19 | } 20 | 21 | private AssertionEntryResult(String url, String message, Boolean failed) { 22 | this.url = url; 23 | this.message = message; 24 | this.failed = failed; 25 | } 26 | 27 | public String getUrl() { 28 | return this.url; 29 | } 30 | 31 | public String getMessage() { 32 | return this.message; 33 | } 34 | 35 | public Boolean getFailed() { 36 | return this.failed; 37 | } 38 | 39 | public static class Builder { 40 | private String url; 41 | private String message; 42 | private Boolean failed; 43 | 44 | public Builder setUrl(String url) { 45 | this.url = url; 46 | return this; 47 | } 48 | 49 | public Builder setMessage(String message) { 50 | this.message = message; 51 | return this; 52 | } 53 | 54 | public Builder setFailed(Boolean failed) { 55 | this.failed = failed; 56 | return this; 57 | } 58 | 59 | public AssertionEntryResult create() { 60 | if (StringUtils.isEmpty(url) || failed == null) { 61 | throw new IllegalArgumentException("Not all required fields are set"); 62 | } 63 | return new AssertionEntryResult(url, message, failed); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/model/filter/AssertionFilterInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.assertion.model.filter; 6 | 7 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 8 | import com.fasterxml.jackson.annotation.JsonInclude; 9 | 10 | @JsonInclude(JsonInclude.Include.NON_NULL) 11 | @JsonIgnoreProperties(ignoreUnknown = true) 12 | public class AssertionFilterInfo { 13 | public AssertionFilterInfo() { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/model/filter/AssertionUrlFilterInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.assertion.model.filter; 6 | 7 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 8 | import com.fasterxml.jackson.annotation.JsonInclude; 9 | 10 | @JsonInclude(JsonInclude.Include.NON_NULL) 11 | @JsonIgnoreProperties(ignoreUnknown = true) 12 | public class AssertionUrlFilterInfo extends AssertionFilterInfo { 13 | private String urlPattern; 14 | 15 | public AssertionUrlFilterInfo() { 16 | } 17 | 18 | public AssertionUrlFilterInfo(String urlPattern) { 19 | this.urlPattern = urlPattern; 20 | } 21 | 22 | public String getUrlPattern() { 23 | return urlPattern; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/supplier/CurrentStepHarEntriesSupplier.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.assertion.supplier; 2 | 3 | import com.browserup.bup.assertion.model.filter.AssertionFilterInfo; 4 | import com.browserup.harreader.model.Har; 5 | import com.browserup.harreader.model.HarEntry; 6 | 7 | import java.util.List; 8 | 9 | public class CurrentStepHarEntriesSupplier extends HarEntriesSupplier { 10 | public CurrentStepHarEntriesSupplier(Har har) { 11 | super(har, new AssertionFilterInfo()); 12 | } 13 | 14 | @Override 15 | public List get() { 16 | return getHar().getLog().getEntries(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/supplier/HarEntriesSupplier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.assertion.supplier; 6 | 7 | import com.browserup.bup.assertion.model.filter.AssertionFilterInfo; 8 | import com.browserup.harreader.model.Har; 9 | import com.browserup.harreader.model.HarEntry; 10 | 11 | import java.util.List; 12 | import java.util.function.Supplier; 13 | 14 | public abstract class HarEntriesSupplier implements Supplier> { 15 | private final Har har; 16 | private final AssertionFilterInfo filterInfo; 17 | 18 | public HarEntriesSupplier(Har har, AssertionFilterInfo filterInfo) { 19 | this.har = har; 20 | this.filterInfo = filterInfo; 21 | } 22 | 23 | public Har getHar() { 24 | return har; 25 | } 26 | 27 | public AssertionFilterInfo getFilterInfo() { 28 | return filterInfo; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/supplier/MostRecentHarEntrySupplier.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.assertion.supplier; 2 | 3 | import com.browserup.bup.assertion.model.filter.AssertionFilterInfo; 4 | import com.browserup.harreader.model.Har; 5 | import com.browserup.harreader.model.HarEntry; 6 | 7 | import java.util.Collections; 8 | import java.util.Comparator; 9 | import java.util.List; 10 | import java.util.regex.Pattern; 11 | 12 | public class MostRecentHarEntrySupplier extends HarEntriesSupplier { 13 | 14 | public MostRecentHarEntrySupplier(Har har) { 15 | super(har, new AssertionFilterInfo()); 16 | } 17 | 18 | @Override 19 | public List get() { 20 | return getHar().getLog().getEntries().stream() 21 | .max(Comparator.comparing(HarEntry::getStartedDateTime)) 22 | .map(Collections::singletonList) 23 | .orElse(Collections.emptyList()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/supplier/MostRecentUrlFilteredHarEntrySupplier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.assertion.supplier; 6 | 7 | import com.browserup.harreader.model.Har; 8 | import com.browserup.harreader.model.HarEntry; 9 | 10 | import java.util.Collections; 11 | import java.util.List; 12 | import java.util.regex.Pattern; 13 | 14 | public class MostRecentUrlFilteredHarEntrySupplier extends UrlFilteredHarEntriesSupplier { 15 | 16 | public MostRecentUrlFilteredHarEntrySupplier(Har har, Pattern pattern) { 17 | super(har, pattern); 18 | } 19 | 20 | @Override 21 | public List get() { 22 | return getHar().getLog().findMostRecentEntry(getPattern()). 23 | map(Collections::singletonList). 24 | orElse(Collections.emptyList()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/assertion/supplier/UrlFilteredHarEntriesSupplier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.assertion.supplier; 6 | 7 | import com.browserup.bup.assertion.model.filter.AssertionUrlFilterInfo; 8 | import com.browserup.harreader.model.Har; 9 | import com.browserup.harreader.model.HarEntry; 10 | 11 | import java.util.List; 12 | import java.util.regex.Pattern; 13 | 14 | public class UrlFilteredHarEntriesSupplier extends HarEntriesSupplier { 15 | private final Pattern pattern; 16 | 17 | public UrlFilteredHarEntriesSupplier(Har har, Pattern pattern) { 18 | super(har, new AssertionUrlFilterInfo(pattern.pattern())); 19 | this.pattern = pattern; 20 | } 21 | 22 | @Override 23 | public List get() { 24 | return getHar().getLog().findEntries(pattern); 25 | } 26 | 27 | public Pattern getPattern() { 28 | return pattern; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/exception/DecompressionException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.exception; 6 | 7 | /** 8 | * Indicates that an error occurred while decompressing content. 9 | */ 10 | public class DecompressionException extends RuntimeException { 11 | private static final long serialVersionUID = 8666473793514307564L; 12 | 13 | public DecompressionException() { 14 | } 15 | 16 | public DecompressionException(String message) { 17 | super(message); 18 | } 19 | 20 | public DecompressionException(String message, Throwable cause) { 21 | super(message, cause); 22 | } 23 | 24 | public DecompressionException(Throwable cause) { 25 | super(cause); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/exception/UnsupportedCharsetException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.exception; 6 | 7 | /** 8 | * A checked exception wrapper for {@link java.nio.charset.UnsupportedCharsetException}. This exception is checked to prevent 9 | * situations where an unsupported character set in e.g. a Content-Type header causes the proxy to fail completely, rather 10 | * than fallback to some suitable default behavior, such as not parsing the text contents of a message. 11 | */ 12 | public class UnsupportedCharsetException extends Exception { 13 | public UnsupportedCharsetException(java.nio.charset.UnsupportedCharsetException e) { 14 | super(e); 15 | 16 | if (e == null) { 17 | throw new IllegalArgumentException("com.browserup.bup.exception.UnsupportedCharsetException must be initialized with a non-null instance of java.nio.charset.UnsupportedCharsetException"); 18 | } 19 | } 20 | 21 | /** 22 | * @return the underlying {@link java.nio.charset.UnsupportedCharsetException} that this exception wraps. 23 | */ 24 | public java.nio.charset.UnsupportedCharsetException getUnsupportedCharsetExceptionCause() { 25 | return (java.nio.charset.UnsupportedCharsetException) this.getCause(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/filters/AddHeadersFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.filters; 6 | 7 | import io.netty.handler.codec.http.HttpObject; 8 | import io.netty.handler.codec.http.HttpRequest; 9 | import io.netty.handler.codec.http.HttpResponse; 10 | import org.littleshoot.proxy.HttpFiltersAdapter; 11 | 12 | import java.util.Collections; 13 | import java.util.Map; 14 | 15 | /** 16 | * Adds the headers specified in the constructor to this request. The filter does not make a defensive copy of the map, so there is no guarantee 17 | * that the map at the time of construction will contain the same values when the filter is actually invoked, if the map is modified concurrently. 18 | */ 19 | public class AddHeadersFilter extends HttpFiltersAdapter { 20 | private final Map additionalHeaders; 21 | 22 | public AddHeadersFilter(HttpRequest originalRequest, Map additionalHeaders) { 23 | super(originalRequest); 24 | 25 | if (additionalHeaders != null) { 26 | this.additionalHeaders = additionalHeaders; 27 | } else { 28 | this.additionalHeaders = Collections.emptyMap(); 29 | } 30 | } 31 | 32 | @Override 33 | public HttpResponse clientToProxyRequest(HttpObject httpObject) { 34 | if (httpObject instanceof HttpRequest) { 35 | HttpRequest httpRequest = (HttpRequest) httpObject; 36 | 37 | additionalHeaders.forEach((key, value) -> httpRequest.headers().add(key, value)); 38 | } 39 | 40 | return null; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/filters/LatencyFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.filters; 6 | 7 | import io.netty.handler.codec.http.HttpObject; 8 | import io.netty.handler.codec.http.HttpRequest; 9 | import io.netty.handler.codec.http.HttpResponse; 10 | import org.littleshoot.proxy.HttpFiltersAdapter; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import java.util.concurrent.TimeUnit; 15 | 16 | /** 17 | * Adds latency to a response before sending it to the client. This filter always adds the specified latency, even if the latency 18 | * between the proxy and the remote server already exceeds this value. 19 | */ 20 | public class LatencyFilter extends HttpFiltersAdapter { 21 | private static final Logger log = LoggerFactory.getLogger(HttpFiltersAdapter.class); 22 | 23 | private final int latencyMs; 24 | 25 | public LatencyFilter(HttpRequest originalRequest, int latencyMs) { 26 | super(originalRequest); 27 | 28 | this.latencyMs = latencyMs; 29 | } 30 | 31 | @Override 32 | public HttpObject proxyToClientResponse(HttpObject httpObject) { 33 | if (httpObject instanceof HttpResponse) { 34 | if (latencyMs > 0) { 35 | try { 36 | TimeUnit.MILLISECONDS.sleep(latencyMs); 37 | } catch (InterruptedException e) { 38 | Thread.currentThread().interrupt(); 39 | 40 | log.warn("Interrupted while adding latency to response", e); 41 | } 42 | } 43 | } 44 | 45 | return super.proxyToClientResponse(httpObject); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/filters/ModifiedRequestAwareFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.filters; 6 | 7 | import io.netty.handler.codec.http.HttpRequest; 8 | 9 | /** 10 | * Indicates that a filter wishes to capture the final HttpRequest that is sent to the server, reflecting all 11 | * modifications from request filters. {@link BrowserUpHttpFilterChain#clientToProxyRequest(io.netty.handler.codec.http.HttpObject)} 12 | * will invoke the {@link #setModifiedHttpRequest(HttpRequest)} method after all filters have processed the initial 13 | * {@link HttpRequest} object. 14 | */ 15 | public interface ModifiedRequestAwareFilter { 16 | /** 17 | * Notifies implementing classes of the modified HttpRequest that will be sent to the server, reflecting all 18 | * modifications from filters. 19 | * 20 | * @param modifiedHttpRequest the modified HttpRequest sent to the server 21 | */ 22 | void setModifiedHttpRequest(HttpRequest modifiedHttpRequest); 23 | } 24 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/filters/RegisterRequestFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.filters; 6 | 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.handler.codec.http.HttpObject; 9 | import io.netty.handler.codec.http.HttpRequest; 10 | import io.netty.handler.codec.http.HttpResponse; 11 | import com.browserup.bup.proxy.ActivityMonitor; 12 | import org.littleshoot.proxy.HttpFiltersAdapter; 13 | 14 | /** 15 | * Registers this request with the {@link com.browserup.bup.proxy.ActivityMonitor} when the HttpRequest is received from the client. 16 | */ 17 | public class RegisterRequestFilter extends HttpFiltersAdapter { 18 | private final ActivityMonitor activityMonitor; 19 | 20 | public RegisterRequestFilter(HttpRequest originalRequest, ChannelHandlerContext ctx, ActivityMonitor activityMonitor) { 21 | super(originalRequest, ctx); 22 | 23 | this.activityMonitor = activityMonitor; 24 | } 25 | 26 | @Override 27 | public HttpResponse clientToProxyRequest(HttpObject httpObject) { 28 | if (httpObject instanceof HttpRequest) { 29 | activityMonitor.requestStarted(); 30 | } 31 | 32 | return super.clientToProxyRequest(httpObject); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/filters/RequestFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.filters; 6 | 7 | import io.netty.handler.codec.http.HttpRequest; 8 | import io.netty.handler.codec.http.HttpResponse; 9 | import com.browserup.bup.util.HttpMessageContents; 10 | import com.browserup.bup.util.HttpMessageInfo; 11 | 12 | /** 13 | * A functional interface to simplify modification and manipulation of requests. 14 | */ 15 | public interface RequestFilter { 16 | /** 17 | * Implement this method to filter an HTTP request. The HTTP method, URI, headers, etc. are available in the {@code request} parameter, 18 | * while the contents of the message are available in the {@code contents} parameter. The request can be modified directly, while the 19 | * contents may be modified using the {@link HttpMessageContents#setTextContents(String)} or {@link HttpMessageContents#setBinaryContents(byte[])} 20 | * methods. The request can be "short-circuited" by returning a non-null value. 21 | * 22 | * @param request The request object, including method, URI, headers, etc. Modifications to the request object will be reflected in the request sent to the server. 23 | * @param contents The request contents. 24 | * @param messageInfo Additional information relating to the HTTP message. 25 | * @return if the return value is non-null, the proxy will suppress the request and send the specified response to the client immediately 26 | */ 27 | HttpResponse filterRequest(HttpRequest request, HttpMessageContents contents, HttpMessageInfo messageInfo); 28 | } 29 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/filters/ResponseFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.filters; 6 | 7 | import io.netty.handler.codec.http.HttpResponse; 8 | import com.browserup.bup.util.HttpMessageContents; 9 | import com.browserup.bup.util.HttpMessageInfo; 10 | 11 | /** 12 | * A functional interface to simplify modification and manipulation of responses. 13 | */ 14 | public interface ResponseFilter { 15 | /** 16 | * Implement this method to filter an HTTP response. The URI, headers, status line, etc. are available in the {@code response} parameter, 17 | * while the contents of the message are available in the {@code contents} parameter. The response can be modified directly, while the 18 | * contents may be modified using the {@link HttpMessageContents#setTextContents(String)} or {@link HttpMessageContents#setBinaryContents(byte[])} 19 | * methods. 20 | * 21 | * @param response The response object, including URI, headers, status line, etc. Modifications to the response object will be reflected in the client response. 22 | * @param contents The response contents. 23 | * @param messageInfo Additional information relating to the HTTP message. 24 | */ 25 | void filterResponse(HttpResponse response, HttpMessageContents contents, HttpMessageInfo messageInfo); 26 | } 27 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/filters/UnregisterRequestFilter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.filters; 6 | 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.handler.codec.http.HttpObject; 9 | import io.netty.handler.codec.http.HttpRequest; 10 | import io.netty.handler.codec.http.LastHttpContent; 11 | import com.browserup.bup.proxy.ActivityMonitor; 12 | import org.littleshoot.proxy.HttpFiltersAdapter; 13 | 14 | /** 15 | * Unregisters this request with the {@link com.browserup.bup.proxy.ActivityMonitor} when the LastHttpContent is sent to the client. 16 | */ 17 | public class UnregisterRequestFilter extends HttpFiltersAdapter { 18 | private final ActivityMonitor activityMonitor; 19 | 20 | public UnregisterRequestFilter(HttpRequest originalRequest, ChannelHandlerContext ctx, ActivityMonitor activityMonitor) { 21 | super(originalRequest, ctx); 22 | 23 | this.activityMonitor = activityMonitor; 24 | } 25 | 26 | @Override 27 | public HttpObject proxyToClientResponse(HttpObject httpObject) { 28 | if (httpObject instanceof LastHttpContent) { 29 | activityMonitor.requestFinished(); 30 | } 31 | 32 | return super.proxyToClientResponse(httpObject); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/mitmproxy/NetworkUtils.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.mitmproxy; 2 | 3 | import com.browserup.bup.filters.HttpConnectHarCaptureFilter; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.io.IOException; 8 | import java.net.DatagramSocket; 9 | import java.net.ServerSocket; 10 | 11 | public class NetworkUtils { 12 | private static final Logger log = LoggerFactory 13 | .getLogger(HttpConnectHarCaptureFilter.class); 14 | 15 | public static boolean isPortAvailable(int port) { 16 | try (ServerSocket ss = new ServerSocket(port); DatagramSocket ds = new DatagramSocket(port)) { 17 | return true; 18 | } catch (IOException e) { 19 | return false; 20 | } 21 | } 22 | 23 | public static int getFreePort() { 24 | ServerSocket s = null; 25 | try { 26 | s = new ServerSocket(0); 27 | } catch (IOException e) { 28 | throw new RuntimeException("Couldn't find free port", e); 29 | } finally { 30 | if (s != null) { 31 | try { 32 | s.close(); 33 | } catch (IOException e) { 34 | // 35 | } 36 | } 37 | } 38 | return s.getLocalPort(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/mitmproxy/addons/AdditionalHeadersAddOn.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.mitmproxy.addons; 2 | 3 | public class AdditionalHeadersAddOn extends AbstractAddon { 4 | private static final String ADDITIONAL_HEADERS_ADDON_FILE = "additional_headers.py"; 5 | 6 | @Override 7 | public String[] getCommandParams() { 8 | return new String[]{ 9 | "-s", getAddOnFilePath() 10 | }; 11 | } 12 | 13 | @Override 14 | public String getAddOnFileName() { 15 | return ADDITIONAL_HEADERS_ADDON_FILE; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/mitmproxy/addons/AddonsManagerAddOn.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.mitmproxy.addons; 2 | 3 | public class AddonsManagerAddOn extends AbstractAddon { 4 | private static final String HAR_DUMP_ADD_ON_FILE_NAME = "bu_addons_manager.py"; 5 | private final int port; 6 | 7 | public AddonsManagerAddOn(int port) { 8 | this.port = port; 9 | } 10 | 11 | @Override 12 | public String[] getCommandParams() { 13 | return new String[]{ 14 | "-s", getAddOnFilePath(), 15 | "--set", "addons_management_port=" + port 16 | }; 17 | } 18 | 19 | @Override 20 | public String getAddOnFileName() { 21 | return HAR_DUMP_ADD_ON_FILE_NAME; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/mitmproxy/addons/AllowListAddOn.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.mitmproxy.addons; 2 | 3 | public class AllowListAddOn extends AbstractAddon { 4 | private static final String ALLOW_LIST_ADDON_FILE = "allow_list.py"; 5 | 6 | @Override 7 | public String[] getCommandParams() { 8 | return new String[]{ 9 | "-s", getAddOnFilePath() 10 | }; 11 | } 12 | 13 | @Override 14 | public String getAddOnFileName() { 15 | return ALLOW_LIST_ADDON_FILE; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/mitmproxy/addons/AuthBasicAddOn.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.mitmproxy.addons; 2 | 3 | public class AuthBasicAddOn extends AbstractAddon { 4 | private static final String ALLOW_LIST_ADDON_FILE = "auth_basic.py"; 5 | 6 | @Override 7 | public String[] getCommandParams() { 8 | return new String[]{ 9 | "-s", getAddOnFilePath() 10 | }; 11 | } 12 | 13 | @Override 14 | public String getAddOnFileName() { 15 | return ALLOW_LIST_ADDON_FILE; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/mitmproxy/addons/BlockListAddOn.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.mitmproxy.addons; 2 | 3 | public class BlockListAddOn extends AbstractAddon { 4 | private static final String BLOCK_LIST_ADDON_FILE = "block_list.py"; 5 | 6 | @Override 7 | public String[] getCommandParams() { 8 | return new String[]{ 9 | "-s", getAddOnFilePath() 10 | }; 11 | } 12 | 13 | @Override 14 | public String getAddOnFileName() { 15 | return BLOCK_LIST_ADDON_FILE; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/mitmproxy/addons/HarCaptureAddOn.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.mitmproxy.addons; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.Optional; 6 | 7 | public class HarCaptureAddOn extends AbstractAddon { 8 | private static final String HAR_DUMP_ADD_ON_FILE_NAME = "har_dump.py"; 9 | 10 | private File harDumpFile = null; 11 | 12 | @Override 13 | public String[] getCommandParams() { 14 | return new String[]{ 15 | "-s", getAddOnFilePath() 16 | }; 17 | } 18 | 19 | @Override 20 | public String getAddOnFileName() { 21 | return HAR_DUMP_ADD_ON_FILE_NAME; 22 | } 23 | 24 | public Optional getHarDumpFile() { 25 | 26 | return Optional.ofNullable(harDumpFile); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/mitmproxy/addons/HttpConnectCaptureAddOn.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.mitmproxy.addons; 2 | 3 | public class HttpConnectCaptureAddOn extends AbstractAddon { 4 | private static final String HTTP_CONNECT_CAPTURE_ADDON_FILE = "http_connect_capture.py"; 5 | 6 | @Override 7 | public String[] getCommandParams() { 8 | return new String[]{ 9 | "-s", getAddOnFilePath() 10 | }; 11 | } 12 | 13 | @Override 14 | public String getAddOnFileName() { 15 | return HTTP_CONNECT_CAPTURE_ADDON_FILE; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/mitmproxy/addons/InitFlowAddOn.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.mitmproxy.addons; 2 | 3 | public class InitFlowAddOn extends AbstractAddon { 4 | private static final String INIT_FLOW_ADDON_FILE = "init_flow.py"; 5 | 6 | @Override 7 | public String[] getCommandParams() { 8 | return new String[]{ 9 | "-s", getAddOnFilePath() 10 | }; 11 | } 12 | 13 | @Override 14 | public String getAddOnFileName() { 15 | return INIT_FLOW_ADDON_FILE; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/mitmproxy/addons/LatencyAddOn.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.mitmproxy.addons; 2 | 3 | public class LatencyAddOn extends AbstractAddon { 4 | private static final String ADDITIONAL_HEADERS_ADDON_FILE = "latency.py"; 5 | 6 | @Override 7 | public String[] getCommandParams() { 8 | return new String[]{ 9 | "-s", getAddOnFilePath() 10 | }; 11 | } 12 | 13 | @Override 14 | public String getAddOnFileName() { 15 | return ADDITIONAL_HEADERS_ADDON_FILE; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/mitmproxy/addons/ProxyManagerAddOn.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.mitmproxy.addons; 2 | 3 | import java.io.File; 4 | import java.util.Optional; 5 | 6 | public class ProxyManagerAddOn extends AbstractAddon { 7 | private static final String PROXY_MANAGER_ADDON_FILE = "proxy_manager.py"; 8 | 9 | @Override 10 | public String[] getCommandParams() { 11 | return new String[]{ 12 | "-s", getAddOnFilePath() 13 | }; 14 | } 15 | 16 | @Override 17 | public String getAddOnFileName() { 18 | return PROXY_MANAGER_ADDON_FILE; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/mitmproxy/addons/RewriteUrlAddOn.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.mitmproxy.addons; 2 | 3 | public class RewriteUrlAddOn extends AbstractAddon { 4 | private static final String REWRITE_URL_ADDON_FILE = "rewrite_url.py"; 5 | 6 | @Override 7 | public String[] getCommandParams() { 8 | return new String[]{ 9 | "-s", getAddOnFilePath() 10 | }; 11 | } 12 | 13 | @Override 14 | public String getAddOnFileName() { 15 | return REWRITE_URL_ADDON_FILE; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/mitmproxy/management/LatencyManager.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.mitmproxy.management; 2 | 3 | import com.browserup.bup.mitmproxy.MitmProxyProcessManager; 4 | import org.apache.commons.lang3.tuple.Pair; 5 | 6 | import java.util.ArrayList; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | import static java.lang.String.valueOf; 10 | import static org.apache.commons.lang3.tuple.Pair.of; 11 | 12 | public class LatencyManager { 13 | private final AddonsManagerClient addonsManagerClient; 14 | private final MitmProxyProcessManager mitmProxyManager; 15 | 16 | private volatile int latencyMs; 17 | 18 | public LatencyManager(AddonsManagerClient addonsManagerClient, MitmProxyProcessManager mitmProxyManager) { 19 | this.addonsManagerClient = addonsManagerClient; 20 | this.mitmProxyManager = mitmProxyManager; 21 | } 22 | 23 | public void setLatency(long latency, TimeUnit timeUnit) { 24 | this.latencyMs = (int) TimeUnit.MILLISECONDS.convert(latency, timeUnit); 25 | 26 | if (!mitmProxyManager.isRunning()) return; 27 | 28 | addonsManagerClient. 29 | getRequestToAddonsManager( 30 | "latency", 31 | "set_latency", 32 | new ArrayList>() {{ 33 | add(of("latency", valueOf(latencyMs))); 34 | }}, 35 | Void.class); 36 | } 37 | 38 | public int getLatencyMs() { 39 | return latencyMs; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/proxy/RewriteRule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.proxy; 6 | 7 | import java.util.regex.Pattern; 8 | 9 | /** 10 | * Container for a URL rewrite rule pattern and replacement string. 11 | */ 12 | public class RewriteRule { 13 | private final Pattern pattern; 14 | private final String replace; 15 | 16 | public RewriteRule(String pattern, String replace) { 17 | this.pattern = Pattern.compile(pattern); 18 | this.replace = replace; 19 | } 20 | 21 | public Pattern getPattern() { 22 | return pattern; 23 | } 24 | 25 | public String getReplace() { 26 | return replace; 27 | } 28 | 29 | @Override 30 | public boolean equals(Object o) { 31 | if (this == o) return true; 32 | if (o == null || getClass() != o.getClass()) return false; 33 | 34 | RewriteRule that = (RewriteRule) o; 35 | 36 | if (!pattern.equals(that.pattern)) return false; 37 | if (!replace.equals(that.replace)) return false; 38 | 39 | return true; 40 | } 41 | 42 | @Override 43 | public int hashCode() { 44 | int result = pattern.hashCode(); 45 | result = 31 * result + replace.hashCode(); 46 | return result; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/proxy/auth/AuthType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.proxy.auth; 6 | 7 | /** 8 | * Authentication types support by BrowserUpProxy. 9 | */ 10 | public enum AuthType { 11 | BASIC, 12 | // TODO: determine if we can actually do NTLM authentication 13 | NTLM 14 | } 15 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/proxy/dns/DelegatingHostResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.proxy.dns; 6 | 7 | import com.google.common.collect.Iterables; 8 | 9 | import java.net.InetAddress; 10 | import java.net.InetSocketAddress; 11 | import java.net.UnknownHostException; 12 | import java.util.Collection; 13 | 14 | /** 15 | * A LittleProxy HostResolver that delegates to the specified {@link com.browserup.bup.proxy.dns.AdvancedHostResolver} instance. This class 16 | * serves as a bridge between {@link AdvancedHostResolver} and {@link org.littleshoot.proxy.HostResolver}. 17 | */ 18 | public class DelegatingHostResolver implements org.littleshoot.proxy.HostResolver { 19 | private volatile AdvancedHostResolver resolver; 20 | 21 | /** 22 | * Creates a new resolver that will delegate to the specified resolver. 23 | * 24 | * @param resolver HostResolver to delegate to 25 | */ 26 | public DelegatingHostResolver(AdvancedHostResolver resolver) { 27 | this.resolver = resolver; 28 | } 29 | 30 | public AdvancedHostResolver getResolver() { 31 | return resolver; 32 | } 33 | 34 | public void setResolver(AdvancedHostResolver resolver) { 35 | this.resolver = resolver; 36 | } 37 | 38 | @Override 39 | public InetSocketAddress resolve(String host, int port) throws UnknownHostException { 40 | Collection resolvedAddresses = resolver.resolve(host); 41 | if (!resolvedAddresses.isEmpty()) { 42 | InetAddress resolvedAddress = Iterables.get(resolvedAddresses, 0); 43 | return new InetSocketAddress(resolvedAddress, port); 44 | } 45 | 46 | // no address found by the resolver 47 | throw new UnknownHostException(host); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/proxy/dns/HostResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.proxy.dns; 6 | 7 | import com.browserup.bup.BrowserUpProxy; 8 | 9 | import java.net.InetAddress; 10 | import java.util.Collection; 11 | 12 | /** 13 | * Defines the basic functionality that {@link BrowserUpProxy} implementations require when resolving hostnames. 14 | */ 15 | public interface HostResolver { 16 | /** 17 | * Resolves a hostname to one or more IP addresses. The iterator over the returned Collection is recommended to reflect the ordering 18 | * returned by the underlying name lookup service. For example, if a DNS server returns three IP addresses, 1.1.1.1, 2.2.2.2, and 19 | * 3.3.3.3, corresponding to www.somehost.com, the returned Collection iterator is recommended to iterate in 20 | * the order [1.1.1.1, 2.2.2.2, 3.3.3.3]. 21 | * 22 | * @param host host to resolve 23 | * @return resolved InetAddresses, or an empty collection if no addresses were found 24 | */ 25 | Collection resolve(String host); 26 | } 27 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/bup/util/HttpStatusClass.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.util; 2 | 3 | public enum HttpStatusClass { 4 | INFORMATIONAL(100, 200, "Informational"), 5 | SUCCESS(200, 300, "Success"), 6 | REDIRECTION(300, 400, "Redirection"), 7 | CLIENT_ERROR(400, 500, "Client Error"), 8 | SERVER_ERROR(500, 600, "Server Error"), 9 | UNKNOWN(0, 0, "Unknown Status") { 10 | @Override 11 | public boolean contains(int code) { 12 | return code < 100 || code >= 600; 13 | } 14 | }; 15 | 16 | public static HttpStatusClass valueOf(int code) { 17 | if (INFORMATIONAL.contains(code)) { 18 | return INFORMATIONAL; 19 | } 20 | if (SUCCESS.contains(code)) { 21 | return SUCCESS; 22 | } 23 | if (REDIRECTION.contains(code)) { 24 | return REDIRECTION; 25 | } 26 | if (CLIENT_ERROR.contains(code)) { 27 | return CLIENT_ERROR; 28 | } 29 | if (SERVER_ERROR.contains(code)) { 30 | return SERVER_ERROR; 31 | } 32 | return UNKNOWN; 33 | } 34 | 35 | private final int min; 36 | private final int max; 37 | private final String description; 38 | 39 | HttpStatusClass(int min, int max, String description) { 40 | this.min = min; 41 | this.max = max; 42 | this.description = description; 43 | } 44 | 45 | public boolean contains(int code) { 46 | return code >= min && code < max; 47 | } 48 | 49 | public int getMin() { 50 | return min; 51 | } 52 | 53 | public int getMax() { 54 | return max; 55 | } 56 | 57 | public String getDescription() { 58 | return description; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/harreader/HarReader.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.browserup.harreader.jackson.DefaultMapperFactory; 5 | import com.browserup.harreader.jackson.MapperFactory; 6 | import com.browserup.harreader.model.Har; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | 11 | public class HarReader { 12 | 13 | private final MapperFactory mapperFactory; 14 | 15 | public HarReader(MapperFactory mapperFactory) { 16 | if (mapperFactory == null) { 17 | throw new IllegalArgumentException("mapperFactory must not be null!"); 18 | } 19 | this.mapperFactory = mapperFactory; 20 | } 21 | 22 | public HarReader() { 23 | this(new DefaultMapperFactory()); 24 | } 25 | 26 | public Har readFromFile(File har) throws HarReaderException { 27 | return this.readFromFile(har, HarReaderMode.STRICT); 28 | } 29 | 30 | public Har readFromFile(File har, HarReaderMode mode) throws HarReaderException { 31 | ObjectMapper mapper = mapperFactory.instance(mode); 32 | try { 33 | return mapper.readValue(har, Har.class); 34 | } catch (IOException e) { 35 | throw new HarReaderException(e); 36 | } 37 | } 38 | 39 | public Har readFromString(String har) throws HarReaderException { 40 | return this.readFromString(har, HarReaderMode.STRICT); 41 | } 42 | 43 | public Har readFromString(String har, HarReaderMode mode) throws HarReaderException { 44 | ObjectMapper mapper = mapperFactory.instance(mode); 45 | try { 46 | return mapper.readValue(har, Har.class); 47 | } catch (IOException e) { 48 | throw new HarReaderException(e); 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/harreader/HarReaderException.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader; 2 | 3 | public class HarReaderException extends Exception { 4 | 5 | public HarReaderException(Throwable cause) { 6 | super(cause); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/harreader/HarReaderMode.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader; 2 | 3 | public enum HarReaderMode { 4 | 5 | /** 6 | * Using strict mode enforces some rules. 7 | * When trying to open an invalid HAR file an exception will be thrown. 8 | */ 9 | STRICT, 10 | 11 | /** 12 | * Using lax mode you are able to read even invalid HAR files. 13 | * Currently lax mode allows: 14 | *
    15 | *
  • invalid date formats
  • 16 | *
17 | */ 18 | LAX; 19 | } 20 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/harreader/LICENSE.MD: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Sebastian Stöhr, 2019 BrowserUp 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/harreader/README.md: -------------------------------------------------------------------------------- 1 | The har reader is included via MIT license. 2 | 3 | Original source: https://github.com/sdstoehr/har-reader 4 | 5 | Additions by BrowserUp are governed by the Apache License 2.0. 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/harreader/filter/HarEntriesFilter.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader.filter; 2 | 3 | import com.browserup.harreader.model.HarEntry; 4 | import java.util.function.Predicate; 5 | 6 | public interface HarEntriesFilter extends Predicate { 7 | 8 | @Override 9 | boolean test(HarEntry entry); 10 | } 11 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/harreader/filter/HarEntriesUrlPatternFilter.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader.filter; 2 | 3 | import com.browserup.harreader.model.HarEntry; 4 | import java.util.regex.Pattern; 5 | 6 | public class HarEntriesUrlPatternFilter implements HarEntriesFilter { 7 | 8 | private final Pattern pattern; 9 | 10 | public HarEntriesUrlPatternFilter(Pattern pattern) { 11 | this.pattern = pattern; 12 | } 13 | 14 | @Override 15 | public boolean test(HarEntry entry) { 16 | return pattern.matcher(entry.getRequest().getUrl()).matches(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/harreader/jackson/DefaultMapperFactory.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader.jackson; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fasterxml.jackson.databind.module.SimpleModule; 5 | import com.browserup.harreader.HarReaderMode; 6 | 7 | import java.util.Date; 8 | 9 | public class DefaultMapperFactory implements MapperFactory { 10 | 11 | public ObjectMapper instance(HarReaderMode mode) { 12 | ObjectMapper mapper = new ObjectMapper(); 13 | SimpleModule module = new SimpleModule(); 14 | if (mode == HarReaderMode.LAX) { 15 | module.addDeserializer(Date.class, new ExceptionIgnoringDateDeserializer()); 16 | module.addDeserializer(Integer.class, new ExceptionIgnoringIntegerDeserializer()); 17 | } 18 | mapper.registerModule(module); 19 | return mapper; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/harreader/jackson/ExceptionIgnoringDateDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader.jackson; 2 | 3 | import java.io.IOException; 4 | import java.util.Date; 5 | 6 | import com.fasterxml.jackson.core.JsonParser; 7 | import com.fasterxml.jackson.databind.DeserializationContext; 8 | import com.fasterxml.jackson.databind.JsonDeserializer; 9 | import com.fasterxml.jackson.databind.deser.std.DateDeserializers; 10 | 11 | public class ExceptionIgnoringDateDeserializer extends JsonDeserializer { 12 | 13 | @Override 14 | public Date deserialize(JsonParser jp, DeserializationContext ctxt) throws java.io.IOException { 15 | try { 16 | DateDeserializers.DateDeserializer dateDeserializer = new DateDeserializers.DateDeserializer(); 17 | return dateDeserializer.deserialize(jp, ctxt); 18 | } catch (IOException e) { 19 | //ignore 20 | } 21 | return new Date(0L); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/harreader/jackson/ExceptionIgnoringIntegerDeserializer.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader.jackson; 2 | 3 | import com.fasterxml.jackson.core.JsonParser; 4 | import com.fasterxml.jackson.databind.DeserializationContext; 5 | import com.fasterxml.jackson.databind.JsonDeserializer; 6 | import com.fasterxml.jackson.databind.deser.std.NumberDeserializers; 7 | 8 | import java.io.IOException; 9 | 10 | public class ExceptionIgnoringIntegerDeserializer extends JsonDeserializer { 11 | @Override 12 | public Integer deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { 13 | try { 14 | NumberDeserializers.IntegerDeserializer integerDeserializer = new NumberDeserializers.IntegerDeserializer(Integer.class, null); 15 | return integerDeserializer.deserialize(jp, ctxt); 16 | } catch (IOException e) { 17 | //ignore 18 | } 19 | return null; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/harreader/jackson/MapperFactory.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader.jackson; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.browserup.harreader.HarReaderMode; 5 | 6 | public interface MapperFactory { 7 | 8 | ObjectMapper instance(HarReaderMode mode); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/harreader/model/Har.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonInclude; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.io.OutputStream; 10 | import java.io.Writer; 11 | import java.util.Objects; 12 | 13 | /** 14 | * Main HTTP Archive Class. 15 | * @see speicification 16 | */ 17 | @JsonInclude(JsonInclude.Include.NON_NULL) 18 | @JsonIgnoreProperties(ignoreUnknown = true) 19 | public class Har { 20 | 21 | private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); 22 | 23 | private HarLog log; 24 | 25 | /** 26 | * @return HAR log. 27 | */ 28 | public HarLog getLog() { 29 | if (log == null) { 30 | log = new HarLog(); 31 | } 32 | return log; 33 | } 34 | 35 | public void setLog(HarLog log) { 36 | this.log = log; 37 | } 38 | 39 | @Override 40 | public boolean equals(Object o) { 41 | if (this == o) return true; 42 | if (o == null || getClass() != o.getClass()) return false; 43 | Har har = (Har) o; 44 | return Objects.equals(log, har.log); 45 | } 46 | 47 | @Override 48 | public int hashCode() { 49 | return Objects.hash(log); 50 | } 51 | 52 | public void writeTo(Writer writer) throws IOException { 53 | OBJECT_MAPPER.writeValue(writer, this); 54 | } 55 | 56 | public void writeTo(OutputStream os) throws IOException { 57 | OBJECT_MAPPER.writeValue(os, this); 58 | } 59 | 60 | public void writeTo(File file) throws IOException { 61 | OBJECT_MAPPER.writeValue(file, this); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/harreader/model/HarHeader.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonInclude; 5 | 6 | import java.util.Objects; 7 | 8 | /** 9 | * Information about a header used in request and/or response. 10 | * @see specification 11 | */ 12 | @JsonInclude(JsonInclude.Include.NON_NULL) 13 | @JsonIgnoreProperties(ignoreUnknown = true) 14 | public class HarHeader { 15 | 16 | private String name; 17 | private String value; 18 | private String comment; 19 | 20 | /** 21 | * @return Header name, null if not present. 22 | */ 23 | public String getName() { 24 | return name; 25 | } 26 | 27 | public void setName(String name) { 28 | this.name = name; 29 | } 30 | 31 | /** 32 | * @return Header value, null if not present. 33 | */ 34 | public String getValue() { 35 | return value; 36 | } 37 | 38 | public void setValue(String value) { 39 | this.value = value; 40 | } 41 | 42 | /** 43 | * @return Comment provided by the user or application, null if not present. 44 | */ 45 | public String getComment() { 46 | return comment; 47 | } 48 | 49 | public void setComment(String comment) { 50 | this.comment = comment; 51 | } 52 | 53 | @Override 54 | public boolean equals(Object o) { 55 | if (this == o) return true; 56 | if (o == null || getClass() != o.getClass()) return false; 57 | HarHeader harHeader = (HarHeader) o; 58 | return Objects.equals(name, harHeader.name) && 59 | Objects.equals(value, harHeader.value) && 60 | Objects.equals(comment, harHeader.comment); 61 | } 62 | 63 | @Override 64 | public int hashCode() { 65 | return Objects.hash(name, value, comment); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/harreader/model/HarQueryParam.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonInclude; 5 | 6 | import java.util.Objects; 7 | 8 | /** 9 | * Information about query params. 10 | * @see specification 11 | */ 12 | @JsonInclude(JsonInclude.Include.NON_NULL) 13 | @JsonIgnoreProperties(ignoreUnknown = true) 14 | public class HarQueryParam { 15 | 16 | private String name; 17 | private String value; 18 | private String comment; 19 | 20 | /** 21 | * @return Name of param, null if not present. 22 | */ 23 | public String getName() { 24 | return name; 25 | } 26 | 27 | public void setName(String name) { 28 | this.name = name; 29 | } 30 | 31 | /** 32 | * @return Value of param, null if not present. 33 | */ 34 | public String getValue() { 35 | return value; 36 | } 37 | 38 | public void setValue(String value) { 39 | this.value = value; 40 | } 41 | 42 | /** 43 | * @return Comment provided by the user or application, null if not present. 44 | */ 45 | public String getComment() { 46 | return comment; 47 | } 48 | 49 | public void setComment(String comment) { 50 | this.comment = comment; 51 | } 52 | 53 | @Override 54 | public boolean equals(Object o) { 55 | if (this == o) return true; 56 | if (o == null || getClass() != o.getClass()) return false; 57 | HarQueryParam that = (HarQueryParam) o; 58 | return Objects.equals(name, that.name) && 59 | Objects.equals(value, that.value) && 60 | Objects.equals(comment, that.comment); 61 | } 62 | 63 | @Override 64 | public int hashCode() { 65 | return Objects.hash(name, value, comment); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/java/com/browserup/harreader/model/HttpMethod.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader.model; 2 | 3 | public enum HttpMethod { 4 | GET, POST, PUT, HEAD, PROPFIND, OPTIONS, REPORT, DELETE, CONNECT, TRACE, CCM_POST, PATCH; 5 | } 6 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/resources/com/browserup/bup/version: -------------------------------------------------------------------------------- 1 | ${project.version} -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/resources/mitmproxy/additional_headers.py: -------------------------------------------------------------------------------- 1 | import json 2 | import base64 3 | import typing 4 | import tempfile 5 | 6 | import re 7 | 8 | from datetime import datetime 9 | from datetime import timezone 10 | 11 | import falcon 12 | 13 | from mitmproxy import ctx 14 | 15 | from mitmproxy import connections 16 | from mitmproxy import version 17 | from mitmproxy.utils import strutils 18 | from mitmproxy.net.http import cookies 19 | from mitmproxy import http 20 | 21 | class AddHeadersResource: 22 | 23 | def addon_path(self): 24 | return "additional_headers" 25 | 26 | def __init__(self, additional_headers_addon): 27 | self.additional_headers_addon = additional_headers_addon 28 | 29 | def on_get(self, req, resp, method_name): 30 | getattr(self, "on_" + method_name)(req, resp) 31 | 32 | def on_add_headers(self, req, resp): 33 | for k, v in req.params.items(): 34 | self.additional_headers_addon.headers[k] = v 35 | 36 | 37 | def on_add_header(self, req, resp): 38 | for k, v in req.params.items(): 39 | self.additional_headers_addon.headers[k] = v 40 | 41 | def on_remove_header(self, req, resp): 42 | self.additional_headers_addon.headers.pop(req.get_param('name')) 43 | 44 | def on_remove_all_headers(self, req, resp): 45 | self.additional_headers_addon.headers = {} 46 | 47 | class AddHeadersAddOn: 48 | 49 | def __init__(self): 50 | self.num = 0 51 | self.headers = {} 52 | 53 | def get_resource(self): 54 | return AddHeadersResource(self) 55 | 56 | def request(self, flow): 57 | for k, v in self.headers.items(): 58 | flow.request.headers[k] = v 59 | 60 | 61 | addons = [ 62 | AddHeadersAddOn() 63 | ] 64 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/resources/mitmproxy/auth_basic.py: -------------------------------------------------------------------------------- 1 | import json 2 | import base64 3 | import typing 4 | import tempfile 5 | 6 | import re 7 | 8 | from datetime import datetime 9 | from datetime import timezone 10 | 11 | import falcon 12 | 13 | from mitmproxy import ctx 14 | 15 | from mitmproxy import connections 16 | from mitmproxy import version 17 | from mitmproxy.utils import strutils 18 | from mitmproxy.net.http import cookies 19 | from mitmproxy import http 20 | 21 | class AuthBasicResource: 22 | 23 | def addon_path(self): 24 | return "auth_basic" 25 | 26 | def __init__(self, auth_basic_addon): 27 | self.auth_basic_addon = auth_basic_addon 28 | 29 | def on_get(self, req, resp, method_name): 30 | getattr(self, "on_" + method_name)(req, resp) 31 | 32 | def on_auth_authorization(self, req, resp): 33 | credentials = req.get_param('base64EncodedCredentials') 34 | domain = req.get_param('domain') 35 | self.auth_basic_addon.credentials_map[domain] = credentials 36 | 37 | def on_stop_authorization(self, req, resp): 38 | domain = req.get_param('domain') 39 | self.auth_basic_addon.credentials_map.pop(domain) 40 | 41 | class AuthBasicAddOn: 42 | 43 | def __init__(self): 44 | self.num = 0 45 | self.credentials_map = {} 46 | 47 | def get_resource(self): 48 | return AuthBasicResource(self) 49 | 50 | def request(self, flow): 51 | if flow.request.host in self.credentials_map: 52 | flow.request.headers['Authorization'] = 'Basic ' + self.credentials_map[flow.request.host] 53 | 54 | 55 | addons = [ 56 | AuthBasicAddOn() 57 | ] 58 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/resources/mitmproxy/init_flow.py: -------------------------------------------------------------------------------- 1 | import json 2 | import base64 3 | import typing 4 | import tempfile 5 | 6 | from time import sleep 7 | 8 | import re 9 | 10 | from datetime import datetime 11 | from datetime import timezone 12 | 13 | import falcon 14 | 15 | from mitmproxy import ctx 16 | 17 | from mitmproxy import connections 18 | from mitmproxy import version 19 | from mitmproxy.utils import strutils 20 | from mitmproxy.net.http import cookies 21 | from mitmproxy import http 22 | 23 | class InitFlowResource: 24 | 25 | def addon_path(self): 26 | return "init_flow" 27 | 28 | def __init__(self, init_flow_addon): 29 | self.init_flow_addon = init_flow_addon 30 | for a in ctx.master.addons.get("scriptloader").addons: 31 | if 'har_dump.py' in a.fullpath: 32 | self.init_flow_addon.har_dump_addon = a.addons[0].addons[0] 33 | 34 | def on_get(self, req, resp, method_name): 35 | getattr(self, "on_" + method_name)(req, resp) 36 | 37 | 38 | class InitFlowAddOn: 39 | 40 | def __init__(self): 41 | self.num = 0 42 | self.har_dump_addon = None 43 | 44 | def get_resource(self): 45 | return InitFlowResource(self) 46 | 47 | # def http_connect(self, flow): 48 | # if not hasattr(flow.request, 'har_entry'): 49 | # self.init_har_entry(flow) 50 | 51 | def request(self, flow): 52 | if not hasattr(flow.request, 'har_entry'): 53 | self.init_har_entry(flow) 54 | 55 | def init_har_entry(self, flow): 56 | ctx.log.debug("Initializing har entry for flow request: {}".format(str(flow.request))) 57 | setattr(flow.request, 'har_entry', self.har_dump_addon.generate_har_entry(flow.request.url)) 58 | self.har_dump_addon.append_har_entry(flow.request.har_entry) 59 | 60 | addons = [ 61 | InitFlowAddOn() 62 | ] 63 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/resources/mitmproxy/latency.py: -------------------------------------------------------------------------------- 1 | import json 2 | import base64 3 | import typing 4 | import tempfile 5 | 6 | from time import sleep 7 | 8 | import re 9 | 10 | from datetime import datetime 11 | from datetime import timezone 12 | 13 | import falcon 14 | 15 | from mitmproxy import ctx 16 | 17 | from mitmproxy import connections 18 | from mitmproxy import version 19 | from mitmproxy.utils import strutils 20 | from mitmproxy.net.http import cookies 21 | from mitmproxy import http 22 | 23 | class LatencyResource: 24 | 25 | def addon_path(self): 26 | return "latency" 27 | 28 | def __init__(self, latency_addon): 29 | self.latency_addon = latency_addon 30 | 31 | def on_get(self, req, resp, method_name): 32 | getattr(self, "on_" + method_name)(req, resp) 33 | 34 | def on_set_latency(self, req, resp): 35 | self.latency_addon.latency_ms = int(req.get_param('latency')) 36 | 37 | 38 | class LatencyAddOn: 39 | 40 | def __init__(self): 41 | self.num = 0 42 | self.latency_ms = 0 43 | 44 | def get_resource(self): 45 | return LatencyResource(self) 46 | 47 | def response(self, flow): 48 | if self.latency_ms != 0: 49 | sleep(self.latency_ms / 1000) 50 | 51 | addons = [ 52 | LatencyAddOn() 53 | ] 54 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/resources/sslSupport/ca-certificate-ec.cer: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIB8jCCAZigAwIBAgIUUridrS1mqPKh8aA7igVOQRUc8P8wCgYIKoZIzj0EAwQw 3 | RjEZMBcGA1UEAwwQTGl0dGxlUHJveHkgTUlUTTEpMCcGA1UECgwgTGl0dGxlUHJv 4 | eHkgRUNDIEltcGVyc29uYXRpb24gQ0EwHhcNMTUwMTAyMDAwMDAwWhcNMjUwMTAy 5 | MDAwMDAwWjBGMRkwFwYDVQQDDBBMaXR0bGVQcm94eSBNSVRNMSkwJwYDVQQKDCBM 6 | aXR0bGVQcm94eSBFQ0MgSW1wZXJzb25hdGlvbiBDQTBZMBMGByqGSM49AgEGCCqG 7 | SM49AwEHA0IABB9DdlM/uhkMWTYFo9ETzPrMWBlfhCD0z3J2F1aH9a3OPiPYBio6 8 | fzTVSZO2rU9ItfcRRpCGeMzY+pilfUNkPXyjZDBiMB0GA1UdDgQWBBQ0TT/oOVF2 9 | mT10+X9W3NDESql7ZzAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBtjAjBgNV 10 | HSUEHDAaBggrBgEFBQcDAQYIKwYBBQUHAwIGBFUdJQAwCgYIKoZIzj0EAwQDSAAw 11 | RQIhAOb/s6H8v1XeEPGEmMdVEhRnhJgTYAktQKQLZid8QBzsAiA7zc1mFLRAKs98 12 | 5d9+qGFsv7Fy0yTNO3vFyL7DL2mykg== 13 | -----END CERTIFICATE----- 14 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/resources/sslSupport/ca-certificate-rsa.cer: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDfzCCAmegAwIBAgIVAMFQpicWi3EjPX08LgeuA8nAOEfIMA0GCSqGSIb3DQEB 3 | DQUAMEYxGTAXBgNVBAMMEExpdHRsZVByb3h5IE1JVE0xKTAnBgNVBAoMIExpdHRs 4 | ZVByb3h5IFJTQSBJbXBlcnNvbmF0aW9uIENBMB4XDTE1MDEwMjAwMDAwMFoXDTI1 5 | MDEwMjAwMDAwMFowRjEZMBcGA1UEAwwQTGl0dGxlUHJveHkgTUlUTTEpMCcGA1UE 6 | CgwgTGl0dGxlUHJveHkgUlNBIEltcGVyc29uYXRpb24gQ0EwggEiMA0GCSqGSIb3 7 | DQEBAQUAA4IBDwAwggEKAoIBAQC141M+lc046DJaNqIARozRPROGt/s5Ng1UOE84 8 | tKhd+M/REaOeNovW+42uMa4ZifJAK7Csc0dx54Iq35LXy0tMw6ly/MB0pFi+aFCJ 9 | VzXZhbAWIsUmjU8t6z2Y0sjKVX/g3HkdXqaX94jlDtsTjeQXvFhiJNRlX/Locc/f 10 | /oNYZWhg7IPGyQglRY9Dco9kZMSbh5y0yfM8002PNPbNOP4dMX4yYqovT90XbvQ2 11 | rCBbiS6Cys7j44vwOcra9srlb3YQiOCOsYCf7eIhT1GH8tqQ84CHblufqxcGIvXv 12 | V1ex6bDFy63tiPySsOwuVnZglkQ0MDl1GMKVySdPw/qQM5v9AgMBAAGjZDBiMB0G 13 | A1UdDgQWBBRFMQtpkCyZIK9NxaEJDvbfaV1QOzAPBgNVHRMBAf8EBTADAQH/MAsG 14 | A1UdDwQEAwIBtjAjBgNVHSUEHDAaBggrBgEFBQcDAQYIKwYBBQUHAwIGBFUdJQAw 15 | DQYJKoZIhvcNAQENBQADggEBAJuYv1NuxPHom579iAjs19YrFGewHpv4aZC7aWTt 16 | oC1y9418w7QzVOAz2VzluURazUdg/HS9s8abJ8IS0iD0xLz0B1cvJ6F2BezjAwyG 17 | 2LxZggmBdLqwjdRkX0Mx3a2HqUpEqaNeKyE8VmzwPuDHN1AqbFcuOPHN7fm7kAtL 18 | 4bxFmjgSt7PjEdYwysdjkLC6m+236tuFydpVkXMjuBthsk/hZ1Y/3tbCj/B9a9// 19 | 5O+HhYEy+Oa64iFvxfgDfKKUQR3VmwThj1Dh2iJw/kbPJEuQ/PtfcnQhOqyliwg6 20 | Edxd1kaO4HU8Am6TwpmpPFWHRqhM2xj2PAGyfFtN1WfBEQ4= 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/resources/sslSupport/ca-keystore-ec.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserup/browserup-proxy/0f28ec6e8f1423e1fdb014fded72e9278bdd083f/browserup-proxy-core/src/main/resources/sslSupport/ca-keystore-ec.p12 -------------------------------------------------------------------------------- /browserup-proxy-core/src/main/resources/sslSupport/ca-keystore-rsa.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserup/browserup-proxy/0f28ec6e8f1423e1fdb014fded72e9278bdd083f/browserup-proxy-core/src/main/resources/sslSupport/ca-keystore-rsa.p12 -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/groovy/com/browserup/bup/assertion/ResponseTimeLessThanOrEqualAssertionTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.assertion 6 | 7 | import com.browserup.harreader.model.HarEntry 8 | import org.junit.Assert; 9 | import org.junit.Test; 10 | 11 | class ResponseTimeLessThanOrEqualAssertionTest { 12 | 13 | @Test 14 | void testAssertionFailsIfTimeExceeds() { 15 | def expectedTime = 500 16 | def assertion = new ResponseTimeLessThanOrEqualAssertion(expectedTime) 17 | def entry = new HarEntry() 18 | def time = 1000 19 | entry.setTime(time) 20 | 21 | def result = assertion.assertion(entry) 22 | 23 | Assert.assertTrue("Expected assertion to return error", result.present) 24 | } 25 | 26 | @Test 27 | void testAssertionDoesNotFailIfTimeDoesNotExceed() { 28 | def expectedTime = 2000 29 | def assertion = new ResponseTimeLessThanOrEqualAssertion(expectedTime) 30 | def entry = new HarEntry() 31 | def time = 1000 32 | entry.setTime(time) 33 | 34 | def result = assertion.assertion(entry) 35 | 36 | Assert.assertFalse("Expected assertion not to return error", result.present) 37 | } 38 | } -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/groovy/com/browserup/bup/mitmproxy/LatencyTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitmproxy 6 | 7 | 8 | import com.browserup.bup.MitmProxyServer 9 | import com.browserup.bup.proxy.test.util.MockServerTest 10 | import com.browserup.bup.proxy.test.util.NewProxyServerTestUtil 11 | import org.apache.http.HttpResponse 12 | import org.apache.http.client.methods.HttpGet 13 | import org.junit.After 14 | import org.junit.Test 15 | 16 | import java.util.concurrent.TimeUnit 17 | 18 | import static com.github.tomakehurst.wiremock.client.WireMock.* 19 | import static org.junit.Assert.assertEquals 20 | import static org.junit.Assert.assertTrue 21 | 22 | class LatencyTest extends MockServerTest { 23 | private MitmProxyServer proxy 24 | 25 | @After 26 | void tearDown() { 27 | if (proxy?.started) { 28 | proxy.abort() 29 | } 30 | } 31 | 32 | @Test 33 | void testLatency() { 34 | String url = "/latency" 35 | stubFor(get(urlEqualTo(url)).willReturn(ok())) 36 | 37 | proxy = new MitmProxyServer() 38 | proxy.setLatency(2, TimeUnit.SECONDS) 39 | proxy.start() 40 | 41 | NewProxyServerTestUtil.getNewHttpClient(proxy.getPort()).withCloseable { 42 | long start = System.nanoTime() 43 | HttpResponse response = it.execute(new HttpGet("http://127.0.0.1:" + mockServerPort + "/latency")) 44 | long stop = System.nanoTime() 45 | 46 | assertEquals("Expected to receive an HTTP 200 from the upstream server", 200, response.getStatusLine().getStatusCode()) 47 | assertTrue("Expected latency to be at least 2 seconds. Total time was: " + TimeUnit.MILLISECONDS.convert(stop - start, TimeUnit.NANOSECONDS) + "ms", 48 | TimeUnit.SECONDS.convert(stop - start, TimeUnit.NANOSECONDS) >= 2) 49 | } 50 | 51 | verify(1, getRequestedFor(urlEqualTo(url))) 52 | } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/groovy/com/browserup/bup/proxy/assertion/field/content/ContentBaseTest.groovy: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.proxy.assertion.field.content 2 | 3 | import com.browserup.bup.proxy.CaptureType 4 | import com.browserup.bup.proxy.assertion.BaseAssertionsTest 5 | import com.github.tomakehurst.wiremock.matching.EqualToPattern 6 | import org.apache.http.HttpHeaders 7 | import org.apache.http.HttpStatus 8 | import org.eclipse.jetty.http.HttpHeader 9 | import org.junit.Before 10 | import java.util.regex.Pattern 11 | 12 | import static com.github.tomakehurst.wiremock.client.WireMock.get 13 | import static com.github.tomakehurst.wiremock.client.WireMock.ok 14 | import static com.github.tomakehurst.wiremock.client.WireMock.stubFor 15 | import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo 16 | class ContentBaseTest extends BaseAssertionsTest { 17 | protected static final String BODY_PART = 'body part' 18 | protected static final String BODY_CONTAINING_BODY_PART = "body example with ${BODY_PART} in the middle".toString() 19 | protected static final String BODY_NOT_CONTAINING_BODY_PART = 'body example' 20 | protected static final Pattern BODY_PATTERN_TO_MATCH_BODY_PART = Pattern.compile(".*${BODY_PART}.*") 21 | protected static final String BODY_PATTERN_NOT_TO_MATCH_BODY_PART = Pattern.compile(".*NOT-TO-MATCH.*") 22 | 23 | @Before 24 | void setUp() { 25 | proxy.enableHarCaptureTypes(CaptureType.RESPONSE_CONTENT, CaptureType.REQUEST_BINARY_CONTENT, CaptureType.REQUEST_CONTENT) 26 | } 27 | 28 | protected mockResponse(String path, String body) { 29 | stubFor(get(urlEqualTo("/" + path)) 30 | .willReturn(ok().withHeader(HttpHeaders.CONTENT_TYPE, "text/plain").withBody(body))) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/groovy/com/browserup/bup/proxy/assertion/field/content/ContentSizeLessThanOrEqualTest.groovy: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.proxy.assertion.field.content 2 | 3 | import org.apache.commons.lang3.StringUtils 4 | import org.junit.Test 5 | 6 | import java.util.regex.Pattern 7 | 8 | import static org.junit.Assert.assertFalse 9 | import static org.junit.Assert.assertTrue 10 | 11 | class ContentSizeLessThanOrEqualTest extends ContentBaseTest { 12 | 13 | @Test 14 | void contentSizeWithinAssertionPasses() { 15 | def body = StringUtils.repeat('a', 100) 16 | def bodySize = body.bytes.size() 17 | 18 | mockResponse(URL_PATH, body) 19 | 20 | requestToMockedServer(URL_PATH, body) 21 | 22 | def result = proxy.assertMostRecentResponseContentLengthLessThanOrEqual(Pattern.compile(".*${URL_PATH}.*"), bodySize) 23 | 24 | assertTrue("Expected assertion to pass", result.passed) 25 | assertFalse("Expected assertion to pass", result.failed) 26 | } 27 | 28 | @Test 29 | void contentSizeWithinAssertionFails() { 30 | def body = StringUtils.repeat('a', 100) 31 | def bodySize = body.bytes.size() 32 | 33 | mockResponse(URL_PATH, body) 34 | 35 | requestToMockedServer(URL_PATH, body) 36 | 37 | def result = proxy.assertMostRecentResponseContentLengthLessThanOrEqual(Pattern.compile(".*${URL_PATH}.*"), bodySize - 1) 38 | 39 | assertFalse("Expected assertion to fail", result.passed) 40 | assertTrue("Expected assertion to fail", result.failed) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/groovy/com/browserup/bup/proxy/assertion/field/content/filtered/FilteredContentBaseTest.groovy: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.proxy.assertion.field.content.filtered; 2 | 3 | import com.browserup.bup.proxy.assertion.field.content.ContentBaseTest; 4 | 5 | import java.util.regex.Pattern; 6 | 7 | class FilteredContentBaseTest extends ContentBaseTest { 8 | protected static final String FIRST_URL_PATH = "first-url-path-with-${URL_PATH}-in-the-middle" 9 | protected static final String SECOND_URL_PATH = "second-url-path-with-${URL_PATH}-in-the-middle" 10 | protected static final Pattern URL_PATTERN_TO_MATCH_BOTH = Pattern.compile(".*${URL_PATH}.*") 11 | protected static final Pattern URL_PATTERN_TO_MATCH_FIRST = Pattern.compile(".*${FIRST_URL_PATH}.*") 12 | 13 | protected mockAndSendRequestsToMockedServer(String firstBody, String secondBody) { 14 | mockResponse(FIRST_URL_PATH, firstBody) 15 | mockResponse(SECOND_URL_PATH, secondBody) 16 | 17 | requestToMockedServer(FIRST_URL_PATH, firstBody) 18 | requestToMockedServer(SECOND_URL_PATH, secondBody) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/groovy/com/browserup/bup/proxy/assertion/field/content/mostrecent/ContentContainsTest.groovy: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.proxy.assertion.field.content.mostrecent 2 | 3 | import org.junit.Test 4 | 5 | import static org.junit.Assert.assertFalse 6 | import static org.junit.Assert.assertTrue 7 | 8 | class ContentContainsTest extends MostRecentContentBaseTest { 9 | 10 | @Test 11 | void oldAndRecentContainsTextAndRecentFilterIsUsedPasses() { 12 | mockAndSendRequestsToMockedServer(BODY_CONTAINING_BODY_PART, BODY_CONTAINING_BODY_PART) 13 | 14 | def result = proxy.assertMostRecentResponseContentContains(RECENT_REQUEST_URL_PATH_PATTERN, BODY_PART) 15 | 16 | assertAssertionPassed(result) 17 | } 18 | 19 | @Test 20 | void oldAndRecentContainsTextAndOldFilterIsUsedPasses() { 21 | mockAndSendRequestsToMockedServer(BODY_CONTAINING_BODY_PART, BODY_CONTAINING_BODY_PART) 22 | 23 | def result = proxy.assertMostRecentResponseContentContains(OLD_REQUEST_URL_PATH_PATTERN, BODY_PART) 24 | 25 | assertAssertionPassed(result) 26 | } 27 | 28 | @Test 29 | void onlyOldContainsTextAndOldFilterIsUsedPasses() { 30 | mockAndSendRequestsToMockedServer(BODY_NOT_CONTAINING_BODY_PART, BODY_CONTAINING_BODY_PART) 31 | 32 | def result = proxy.assertMostRecentResponseContentContains(OLD_REQUEST_URL_PATH_PATTERN, BODY_PART) 33 | 34 | assertAssertionPassed(result) 35 | } 36 | 37 | @Test 38 | void onlyOldContainsTextAndRecentFilterIsUsedFails() { 39 | mockAndSendRequestsToMockedServer(BODY_NOT_CONTAINING_BODY_PART, BODY_CONTAINING_BODY_PART) 40 | 41 | def result = proxy.assertMostRecentResponseContentContains(RECENT_REQUEST_URL_PATH_PATTERN, BODY_PART) 42 | 43 | assertAssertionFailed(result) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/groovy/com/browserup/bup/proxy/assertion/field/content/mostrecent/ContentDoesNotContainTest.groovy: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.proxy.assertion.field.content.mostrecent 2 | 3 | import org.junit.Test 4 | 5 | import static org.junit.Assert.assertFalse 6 | import static org.junit.Assert.assertTrue 7 | 8 | class ContentDoesNotContainTest extends MostRecentContentBaseTest { 9 | 10 | @Test 11 | void oldAndRecentDoNotContainTextAndRecentFilterIsUsedPasses() { 12 | mockAndSendRequestsToMockedServer(BODY_NOT_CONTAINING_BODY_PART, BODY_NOT_CONTAINING_BODY_PART) 13 | 14 | def result = proxy.assertMostRecentResponseContentDoesNotContain(RECENT_REQUEST_URL_PATH_PATTERN, BODY_PART) 15 | 16 | assertAssertionPassed(result) 17 | } 18 | 19 | @Test 20 | void oldAndRecentDoNotContainTextAndOldFilterIsUsedPasses() { 21 | mockAndSendRequestsToMockedServer(BODY_NOT_CONTAINING_BODY_PART, BODY_NOT_CONTAINING_BODY_PART) 22 | 23 | def result = proxy.assertMostRecentResponseContentDoesNotContain(OLD_REQUEST_URL_PATH_PATTERN, BODY_PART) 24 | 25 | assertAssertionPassed(result) 26 | } 27 | 28 | @Test 29 | void onlyOldDoesNotContainTextAndOldFilterIsUsedPasses() { 30 | mockAndSendRequestsToMockedServer(BODY_NOT_CONTAINING_BODY_PART, BODY_NOT_CONTAINING_BODY_PART) 31 | 32 | def result = proxy.assertMostRecentResponseContentDoesNotContain(OLD_REQUEST_URL_PATH_PATTERN, BODY_PART) 33 | 34 | assertAssertionPassed(result) 35 | } 36 | 37 | @Test 38 | void onlyOldDoesNotContainTextAndRecentFilterIsUsedFails() { 39 | mockAndSendRequestsToMockedServer(BODY_CONTAINING_BODY_PART, BODY_NOT_CONTAINING_BODY_PART) 40 | 41 | def result = proxy.assertMostRecentResponseContentDoesNotContain(RECENT_REQUEST_URL_PATH_PATTERN, BODY_PART) 42 | 43 | assertAssertionFailed(result) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/groovy/com/browserup/bup/proxy/assertion/field/content/mostrecent/MostRecentContentBaseTest.groovy: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.proxy.assertion.field.content.mostrecent 2 | 3 | import com.browserup.bup.proxy.assertion.field.content.ContentBaseTest 4 | 5 | import java.util.regex.Pattern 6 | 7 | class MostRecentContentBaseTest extends ContentBaseTest { 8 | protected static final String RECENT_REQUEST_URL_PATH = "recent-${URL_PATH}" 9 | protected static final Pattern RECENT_REQUEST_URL_PATH_PATTERN = Pattern.compile(".*${RECENT_REQUEST_URL_PATH}.*") 10 | protected static final String OLD_REQUEST_URL_PATH = "old-${URL_PATH}" 11 | protected static final Pattern OLD_REQUEST_URL_PATH_PATTERN = Pattern.compile(".*${OLD_REQUEST_URL_PATH}.*") 12 | protected static final Integer DELAY_BETWEEN_REQUESTS = 50 13 | protected static final String RECENT_BODY = "recent body" 14 | protected static final String OLD_BODY = 'old body' 15 | protected static final Pattern BODY_PATTERN_TO_MATCH_RECENT = Pattern.compile(".*${RECENT_BODY}.*") 16 | protected static final Pattern BODY_PATTERN_TO_MATCH_OLD = Pattern.compile(".*${OLD_BODY}.*") 17 | 18 | protected mockAndSendRequestsToMockedServer(String recentBody, String oldBody) { 19 | mockResponse(RECENT_REQUEST_URL_PATH, recentBody) 20 | mockResponse(OLD_REQUEST_URL_PATH, oldBody) 21 | 22 | requestToMockedServer(OLD_REQUEST_URL_PATH, oldBody) 23 | sleep DELAY_BETWEEN_REQUESTS 24 | requestToMockedServer(RECENT_REQUEST_URL_PATH, recentBody) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/groovy/com/browserup/bup/proxy/assertion/field/header/HeaderBaseTest.groovy: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.proxy.assertion.field.header 2 | 3 | import com.browserup.bup.proxy.CaptureType 4 | import com.browserup.bup.proxy.assertion.BaseAssertionsTest 5 | import com.github.tomakehurst.wiremock.http.HttpHeader 6 | import com.github.tomakehurst.wiremock.http.HttpHeaders 7 | import org.apache.http.HttpStatus 8 | import org.junit.Before 9 | 10 | import static com.github.tomakehurst.wiremock.client.WireMock.get 11 | import static com.github.tomakehurst.wiremock.client.WireMock.ok 12 | import static com.github.tomakehurst.wiremock.client.WireMock.stubFor 13 | import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo 14 | 15 | class HeaderBaseTest extends BaseAssertionsTest { 16 | protected static final def HEADER_NAME = 'headerName' 17 | protected static final def NOT_MATCHING_HEADER_NAME = 'headerName not to match' 18 | protected static final def HEADER_VALUE = 'headerValue' 19 | protected static final def NOT_MATCHING_HEADER_VALUE = 'headerValue not to match' 20 | protected static final def HEADER = new HttpHeader(HEADER_NAME, HEADER_VALUE) 21 | 22 | @Before 23 | void setUp() { 24 | proxy.enableHarCaptureTypes(CaptureType.RESPONSE_HEADERS) 25 | mockResponse(URL_PATH, HEADER) 26 | } 27 | 28 | protected mockResponse(String path, HttpHeader header) { 29 | stubFor(get(urlEqualTo("/" + path)) 30 | .willReturn(ok() 31 | .withBody(SUCCESSFUL_RESPONSE_BODY) 32 | .withHeaders(new HttpHeaders(header)) 33 | )) 34 | // mockServer.when(request() 35 | // .withMethod("GET") 36 | // .withPath("/${path}"), 37 | // Times.once()) 38 | // .respond(response() 39 | // .withStatusCode(HttpStatus.SC_OK) 40 | // .withBody(SUCCESSFUL_RESPONSE_BODY) 41 | // .withHeader(header)) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/java/com/browserup/bup/proxy/test/util/MockServerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.proxy.test.util; 6 | 7 | import com.github.tomakehurst.wiremock.junit.WireMockRule; 8 | import org.junit.Before; 9 | import org.junit.Rule; 10 | 11 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; 12 | 13 | /** 14 | * Tests can subclass this to get access to a ClientAndServer instance for creating mock responses. 15 | */ 16 | public class MockServerTest { 17 | protected int mockServerPort; 18 | protected int mockServerHttpsPort; 19 | 20 | @Rule 21 | public WireMockRule wireMockRule = new WireMockRule(options().port(0).httpsPort(0)); 22 | 23 | @Before 24 | public void setUpMockServer() { 25 | mockServerPort = wireMockRule.port(); 26 | mockServerHttpsPort = wireMockRule.httpsPort(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/java/com/browserup/bup/proxy/test/util/NewProxyServerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.proxy.test.util; 6 | 7 | import com.browserup.bup.BrowserUpProxy; 8 | import com.browserup.bup.BrowserUpProxyServer; 9 | import org.junit.After; 10 | import org.junit.Before; 11 | 12 | /** 13 | * A base class that spins up and shuts down a BrowserUpProxy instance using the new interface. It also provides mock server support via 14 | * {@link com.browserup.bup.proxy.test.util.MockServerTest}. 15 | */ 16 | public class NewProxyServerTest extends MockServerTest { 17 | protected BrowserUpProxy proxy; 18 | 19 | @Before 20 | public void setUpProxyServer() { 21 | proxy = new BrowserUpProxyServer(); 22 | proxy.start(); 23 | } 24 | 25 | @After 26 | public void shutDownProxyServer() { 27 | if (proxy != null) { 28 | proxy.abort(); 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/java/com/browserup/bup/proxy/test/util/TestConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.proxy.test.util; 6 | 7 | import com.google.common.collect.ImmutableList; 8 | 9 | import java.net.InetAddress; 10 | import java.net.UnknownHostException; 11 | import java.util.List; 12 | 13 | /** 14 | * Convenience class for test constants. 15 | */ 16 | public class TestConstants { 17 | public static final InetAddress addressOnes; 18 | public static final InetAddress addressTwos; 19 | public static final List addressOnesList; 20 | public static final List addressTwosList; 21 | 22 | static { 23 | try { 24 | addressOnes = InetAddress.getByName("1.1.1.1"); 25 | addressTwos = InetAddress.getByName("2.2.2.2"); 26 | addressOnesList = ImmutableList.of(TestConstants.addressOnes); 27 | addressTwosList = ImmutableList.of(TestConstants.addressTwos); 28 | } catch (UnknownHostException e) { 29 | throw new RuntimeException(e); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/java/com/browserup/harreader/model/AbstractMapperTest.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader.model; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.junit.Assert; 5 | import org.junit.Test; 6 | 7 | public abstract class AbstractMapperTest { 8 | 9 | protected final static String UNKNOWN_PROPERTY = "{\"unknownProperty\":\"value\"}"; 10 | 11 | @Test 12 | public abstract void testMapping(); 13 | 14 | public T map(String input, Class tClass) { 15 | ObjectMapper mapper = new ObjectMapper(); 16 | try { 17 | return mapper.readValue(input, tClass); 18 | } catch (Exception e) { 19 | Assert.fail(e.getMessage()); 20 | } 21 | return null; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/java/com/browserup/harreader/model/HarCacheTest.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader.model; 2 | 3 | import org.junit.Assert; 4 | 5 | import java.util.Date; 6 | 7 | public class HarCacheTest extends AbstractMapperTest { 8 | 9 | private final static Date EXPECTED_EXPIRES = new Date() {{ 10 | setTime(1388577600000L); 11 | }}; 12 | private final static Date EXPECTED_LAST_ACCESS = new Date() {{ 13 | setTime(1370088000000L); 14 | }}; 15 | 16 | @Override 17 | public void testMapping() { 18 | HarCache cache = map("{\"beforeRequest\":{\"expires\":\"2014-01-01T12:00:00\",\"lastAccess\":\"2013-06-01T12:00:00\",\"eTag\":\"abc123\"," + 19 | "\"hitCount\":3,\"comment\":\"my comment\"},\"afterRequest\":{},\"comment\":\"my comment 2\"}", HarCache.class); 20 | 21 | Assert.assertNotNull(cache.getBeforeRequest()); 22 | Assert.assertEquals(EXPECTED_EXPIRES, cache.getBeforeRequest().getExpires()); 23 | Assert.assertEquals(EXPECTED_LAST_ACCESS, cache.getBeforeRequest().getLastAccess()); 24 | Assert.assertEquals("abc123", cache.getBeforeRequest().geteTag()); 25 | Assert.assertEquals(3, (long) cache.getBeforeRequest().getHitCount()); 26 | Assert.assertEquals("my comment", cache.getBeforeRequest().getComment()); 27 | 28 | Assert.assertNotNull(cache.getAfterRequest()); 29 | 30 | Assert.assertEquals("my comment 2", cache.getComment()); 31 | 32 | cache = map(UNKNOWN_PROPERTY, HarCache.class); 33 | Assert.assertNotNull(cache); 34 | 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/java/com/browserup/harreader/model/HarContentTest.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader.model; 2 | 3 | import org.junit.Assert; 4 | 5 | public class HarContentTest extends AbstractMapperTest { 6 | 7 | @Override 8 | public void testMapping() { 9 | HarContent content = map("{\"size\":123,\"compression\":45,\"mimeType\":\"mime/type\"," + 10 | "\"text\":\"my content\",\"encoding\":\"base64\",\"comment\":\"my comment\"}", HarContent.class); 11 | 12 | Assert.assertEquals(123L, (long) content.getSize()); 13 | Assert.assertEquals(45L, (long) content.getCompression()); 14 | Assert.assertEquals("mime/type", content.getMimeType()); 15 | Assert.assertEquals("my content", content.getText()); 16 | Assert.assertEquals("base64", content.getEncoding()); 17 | Assert.assertEquals("my comment", content.getComment()); 18 | 19 | content = map(UNKNOWN_PROPERTY, HarContent.class); 20 | Assert.assertNotNull(content); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/java/com/browserup/harreader/model/HarCookieTest.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader.model; 2 | 3 | import org.junit.Assert; 4 | 5 | import java.util.Date; 6 | 7 | public class HarCookieTest extends AbstractMapperTest { 8 | 9 | private final static Date EXPECTED_EXPIRES = new Date() {{ 10 | setTime(1388577600000L); 11 | }}; 12 | 13 | @Override 14 | public void testMapping() { 15 | HarCookie cookie = map("{\"name\":\"aName\",\"value\":\"aValue\",\"path\":\"/\",\"domain\":\"sstoehr.de\"," + 16 | "\"expires\":\"2014-01-01T12:00:00\",\"httpOnly\":\"true\",\"secure\":\"false\",\"comment\":\"my comment\"}", HarCookie.class); 17 | 18 | Assert.assertNotNull(cookie); 19 | Assert.assertEquals("aName", cookie.getName()); 20 | Assert.assertEquals("aValue", cookie.getValue()); 21 | Assert.assertEquals("/", cookie.getPath()); 22 | Assert.assertEquals("sstoehr.de", cookie.getDomain()); 23 | Assert.assertEquals(EXPECTED_EXPIRES, cookie.getExpires()); 24 | Assert.assertEquals(true, cookie.getHttpOnly()); 25 | Assert.assertEquals(false, cookie.getSecure()); 26 | Assert.assertEquals("my comment", cookie.getComment()); 27 | 28 | cookie = map(UNKNOWN_PROPERTY, HarCookie.class); 29 | Assert.assertNotNull(cookie); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/java/com/browserup/harreader/model/HarCreatorBrowserTest.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader.model; 2 | 3 | import org.junit.Assert; 4 | 5 | public class HarCreatorBrowserTest extends AbstractMapperTest { 6 | 7 | @Override 8 | public void testMapping() { 9 | HarCreatorBrowser creatorBrowser = map("{\"name\":\"aName\",\"version\":\"aVersion\",\"comment\":\"my comment\"}", HarCreatorBrowser.class); 10 | 11 | Assert.assertNotNull(creatorBrowser); 12 | Assert.assertEquals("aName", creatorBrowser.getName()); 13 | Assert.assertEquals("aVersion", creatorBrowser.getVersion()); 14 | Assert.assertEquals("my comment", creatorBrowser.getComment()); 15 | 16 | creatorBrowser = map(UNKNOWN_PROPERTY, HarCreatorBrowser.class); 17 | Assert.assertNotNull(creatorBrowser); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/java/com/browserup/harreader/model/HarHeaderTest.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader.model; 2 | 3 | import org.junit.Assert; 4 | 5 | public class HarHeaderTest extends AbstractMapperTest { 6 | 7 | @Override 8 | public void testMapping() { 9 | HarHeader header = map("{\"name\":\"aName\",\"value\":\"aValue\",\"comment\":\"my comment\"}", HarHeader.class); 10 | 11 | Assert.assertNotNull(header); 12 | Assert.assertEquals("aName", header.getName()); 13 | Assert.assertEquals("aValue", header.getValue()); 14 | Assert.assertEquals("my comment", header.getComment()); 15 | 16 | header = map(UNKNOWN_PROPERTY, HarHeader.class); 17 | Assert.assertNotNull(header); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/java/com/browserup/harreader/model/HarPageTest.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader.model; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | import java.util.Date; 7 | 8 | public class HarPageTest extends AbstractMapperTest { 9 | 10 | private final static Date EXPECTED_STARTED = new Date() {{ 11 | setTime(1388577600000L); 12 | }}; 13 | 14 | @Override 15 | public void testMapping() { 16 | HarPage page = map("{\"startedDateTime\":\"2014-01-01T12:00:00\",\"id\":\"anId\"," 17 | + "\"title\":\"aTitle\",\"pageTimings\":{},\"comment\":\"my comment\", \"_add\": \"additional info\"}", HarPage.class); 18 | 19 | Assert.assertNotNull(page); 20 | Assert.assertEquals(EXPECTED_STARTED, page.getStartedDateTime()); 21 | Assert.assertEquals("anId", page.getId()); 22 | Assert.assertEquals("aTitle", page.getTitle()); 23 | Assert.assertNotNull(page.getPageTimings()); 24 | Assert.assertEquals("my comment", page.getComment()); 25 | Assert.assertEquals("additional info", page.getAdditional().get("_add")); 26 | 27 | page = map(UNKNOWN_PROPERTY, HarPage.class); 28 | Assert.assertNotNull(page); 29 | } 30 | 31 | @Test 32 | public void testPageTimingsNull() { 33 | HarPage page = new HarPage(); 34 | page.setPageTimings(null); 35 | Assert.assertNotNull(page.getPageTimings()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/java/com/browserup/harreader/model/HarPageTimingTest.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader.model; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class HarPageTimingTest extends AbstractMapperTest { 7 | 8 | private static final Integer EXPECTED_DEFAULT_DURATION = -1; 9 | 10 | @Test 11 | public void testOnContentLoad() { 12 | HarPageTiming pageTiming = new HarPageTiming(); 13 | Assert.assertEquals(EXPECTED_DEFAULT_DURATION, pageTiming.getOnContentLoad()); 14 | 15 | pageTiming.setOnContentLoad(1234); 16 | Assert.assertEquals(1234, (int) pageTiming.getOnContentLoad()); 17 | 18 | pageTiming.setOnContentLoad(null); 19 | Assert.assertEquals(EXPECTED_DEFAULT_DURATION, pageTiming.getOnContentLoad()); 20 | } 21 | 22 | @Test 23 | public void testOnLoad() { 24 | HarPageTiming pageTiming = new HarPageTiming(); 25 | Assert.assertEquals(EXPECTED_DEFAULT_DURATION, pageTiming.getOnLoad()); 26 | 27 | pageTiming.setOnLoad(1234); 28 | Assert.assertEquals(1234, (int) pageTiming.getOnLoad()); 29 | 30 | pageTiming.setOnLoad(null); 31 | Assert.assertEquals(EXPECTED_DEFAULT_DURATION, pageTiming.getOnLoad()); 32 | } 33 | 34 | @Override 35 | public void testMapping() { 36 | HarPageTiming pageTiming = map("{\"onContentLoad\": 1234, \"onLoad\": 5678, \"comment\": \"My comment\"}", HarPageTiming.class); 37 | 38 | Assert.assertEquals(1234, (int) pageTiming.getOnContentLoad()); 39 | Assert.assertEquals(5678, (int) pageTiming.getOnLoad()); 40 | Assert.assertEquals("My comment", pageTiming.getComment()); 41 | 42 | pageTiming = map(UNKNOWN_PROPERTY, HarPageTiming.class); 43 | Assert.assertNotNull(pageTiming); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/java/com/browserup/harreader/model/HarPostDataParamTest.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader.model; 2 | 3 | import org.junit.Assert; 4 | 5 | public class HarPostDataParamTest extends AbstractMapperTest { 6 | 7 | @Override 8 | public void testMapping() { 9 | HarPostDataParam postDataParam = map("{\"name\": \"aName\", \"value\": \"aValue\", \"fileName\": \"aFilename\", \"contentType\": \"aContentType\", \"comment\": \"My comment\"}", HarPostDataParam.class); 10 | 11 | Assert.assertEquals("aName", postDataParam.getName()); 12 | Assert.assertEquals("aValue", postDataParam.getValue()); 13 | Assert.assertEquals("aFilename", postDataParam.getFileName()); 14 | Assert.assertEquals("aContentType", postDataParam.getContentType()); 15 | Assert.assertEquals("My comment", postDataParam.getComment()); 16 | 17 | 18 | postDataParam = map(UNKNOWN_PROPERTY, HarPostDataParam.class); 19 | Assert.assertNotNull(postDataParam); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/java/com/browserup/harreader/model/HarPostDataTest.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader.model; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class HarPostDataTest extends AbstractMapperTest { 10 | 11 | private static final List EXPECTED_LIST = new ArrayList<>(); 12 | 13 | @Override 14 | public void testMapping() { 15 | HarPostData postData = map("{\"mimeType\": \"aMimeType\", \"params\": [], \"text\":\"aText\", \"comment\": \"My comment\"}", HarPostData.class); 16 | 17 | Assert.assertEquals("aMimeType", postData.getMimeType()); 18 | Assert.assertEquals(EXPECTED_LIST, postData.getParams()); 19 | Assert.assertEquals("aText", postData.getText()); 20 | Assert.assertEquals("My comment", postData.getComment()); 21 | 22 | postData = map(UNKNOWN_PROPERTY, HarPostData.class); 23 | Assert.assertNotNull(postData); 24 | } 25 | 26 | @Test 27 | public void testParams() { 28 | HarPostData postData = new HarPostData(); 29 | postData.setParams(null); 30 | Assert.assertNotNull(postData.getParams()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/java/com/browserup/harreader/model/HarQueryParamTest.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader.model; 2 | 3 | import org.junit.Assert; 4 | 5 | public class HarQueryParamTest extends AbstractMapperTest { 6 | 7 | @Override 8 | public void testMapping() { 9 | HarQueryParam queryParam = map("{\"name\": \"aName\", \"value\":\"aValue\", \"comment\": \"My comment\"}", HarQueryParam.class); 10 | 11 | Assert.assertEquals("aName", queryParam.getName()); 12 | Assert.assertEquals("aValue", queryParam.getValue()); 13 | Assert.assertEquals("My comment", queryParam.getComment()); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/java/com/browserup/harreader/model/HarTest.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader.model; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class HarTest extends AbstractMapperTest{ 7 | 8 | @Test 9 | public void testLogNull() { 10 | Har har = new Har(); 11 | har.setLog(null); 12 | Assert.assertNotNull(har.getLog()); 13 | } 14 | 15 | @Override 16 | public void testMapping() { 17 | Har har = map("{\"log\": {}}", Har.class); 18 | Assert.assertNotNull(har.getLog()); 19 | 20 | har = map(UNKNOWN_PROPERTY, Har.class); 21 | Assert.assertNotNull(har); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/java/com/browserup/harreader/model/HarTimingTest.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader.model; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class HarTimingTest extends AbstractMapperTest { 7 | 8 | @Override 9 | public void testMapping() { 10 | HarTiming timing = map("{\"blocked\": 3804,\"dns\": 23,\"connect\": 5,\"send\": 9,\"wait\": 5209," 11 | + "\"receive\": 79, \"ssl\": 123, \"comment\": \"my comment\"}", HarTiming.class); 12 | 13 | Assert.assertNotNull(timing); 14 | Assert.assertEquals(3804, (int) timing.getBlocked()); 15 | Assert.assertEquals(23, (int) timing.getDns()); 16 | Assert.assertEquals(5, (int) timing.getConnect()); 17 | Assert.assertEquals(9, (int) timing.getSend()); 18 | Assert.assertEquals(5209, (int) timing.getWait()); 19 | Assert.assertEquals(79, (int) timing.getReceive()); 20 | Assert.assertEquals(123, (int) timing.getSsl()); 21 | Assert.assertEquals("my comment", timing.getComment()); 22 | } 23 | 24 | @Test 25 | public void testBlocked() { 26 | HarTiming timing = new HarTiming(); 27 | timing.setBlocked(null); 28 | Assert.assertEquals(-1, (int) timing.getBlocked()); 29 | } 30 | 31 | @Test 32 | public void testDns() { 33 | HarTiming timing = new HarTiming(); 34 | timing.setDns(null); 35 | Assert.assertEquals(-1, (int) timing.getDns()); 36 | } 37 | 38 | @Test 39 | public void testConnect() { 40 | HarTiming timing = new HarTiming(); 41 | timing.setConnect(null); 42 | Assert.assertEquals(-1, (int) timing.getConnect()); 43 | } 44 | 45 | @Test 46 | public void testSsl() { 47 | HarTiming timing = new HarTiming(); 48 | timing.setSsl(null); 49 | Assert.assertEquals(-1, (int) timing.getSsl()); 50 | } 51 | } -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/java/com/browserup/harreader/model/HttpStatusTest.java: -------------------------------------------------------------------------------- 1 | package com.browserup.harreader.model; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | public class HttpStatusTest { 7 | 8 | @Test 9 | public void testByCode() { 10 | for (HttpStatus status : HttpStatus.values()) { 11 | Assert.assertEquals(status, HttpStatus.byCode(status.getCode())); 12 | } 13 | } 14 | 15 | @Test 16 | public void test302() { 17 | Assert.assertEquals(HttpStatus.FOUND, HttpStatus.byCode(302)); 18 | } 19 | 20 | @Test 21 | public void testInvalidCode() { 22 | Assert.assertEquals(HttpStatus.UNKNOWN_HTTP_STATUS, HttpStatus.byCode(0)); 23 | Assert.assertEquals(HttpStatus.UNKNOWN_HTTP_STATUS, HttpStatus.byCode(1000)); 24 | Assert.assertEquals(HttpStatus.UNKNOWN_HTTP_STATUS, HttpStatus.byCode(-999)); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /browserup-proxy-core/src/test/resources/log4j2-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "configuration" : { 3 | "name": "test", 4 | "appenders": { 5 | "Console": { 6 | "name": "console", 7 | "target": "SYSTEM_OUT", 8 | "PatternLayout": { 9 | "pattern": "%-7r %date %level [%thread] %logger - %msg%n" 10 | } 11 | } 12 | }, 13 | 14 | "loggers": { 15 | "root": { 16 | "level": "debug", 17 | "appender-ref": { 18 | "ref": "console" 19 | } 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /browserup-proxy-dist/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /browserup-proxy-dist/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | browserup-proxy-dist 4 | Project browserup-proxy-dist created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.buildship.core.gradleprojectnature 22 | 23 | 24 | -------------------------------------------------------------------------------- /browserup-proxy-dist/src/main/java/com/browserup/bup/exception/JettyException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.exception; 6 | 7 | /** 8 | * A wrapper for exceptions coming from Jetty methods that throw Exception, 9 | * rather than a useful Exception subclass. 10 | */ 11 | public class JettyException extends RuntimeException { 12 | private static final long serialVersionUID = 8125833642102189196L; 13 | 14 | public JettyException() { 15 | } 16 | 17 | public JettyException(String message) { 18 | super(message); 19 | } 20 | 21 | public JettyException(Throwable cause) { 22 | super(cause); 23 | } 24 | 25 | public JettyException(String message, Throwable cause) { 26 | super(message, cause); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /browserup-proxy-dist/src/main/resources/bup-logging.yaml: -------------------------------------------------------------------------------- 1 | # This file controls the logging configuration for BrowserUp-proxy in "standalone" mode. To adjust the amount of log output, modify the 2 | # 'level' fields below. For more information on log4j configuration files, see http://logging.apache.org/log4j/2.x/manual/configuration.html. 3 | 4 | configuration: 5 | name: standalone 6 | appenders: 7 | console: 8 | name: console 9 | target: SYSTEM_OUT 10 | PatternLayout: 11 | pattern: "[%-5level %date{ISO8601} %logger] (%thread) %msg %xThrowable%n" 12 | file: 13 | - 14 | name: file 15 | fileName: bup.log 16 | PatternLayout: 17 | pattern: "[%-5level %date{ISO8601} %logger] (%thread) %msg %xThrowable%n" 18 | append: false 19 | loggers: 20 | logger: 21 | - 22 | name: com.browserup.bup.proxy.jetty.util.ThreadedServer 23 | level: warn 24 | additivity: false 25 | - 26 | name: com.browserup.bup 27 | # to suppress unwanted BUP logging statements, set the level below for the source logger to WARN or ERROR. 28 | # to enable more verbose logging, set the level to DEBUG or TRACE. 29 | level: info 30 | root: 31 | # to suppress unwanted logging statements globally, set the level below to WARN or ERROR. 32 | level: info 33 | appender-ref: 34 | - 35 | ref: console 36 | - 37 | ref: file -------------------------------------------------------------------------------- /browserup-proxy-mitm/.classpath: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | browserup-proxy-mitm 4 | Project browserup-proxy-mitm created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.buildship.core.gradleprojectnature 22 | 23 | 24 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/main/java/com/browserup/bup/mitm/CertificateAndKey.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm; 6 | 7 | import java.security.PrivateKey; 8 | import java.security.cert.X509Certificate; 9 | 10 | /** 11 | * A simple container for an X.509 certificate and its corresponding private key. 12 | */ 13 | public class CertificateAndKey { 14 | private final X509Certificate certificate; 15 | private final PrivateKey privateKey; 16 | 17 | public CertificateAndKey(X509Certificate certificate, PrivateKey privateKey) { 18 | this.certificate = certificate; 19 | this.privateKey = privateKey; 20 | } 21 | 22 | public X509Certificate getCertificate() { 23 | return certificate; 24 | } 25 | 26 | public PrivateKey getPrivateKey() { 27 | return privateKey; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/main/java/com/browserup/bup/mitm/CertificateAndKeySource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm; 6 | 7 | /** 8 | * A CertificateAndKeySource generates {@link CertificateAndKey}s, i.e. the root certificate and private key used 9 | * to sign impersonated certificates of upstream servers. Implementations of this interface load impersonation materials 10 | * from various sources, including Java KeyStores, JKS files, etc., or generate them on-the-fly. 11 | */ 12 | public interface CertificateAndKeySource { 13 | /** 14 | * Loads a certificate and its corresponding private key. Every time this method is called, it should return the same 15 | * certificate and private key (although it may be a different {@link CertificateAndKey} instance). 16 | * 17 | * @return certificate and its corresponding private key 18 | */ 19 | CertificateAndKey load(); 20 | } 21 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/main/java/com/browserup/bup/mitm/CertificateInfoGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm; 6 | 7 | import java.security.cert.X509Certificate; 8 | import java.util.List; 9 | 10 | /** 11 | * A functional interface to allow customization of the certificates generated by the 12 | * {@link com.browserup.bup.mitm.manager.ImpersonatingMitmManager}. 13 | */ 14 | public interface CertificateInfoGenerator { 15 | /** 16 | * Generate a certificate for the specified hostnames, optionally using parameters from the originalCertificate. 17 | * 18 | * @param hostnames the hostnames to generate the certificate for, which may include wildcards 19 | * @param originalCertificate original X.509 certificate sent by the upstream server, which may be null 20 | * @return CertificateInfo to be used to create an X509Certificate for the specified hostnames 21 | */ 22 | CertificateInfo generate(List hostnames, X509Certificate originalCertificate); 23 | } 24 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/main/java/com/browserup/bup/mitm/ExistingCertificateSource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm; 6 | 7 | import java.security.PrivateKey; 8 | import java.security.cert.X509Certificate; 9 | 10 | /** 11 | * A simple adapter that produces a {@link CertificateAndKey} from existing {@link X509Certificate} and {@link PrivateKey} 12 | * java objects. 13 | */ 14 | public class ExistingCertificateSource implements CertificateAndKeySource { 15 | private final X509Certificate rootCertificate; 16 | private final PrivateKey privateKey; 17 | 18 | public ExistingCertificateSource(X509Certificate rootCertificate, PrivateKey privateKey) { 19 | if (rootCertificate == null) { 20 | throw new IllegalArgumentException("CA root certificate cannot be null"); 21 | } 22 | 23 | if (privateKey == null) { 24 | throw new IllegalArgumentException("Private key cannot be null"); 25 | } 26 | 27 | this.rootCertificate = rootCertificate; 28 | this.privateKey = privateKey; 29 | } 30 | 31 | @Override 32 | public CertificateAndKey load() { 33 | return new CertificateAndKey(rootCertificate, privateKey); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/main/java/com/browserup/bup/mitm/exception/CertificateCreationException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm.exception; 6 | 7 | /** 8 | * Indicates a problem creating a certificate (server or CA). 9 | */ 10 | public class CertificateCreationException extends RuntimeException { 11 | private static final long serialVersionUID = 592999944486567944L; 12 | 13 | public CertificateCreationException() { 14 | } 15 | 16 | public CertificateCreationException(String message) { 17 | super(message); 18 | } 19 | 20 | public CertificateCreationException(String message, Throwable cause) { 21 | super(message, cause); 22 | } 23 | 24 | public CertificateCreationException(Throwable cause) { 25 | super(cause); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/main/java/com/browserup/bup/mitm/exception/CertificateSourceException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm.exception; 6 | 7 | /** 8 | * Indicates that a {@link com.browserup.bup.mitm.CertificateAndKeySource} encountered an error while loading a 9 | * certificate and/or private key from a KeyStore, PEM file, or other source. 10 | */ 11 | public class CertificateSourceException extends RuntimeException { 12 | private static final long serialVersionUID = 6195838041376082083L; 13 | 14 | public CertificateSourceException() { 15 | } 16 | 17 | public CertificateSourceException(String message) { 18 | super(message); 19 | } 20 | 21 | public CertificateSourceException(String message, Throwable cause) { 22 | super(message, cause); 23 | } 24 | 25 | public CertificateSourceException(Throwable cause) { 26 | super(cause); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/main/java/com/browserup/bup/mitm/exception/ExportException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm.exception; 6 | 7 | /** 8 | * Indicates an error occurred while exporting/serializing a certificate, private key, KeyStore, etc. 9 | */ 10 | public class ExportException extends RuntimeException { 11 | private static final long serialVersionUID = -3505301862887355206L; 12 | 13 | public ExportException() { 14 | } 15 | 16 | public ExportException(String message) { 17 | super(message); 18 | } 19 | 20 | public ExportException(String message, Throwable cause) { 21 | super(message, cause); 22 | } 23 | 24 | public ExportException(Throwable cause) { 25 | super(cause); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/main/java/com/browserup/bup/mitm/exception/ImportException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm.exception; 6 | 7 | /** 8 | * Indicates that an error occurred while importing a certificate, private key, or KeyStore. 9 | */ 10 | public class ImportException extends RuntimeException { 11 | private static final long serialVersionUID = 584414535648926010L; 12 | 13 | public ImportException() { 14 | } 15 | 16 | public ImportException(String message) { 17 | super(message); 18 | } 19 | 20 | public ImportException(String message, Throwable cause) { 21 | super(message, cause); 22 | } 23 | 24 | public ImportException(Throwable cause) { 25 | super(cause); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/main/java/com/browserup/bup/mitm/exception/KeyGeneratorException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm.exception; 6 | 7 | /** 8 | * Indicates an exception occurred while generating a key pair. 9 | */ 10 | public class KeyGeneratorException extends RuntimeException { 11 | private static final long serialVersionUID = 7607159769324427808L; 12 | 13 | public KeyGeneratorException() { 14 | } 15 | 16 | public KeyGeneratorException(String message) { 17 | super(message); 18 | } 19 | 20 | public KeyGeneratorException(String message, Throwable cause) { 21 | super(message, cause); 22 | } 23 | 24 | public KeyGeneratorException(Throwable cause) { 25 | super(cause); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/main/java/com/browserup/bup/mitm/exception/KeyStoreAccessException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm.exception; 6 | 7 | /** 8 | * Indicates an error occurred while accessing a java KeyStore. 9 | */ 10 | public class KeyStoreAccessException extends RuntimeException { 11 | private static final long serialVersionUID = -5560417886988154298L; 12 | 13 | public KeyStoreAccessException() { 14 | } 15 | 16 | public KeyStoreAccessException(String message) { 17 | super(message); 18 | } 19 | 20 | public KeyStoreAccessException(String message, Throwable cause) { 21 | super(message, cause); 22 | } 23 | 24 | public KeyStoreAccessException(Throwable cause) { 25 | super(cause); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/main/java/com/browserup/bup/mitm/exception/MitmException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm.exception; 6 | 7 | /** 8 | * Indicates a general problem occurred while attempting to man-in-the-middle communications between the client and the 9 | * upstream server. 10 | */ 11 | public class MitmException extends RuntimeException { 12 | private static final long serialVersionUID = -1960691906515767537L; 13 | 14 | public MitmException() { 15 | } 16 | 17 | public MitmException(String message) { 18 | super(message); 19 | } 20 | 21 | public MitmException(String message, Throwable cause) { 22 | super(message, cause); 23 | } 24 | 25 | public MitmException(Throwable cause) { 26 | super(cause); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/main/java/com/browserup/bup/mitm/exception/SslContextInitializationException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm.exception; 6 | 7 | /** 8 | * Indicates an error occurred while attempting to create a new {@link javax.net.ssl.SSLContext}. 9 | */ 10 | public class SslContextInitializationException extends RuntimeException { 11 | private static final long serialVersionUID = 6744059714710316821L; 12 | 13 | public SslContextInitializationException() { 14 | } 15 | 16 | public SslContextInitializationException(String message) { 17 | super(message); 18 | } 19 | 20 | public SslContextInitializationException(String message, Throwable cause) { 21 | super(message, cause); 22 | } 23 | 24 | public SslContextInitializationException(Throwable cause) { 25 | super(cause); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/main/java/com/browserup/bup/mitm/exception/TrustSourceException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm.exception; 6 | 7 | /** 8 | * Indicates that an error occurred while attempting to create or populate a {@link com.browserup.bup.mitm.TrustSource}. 9 | */ 10 | public class TrustSourceException extends RuntimeException { 11 | public TrustSourceException() { 12 | } 13 | 14 | public TrustSourceException(String message) { 15 | super(message); 16 | } 17 | 18 | public TrustSourceException(String message, Throwable cause) { 19 | super(message, cause); 20 | } 21 | 22 | public TrustSourceException(Throwable cause) { 23 | super(cause); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/main/java/com/browserup/bup/mitm/exception/UncheckedIOException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm.exception; 6 | 7 | import java.io.IOException; 8 | 9 | /** 10 | * A convenience exception that wraps checked {@link IOException}s. (The built-in java.io.UncheckedIOException is only 11 | * available on Java 8.) 12 | */ 13 | public class UncheckedIOException extends RuntimeException { 14 | public UncheckedIOException(String message, IOException cause) { 15 | super(message, cause); 16 | } 17 | 18 | public UncheckedIOException(IOException cause) { 19 | super(cause); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/main/java/com/browserup/bup/mitm/keys/KeyGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm.keys; 6 | 7 | import java.security.KeyPair; 8 | 9 | /** 10 | * A functional interface for key pair generators. 11 | */ 12 | public interface KeyGenerator { 13 | /** 14 | * Generates a new public/private key pair. This method should not cache or reuse any previously-generated key pairs. 15 | * 16 | * @return a new public/private key pair 17 | */ 18 | KeyPair generate(); 19 | } 20 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/main/java/com/browserup/bup/mitm/keys/RSAKeyGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm.keys; 6 | 7 | import com.browserup.bup.mitm.exception.KeyGeneratorException; 8 | 9 | import java.security.KeyPair; 10 | import java.security.KeyPairGenerator; 11 | import java.security.NoSuchAlgorithmException; 12 | 13 | /** 14 | * A {@link KeyGenerator} that creates RSA key pairs. 15 | */ 16 | public class RSAKeyGenerator implements KeyGenerator { 17 | private static final String RSA_KEY_GEN_ALGORITHM = "RSA"; 18 | 19 | /** 20 | * Use a default RSA key size of 2048, since Chrome, Firefox, and possibly other browsers have begun to distrust 21 | * certificates signed with 1024-bit RSA keys. 22 | */ 23 | private static final int DEFAULT_KEY_SIZE = 2048; 24 | 25 | private final int keySize; 26 | 27 | /** 28 | * Create a {@link KeyGenerator} that will create a 2048-bit RSA key pair. 29 | */ 30 | public RSAKeyGenerator() { 31 | this.keySize = DEFAULT_KEY_SIZE; 32 | } 33 | 34 | /** 35 | * Create a {@link KeyGenerator} that will create an RSA key pair of the specified keySize. 36 | * @param keySize keySize 37 | */ 38 | public RSAKeyGenerator(int keySize) { 39 | this.keySize = keySize; 40 | } 41 | 42 | @Override 43 | public KeyPair generate() { 44 | // obtain an RSA key pair generator for the specified key size 45 | KeyPairGenerator generator; 46 | try { 47 | generator = KeyPairGenerator.getInstance(RSA_KEY_GEN_ALGORITHM); 48 | generator.initialize(keySize); 49 | } catch (NoSuchAlgorithmException e) { 50 | throw new KeyGeneratorException("Unable to generate " + keySize + "-bit RSA public/private key pair", e); 51 | } 52 | 53 | return generator.generateKeyPair(); 54 | } 55 | 56 | @Override 57 | public String toString() { 58 | return "RSA (" + keySize + ")"; 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/main/java/com/browserup/bup/mitm/util/MitmConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm.util; 6 | 7 | /** 8 | * Default values for basic MITM properties. 9 | */ 10 | public class MitmConstants { 11 | /** 12 | * The default message digest to use when signing certificates (CA or server). On 64-bit systems this is set to 13 | * SHA512, on 32-bit systems this is SHA256. On 64-bit systems, SHA512 generally performs better than SHA256; see 14 | * this question for details: http://crypto.stackexchange.com/questions/26336/sha512-faster-than-sha256. SHA384 is 15 | * SHA512 with a smaller output size. 16 | */ 17 | public static final String DEFAULT_MESSAGE_DIGEST = is32BitJvm() ? "SHA256": "SHA384"; 18 | 19 | /** 20 | * The default {@link java.security.KeyStore} type to use when creating KeyStores (e.g. for impersonated server 21 | * certificates). PKCS12 is widely supported. 22 | */ 23 | public static final String DEFAULT_KEYSTORE_TYPE = "PKCS12"; 24 | 25 | /** 26 | * Uses the non-portable system property sun.arch.data.model to help determine if we are running on a 32-bit JVM. 27 | * Since the majority of modern systems are 64 bits, this method "assumes" 64 bits and only returns true if 28 | * sun.arch.data.model explicitly indicates a 32-bit JVM. 29 | * 30 | * @return true if we can determine definitively that this is a 32-bit JVM, otherwise false 31 | */ 32 | private static boolean is32BitJvm() { 33 | Integer bits = Integer.getInteger("sun.arch.data.model"); 34 | 35 | return bits != null && bits == 32; 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/main/resources/default-ciphers.txt: -------------------------------------------------------------------------------- 1 | TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 2 | TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 3 | TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 4 | TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 5 | TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 6 | TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 7 | TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 8 | TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 9 | TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 10 | TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 11 | TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 12 | TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 13 | TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 14 | TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA 15 | TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA 16 | TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA 17 | TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA 18 | TLS_DHE_RSA_WITH_AES_256_CBC_SHA 19 | TLS_DHE_RSA_WITH_AES_128_CBC_SHA 20 | TLS_RSA_WITH_AES_256_GCM_SHA384 21 | TLS_RSA_WITH_AES_256_GCM_SHA384 22 | TLS_RSA_WITH_AES_128_GCM_SHA256 23 | TLS_RSA_WITH_AES_256_CBC_SHA 24 | TLS_RSA_WITH_AES_128_CBC_SHA 25 | SSL_RSA_WITH_3DES_EDE_CBC_SHA 26 | TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 27 | TLS_DHE_DSS_WITH_AES_256_CBC_SHA 28 | TLS_DHE_DSS_WITH_AES_128_CBC_SHA 29 | 30 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/test/groovy/com/browserup/bup/mitm/ExistingCertificateSourceTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm 6 | 7 | import org.junit.Test 8 | 9 | import java.security.PrivateKey 10 | import java.security.cert.X509Certificate 11 | 12 | import static org.junit.Assert.assertEquals 13 | import static org.mockito.Mockito.mock 14 | 15 | class ExistingCertificateSourceTest { 16 | 17 | X509Certificate mockCertificate = mock(X509Certificate) 18 | PrivateKey mockPrivateKey = mock(PrivateKey) 19 | 20 | @Test 21 | void testLoadExistingCertificateAndKey() { 22 | ExistingCertificateSource certificateSource = new ExistingCertificateSource(mockCertificate, mockPrivateKey) 23 | CertificateAndKey certificateAndKey = certificateSource.load() 24 | 25 | assertEquals(mockCertificate, certificateAndKey.certificate) 26 | assertEquals(mockPrivateKey, certificateAndKey.privateKey) 27 | } 28 | 29 | @Test(expected = IllegalArgumentException) 30 | void testMustSupplyCertificate() { 31 | ExistingCertificateSource certificateSource = new ExistingCertificateSource(null, mockPrivateKey) 32 | certificateSource.load() 33 | } 34 | 35 | @Test(expected = IllegalArgumentException) 36 | void testMustSupplyPrivateKey() { 37 | ExistingCertificateSource certificateSource = new ExistingCertificateSource(mockCertificate, null) 38 | certificateSource.load() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/test/groovy/com/browserup/bup/mitm/KeyStoreCertificateSourceTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm 6 | 7 | import org.junit.Test 8 | 9 | import java.security.KeyStore 10 | 11 | import static org.mockito.Mockito.mock 12 | 13 | class KeyStoreCertificateSourceTest { 14 | 15 | KeyStore mockKeyStore = mock(KeyStore) 16 | 17 | // the happy-path test cases are already covered implicitly as part of KeyStoreFileCertificateSourceTest, so just test negative cases 18 | 19 | @Test(expected = IllegalArgumentException) 20 | void testMustSupplyKeystore() { 21 | KeyStoreCertificateSource keyStoreCertificateSource = new KeyStoreCertificateSource(null, "privatekey", "password") 22 | keyStoreCertificateSource.load() 23 | } 24 | 25 | @Test(expected = IllegalArgumentException) 26 | void testMustSupplyPassword() { 27 | KeyStoreCertificateSource keyStoreCertificateSource = new KeyStoreCertificateSource(mockKeyStore, "privatekey", null) 28 | keyStoreCertificateSource.load() 29 | } 30 | 31 | @Test(expected = IllegalArgumentException) 32 | void testMustSupplyPrivateKeyAlias() { 33 | KeyStoreCertificateSource keyStoreCertificateSource = new KeyStoreCertificateSource(mockKeyStore, null, "password") 34 | keyStoreCertificateSource.load() 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/test/groovy/com/browserup/bup/mitm/tools/ECKeyGeneratorTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm.tools 6 | 7 | import com.browserup.bup.mitm.keys.ECKeyGenerator 8 | import org.junit.Test 9 | 10 | import java.security.KeyPair 11 | 12 | import static org.junit.Assert.assertNotNull 13 | 14 | class ECKeyGeneratorTest { 15 | @Test 16 | void testGenerateWithDefaults() { 17 | ECKeyGenerator keyGenerator = new ECKeyGenerator() 18 | KeyPair keyPair = keyGenerator.generate() 19 | 20 | assertNotNull(keyPair) 21 | } 22 | 23 | @Test 24 | void testGenerateWithExplicitNamedCurve() { 25 | ECKeyGenerator keyGenerator = new ECKeyGenerator("secp384r1") 26 | KeyPair keyPair = keyGenerator.generate() 27 | 28 | assertNotNull(keyPair) 29 | // not much else to verify, other than successful generation 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/test/groovy/com/browserup/bup/mitm/tools/RSAKeyGeneratorTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm.tools 6 | 7 | import com.browserup.bup.mitm.keys.RSAKeyGenerator 8 | import org.junit.Test 9 | 10 | import java.security.KeyPair 11 | 12 | import static org.junit.Assert.assertNotNull 13 | 14 | class RSAKeyGeneratorTest { 15 | @Test 16 | void testGenerateWithDefaults() { 17 | RSAKeyGenerator keyGenerator = new RSAKeyGenerator() 18 | KeyPair keyPair = keyGenerator.generate() 19 | 20 | assertNotNull(keyPair) 21 | } 22 | 23 | @Test 24 | void testGenerateWithExplicitKeySize() { 25 | RSAKeyGenerator keyGenerator = new RSAKeyGenerator(1024) 26 | KeyPair keyPair = keyGenerator.generate() 27 | 28 | assertNotNull(keyPair) 29 | // not much else to verify, other than successful generation 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/test/java/com/browserup/bup/mitm/example/CustomCAKeyStoreExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm.example; 6 | 7 | import com.browserup.bup.mitm.KeyStoreFileCertificateSource; 8 | import com.browserup.bup.mitm.manager.ImpersonatingMitmManager; 9 | import org.littleshoot.proxy.HttpProxyServer; 10 | import org.littleshoot.proxy.impl.DefaultHttpProxyServer; 11 | 12 | import java.io.File; 13 | 14 | /** 15 | * This example creates an ImpersonatingMitmManager which loads the CA Root Certificate and Private Key 16 | * from a custom KeyStore. 17 | */ 18 | public class CustomCAKeyStoreExample { 19 | public static void main(String[] args) { 20 | // load the root certificate and private key from an existing KeyStore 21 | KeyStoreFileCertificateSource fileCertificateSource = new KeyStoreFileCertificateSource( 22 | "PKCS12", // KeyStore type. for .jks files (Java KeyStore), use "JKS" 23 | new File("/path/to/my/keystore.p12"), 24 | "keyAlias", // alias of the private key in the KeyStore; if you did not specify an alias when creating it, use "1" 25 | "keystorePassword"); 26 | 27 | 28 | // tell the MitmManager to use the custom certificate and private key 29 | ImpersonatingMitmManager mitmManager = ImpersonatingMitmManager.builder() 30 | .rootCertificateSource(fileCertificateSource) 31 | .build(); 32 | 33 | // tell the HttpProxyServerBootstrap to use the new MitmManager 34 | HttpProxyServer proxyServer = DefaultHttpProxyServer.bootstrap() 35 | .withManInTheMiddle(mitmManager) 36 | .start(); 37 | 38 | // make your requests to the proxy server 39 | //... 40 | 41 | proxyServer.abort(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/test/java/com/browserup/bup/mitm/example/CustomCAPemFileExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm.example; 6 | 7 | import com.browserup.bup.mitm.PemFileCertificateSource; 8 | import com.browserup.bup.mitm.manager.ImpersonatingMitmManager; 9 | import org.littleshoot.proxy.HttpProxyServer; 10 | import org.littleshoot.proxy.impl.DefaultHttpProxyServer; 11 | 12 | import java.io.File; 13 | 14 | /** 15 | * This example creates an ImpersonatingMitmManager which loads the CA Root Certificate and Private Key 16 | * from a PEM-encoded certificate and a PEM-encoded private key file. 17 | */ 18 | public class CustomCAPemFileExample { 19 | public static void main(String[] args) { 20 | // load the root certificate and private key from existing PEM-encoded certificate and private key files 21 | PemFileCertificateSource fileCertificateSource = new PemFileCertificateSource( 22 | new File("/path/to/my/certificate.cer"), // the PEM-encoded certificate file 23 | new File("/path/to/my/private-key.pem"), // the PEM-encoded private key file 24 | "privateKeyPassword"); // the password for the private key -- can be null if the private key is not encrypted 25 | 26 | 27 | // tell the MitmManager to use the custom certificate and private key 28 | ImpersonatingMitmManager mitmManager = ImpersonatingMitmManager.builder() 29 | .rootCertificateSource(fileCertificateSource) 30 | .build(); 31 | 32 | // tell the HttpProxyServerBootstrap to use the new MitmManager 33 | HttpProxyServer proxyServer = DefaultHttpProxyServer.bootstrap() 34 | .withManInTheMiddle(mitmManager) 35 | .start(); 36 | 37 | // make your requests to the proxy server 38 | //... 39 | 40 | proxyServer.abort(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/test/java/com/browserup/bup/mitm/example/LittleProxyDefaultConfigExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm.example; 6 | 7 | import com.browserup.bup.mitm.manager.ImpersonatingMitmManager; 8 | import org.littleshoot.proxy.HttpProxyServer; 9 | import org.littleshoot.proxy.impl.DefaultHttpProxyServer; 10 | 11 | /** 12 | * This example creates an ImpersonatingMitmManager with all-default settings: 13 | * - Dynamically-generated CA Root Certificate and Private Key (2048-bit RSA. SHA512 signature) 14 | * - Server certificate impersonation by domain name (2048-bit RSA, SHA512 signature) 15 | * - Default Java trust store (upstream servers' certificates validated against Java's trusted CAs) 16 | */ 17 | public class LittleProxyDefaultConfigExample { 18 | public static void main(String[] args) { 19 | // initialize an MitmManager with default settings 20 | ImpersonatingMitmManager mitmManager = ImpersonatingMitmManager.builder().build(); 21 | 22 | // to save the generated CA certificate for installation in a browser, see SaveGeneratedCAExample.java 23 | 24 | // tell the HttpProxyServerBootstrap to use the new MitmManager 25 | HttpProxyServer proxyServer = DefaultHttpProxyServer.bootstrap() 26 | .withManInTheMiddle(mitmManager) 27 | .start(); 28 | 29 | // make your requests to the proxy server 30 | //... 31 | 32 | proxyServer.abort(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/test/java/com/browserup/bup/mitm/example/SaveGeneratedCAExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm.example; 6 | 7 | import com.browserup.bup.mitm.RootCertificateGenerator; 8 | import com.browserup.bup.mitm.manager.ImpersonatingMitmManager; 9 | import org.littleshoot.proxy.HttpProxyServer; 10 | import org.littleshoot.proxy.impl.DefaultHttpProxyServer; 11 | 12 | import java.io.File; 13 | 14 | /** 15 | * This example creates an ImpersonatingMitmManager with all-default settings and saves the dynamically generated 16 | * CA Root Certificate as a PEM file for installation in a browser. 17 | */ 18 | public class SaveGeneratedCAExample { 19 | public static void main(String[] args) { 20 | // create a dynamic CA root certificate generator using default settings (2048-bit RSA keys) 21 | RootCertificateGenerator rootCertificateGenerator = RootCertificateGenerator.builder().build(); 22 | 23 | // save the dynamically-generated CA root certificate for installation in a browser 24 | rootCertificateGenerator.saveRootCertificateAsPemFile(new File("/tmp/my-dynamic-ca.cer")); 25 | 26 | // tell the MitmManager to use the root certificate we just generated 27 | ImpersonatingMitmManager mitmManager = ImpersonatingMitmManager.builder() 28 | .rootCertificateSource(rootCertificateGenerator) 29 | .build(); 30 | 31 | // tell the HttpProxyServerBootstrap to use the new MitmManager 32 | HttpProxyServer proxyServer = DefaultHttpProxyServer.bootstrap() 33 | .withManInTheMiddle(mitmManager) 34 | .start(); 35 | 36 | // make your requests to the proxy server 37 | //... 38 | 39 | proxyServer.abort(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/test/java/com/browserup/bup/mitm/test/util/CertificateTestUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.mitm.test.util; 6 | 7 | import com.browserup.bup.mitm.CertificateAndKey; 8 | 9 | import java.security.PrivateKey; 10 | import java.security.cert.X509Certificate; 11 | 12 | import static org.junit.Assert.assertEquals; 13 | import static org.junit.Assert.assertNotNull; 14 | 15 | /** 16 | * Utility methods for X.509 certificate verification in unit tests. 17 | */ 18 | public class CertificateTestUtil { 19 | /** 20 | * Asserts that the specified {@link CertificateAndKey} contains an RSA private key and an X.509 certificate 21 | * with CN="littleproxy-test" and O="LittleProxy test". 22 | */ 23 | public static void verifyTestRSACertWithCNandO(CertificateAndKey certificateAndKey) { 24 | X509Certificate certificate = certificateAndKey.getCertificate(); 25 | assertNotNull(certificate); 26 | assertNotNull(certificate.getIssuerDN()); 27 | assertEquals("CN=littleproxy-test, O=LittleProxy test", certificate.getIssuerDN().getName()); 28 | 29 | PrivateKey privateKey = certificateAndKey.getPrivateKey(); 30 | assertNotNull(privateKey); 31 | assertEquals("RSA", privateKey.getAlgorithm()); 32 | } 33 | 34 | /** 35 | * Asserts that the specified {@link CertificateAndKey} contains an RSA private key and an X.509 certificate 36 | * with CN="littleproxy-test". 37 | */ 38 | public static void verifyTestRSACertWithCN(CertificateAndKey certificateAndKey) { 39 | X509Certificate certificate = certificateAndKey.getCertificate(); 40 | assertNotNull(certificate); 41 | assertNotNull(certificate.getIssuerDN()); 42 | assertEquals("CN=littleproxy-test", certificate.getIssuerDN().getName()); 43 | 44 | PrivateKey privateKey = certificateAndKey.getPrivateKey(); 45 | assertNotNull(privateKey); 46 | assertEquals("RSA", privateKey.getAlgorithm()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/test/resources/com/browserup/bup/mitm/certificate.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDPzCCAiegAwIBAgIJALPOOC+0RqAQMA0GCSqGSIb3DQEBCwUAMDYxGTAXBgNV 3 | BAoMEExpdHRsZVByb3h5IHRlc3QxGTAXBgNVBAMMEGxpdHRsZXByb3h5LXRlc3Qw 4 | HhcNMTUxMjE4MDIxMDI4WhcNMjUxMjE1MDIxMDI4WjA2MRkwFwYDVQQKDBBMaXR0 5 | bGVQcm94eSB0ZXN0MRkwFwYDVQQDDBBsaXR0bGVwcm94eS10ZXN0MIIBIjANBgkq 6 | hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2vcm/qETdg92EjYka3Zt+UEraLKzTovz 7 | gvVx4F0EmCC3KDU2v2+WeX4wmVjP6qtMr6nJOSYkFedAvIRGi7hbf3JKQ1nqhH1q 8 | U2zP9HXNt+/LCw8kOFJFii9cp6/h8OnlF8hoIWz4lRTMMcjoiBzV5vfyTb2zWE0l 9 | 1DXGypKTxjqg8Pd/tqsMl2uc4+xnEL/ZK9cK8wxg0suUqaGQRaX2R3SovbFKZ1c3 10 | VApS8zHksSm6qQStbisuEEHSYLpFCrMnXsLJ9KfzTUDwBqrKaMyDAEjoS2LpoijF 11 | YalTW3bF721GiYl2GtcwCIzqpHcIbHAPn1PxQY4UHUt3z4YSjTsuXwIDAQABo1Aw 12 | TjAdBgNVHQ4EFgQUElRcE71sJsuWUVOyYALgQQhPHUUwHwYDVR0jBBgwFoAUElRc 13 | E71sJsuWUVOyYALgQQhPHUUwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC 14 | AQEArU29g5/V5rywfQizo5J/XEbWjfN0zvuFithS7Zc2OqrSKV8c3NY6NdER/0jj 15 | CmhohnUkduMW+/bizWcuiyt5KDwg6Ar2kEXYY0YgOLbxTmoNvBG84zO+54BV0qy7 16 | 16D8ItrPbcIn83eW8TXug1dWISxWhVRIpHdH2Ok4/XDRFgTxORsC08v2OwwQ566g 17 | PqphR6eIJN8MwThcT9D9rv0HcX9esBJJlNc9OigB9T3O2Xg7+qPJtgqktkN8vlTb 18 | Co0BWoR43xVPTNam533ioUX7woUWnlsQtclN//vazr+uofJ8jnHjpAkh9gZ/l6kn 19 | AdZVrExA+SSUqtWgk9cD0WGSEQ== 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/test/resources/com/browserup/bup/mitm/encrypted-private-key.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: AES-256-CBC,2D5055AA227DC3421AEBE5C8480F57B1 4 | 5 | VZnrQPjan9fUvkJVKwJrJLWqWx7qgw9M8eeqZoqjrueZLNFu74mp2qbKeJ4hkbqf 6 | dBvMuBaSaqrS2SxfHf/T8uZg+K0vMB0hH+1pVt+AtJRYLybQLmjZSnU/8CDEp358 7 | 59FX5rYFvMRD3fUkESWeQSlSDuE/YG5YlqmxnSVOKEmAQQ0QTGEDIDKl77gRrg7r 8 | mr6ey8NgW2OBaJvLbXLAldHd2lBlj/NH7sXwWq1LgbF/CMm5LLtIEMkOSZ5jgFvU 9 | UqMOTTSGrhtPa4vvy1m8XFx9Z33usi/mEvXQpVeLCmE4BQkS7rSMV1E/ETVkzsfF 10 | wq3c6cWNmh/JvPYUIa6dXjDZPwCz/WpVH7fVwTqBkBTbTUuIJSP//jJB7npZoQrE 11 | rfpdJ9eX9QAjbFI6goYnc4SVWhpzR9LDSk9mpEikGysN8yDYabKbl0OzT9KovGn0 12 | CLKYxo2LQBwGJR171y5yyC/3s39b9pPC6JQAzji4S7uJQjayYx8Yv3xb1f+4t2Xa 13 | /3M46QtAUJMGAqdmpTXhvNzPyGzJFK1gwNoUf6oM/Vww8KRvVNJTfPljEARxTyRO 14 | jzan4Dd5jtSqw87E2b76ECrj92C7qp3iC/4zkkCHTulPMjwvIzY9WQ2wx1qyVy9B 15 | 4qwat9h90AnrAX9q6kvDTglD3n605vgU6P/u1PCzTsTLT1DL5OyMfT3MVSS/MyNs 16 | SKLx5vJh93aq7EyWurWVCzUfYDIFngb+WFINEGb2XYFD3Ah/y5D6kD24vooATgFJ 17 | KQBQdCBWWjZbYS4nZUTOSJqSMp5V/RzjjKr9QpCKpXLvxwfQ9ME6MaobgfUSy1er 18 | S+btHNItD/xA7/rO/eAfmlkFLJgGi6cfvnyBxHsR7+/T5yDyZAPbDwp8kErxFFeY 19 | xqQmY6hvaSiQXek23AIiTOCuzyYluMsTnxRfQGlL/A77R+t7gJv5in0FPQqHzD1T 20 | h5V4fIBcpOSmc6Qk7U7vYNi3AyvpWDsr03E+bNlY5dzNguxsO9QzJMWGd17VZiwc 21 | Q2qkKxl5JC0c3h4mYh4e3V5C0c9z32ffYS6sICsNaAw0r8C2svULBCtm29oDDc5X 22 | oC5EzfZb5vG9qpQ8H/LE2I215YOwsvmda9gdpnxrbk4y8MlSMOJwMXVYGKNSEg2h 23 | wWPo/4qsNV8hX7IlOYPOnVRCzpjCWTc2CzvhHISxv1CrXKV0D2qAxkX/ezRYsaiE 24 | T7E0K5kyXquVjhjnUTMjpOc/LM2bQ9v0lmmJIcnJFSlCTS5+DvUrngYSnqfyPgkS 25 | 7dbSuW8E1UusxnY8664snWugxCLB4E0PacKTr+0E77qukFAE02j8hwHbn+TplWaH 26 | jOVSyLicsqwi0TTxWoWk/fTk6StSGVuUTaPPoz6gkrN3H01V8vhcx/PPqWWbfjjz 27 | yGqOm6pXXaNwPYjgQUWFPc3QHOaPbTAl1Y4teCaAqnSDlEBYqDUGK0gSMAQ/vgdC 28 | uvUAGctb2Kikp4sQcvzKRHsStHrHDVdeum842r5zyHKMuEbhTGjmDR70FknpDaoS 29 | vQIfgasKqXjRKgQfSSiGTX5CsvYvSRqFSD4qDbQbYnR0XMNphJxK/3rWP86oEXk1 30 | -----END RSA PRIVATE KEY----- 31 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/test/resources/com/browserup/bup/mitm/keystore.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserup/browserup-proxy/0f28ec6e8f1423e1fdb014fded72e9278bdd083f/browserup-proxy-mitm/src/test/resources/com/browserup/bup/mitm/keystore.jks -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/test/resources/com/browserup/bup/mitm/keystore.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserup/browserup-proxy/0f28ec6e8f1423e1fdb014fded72e9278bdd083f/browserup-proxy-mitm/src/test/resources/com/browserup/bup/mitm/keystore.p12 -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/test/resources/com/browserup/bup/mitm/trusted-cert.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserup/browserup-proxy/0f28ec6e8f1423e1fdb014fded72e9278bdd083f/browserup-proxy-mitm/src/test/resources/com/browserup/bup/mitm/trusted-cert.jks -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/test/resources/com/browserup/bup/mitm/unencrypted-private-key.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA2vcm/qETdg92EjYka3Zt+UEraLKzTovzgvVx4F0EmCC3KDU2 3 | v2+WeX4wmVjP6qtMr6nJOSYkFedAvIRGi7hbf3JKQ1nqhH1qU2zP9HXNt+/LCw8k 4 | OFJFii9cp6/h8OnlF8hoIWz4lRTMMcjoiBzV5vfyTb2zWE0l1DXGypKTxjqg8Pd/ 5 | tqsMl2uc4+xnEL/ZK9cK8wxg0suUqaGQRaX2R3SovbFKZ1c3VApS8zHksSm6qQSt 6 | bisuEEHSYLpFCrMnXsLJ9KfzTUDwBqrKaMyDAEjoS2LpoijFYalTW3bF721GiYl2 7 | GtcwCIzqpHcIbHAPn1PxQY4UHUt3z4YSjTsuXwIDAQABAoIBAB/Opyt12o3b0Rr0 8 | InY5zd/XR6b9zm4qhkUPwmsFGBXBKtn8YOeOHh2n5wdfj1RXbdxWnZRfpf5IiW7Z 9 | CCZjsWbiA0elWBvG3BsiQ1MPicKeYrBIkspbqR5Zouv48Kk+ULkTs4ynd7SwQLk6 10 | pgyfo7LZcak5VUQOcOBSr33drPmuZcTyozuEs+XmOaUIH9SHr5dLP3mysb7dEZQS 11 | gaHMR1a3BQQrQk9H5oVVU0kI0iqqX5NltiWodl3shUHqlXrlCEop+RxHhkfBgz8H 12 | w4MlyFSiYrrzTyL7yTkMa6JTcP0s4oNH2E+foIMyf46z/lxH7Nk0bg4pmsfXb0o7 13 | bKqJzAECgYEA8kRvRpDB9z6ddQkJ1I++fGh8vJMUuB6MNlS7Shhp6ClHQwdUK80f 14 | dpWT8CxIOX6sKJgm9HGbxaPxn7RnYc4v+M0L/QsPU8RGfVggNvQ9xQEaocp3CD9D 15 | TUeZUtTcTmKd86h9ICjlkbtF2coKNcIfM3teVtGnMKjf5VowupYyzj8CgYEA52CU 16 | 6Su4ewgTcEPRqWWyg/GHTPN8TJaXjutgvhnt7w8po8xMmIK1qSeOP4paF/UOuGsT 17 | rX6IoXmSzSz5MkFkjGLBd/wUd4p9YQbgjtv/lv52XBZ8mqffjDGTGl/n0r803yMH 18 | So7Fup9f8kXkgw+vszxzCqnHq3usx1WjCKNj1+ECgYAT1wTh24L283rDldznOmpY 19 | F9p3OvhMZ7wFywSXec5ag97hH12GRMMZ3AAEgCveAYCpxmQSSqd+FQH5mTWKLe+B 20 | yZD8xQYZTw6Svz/MIE5ars92hnUfCMdDMeTdgq8UAEF9LcQpeQ/r0lFTF5ekdWRG 21 | vAiqxXqSopHLX4p0DU7V0wKBgGi16c44Tg3H0tw8pPbPomFZ/gxSKM+UW1R/q1F8 22 | 9JP6vbJ2M7fVd5bs4tBYsXskGRxWwRoEKJtDJK+cCc63j2SFEN9XAoAy+ZjefuPI 23 | JjxUPoZgWtW24VFV4ifOfWB/zdKpzJPuVwelNsuy276Aa9hmo/2QZl9x4fh4BgdT 24 | wkyhAoGBAKrzqA0+kotqocA9cT7GuJb7mfjewXZx1jztQMC5mHs+L/tRGL85dZZk 25 | 5hK8Rtessw5TKmfA53bKbOEklEcsjSNWgIq3wYjJVtBDUvcmA2Hgm3psjUCrl1iv 26 | h/31bf05fYaWFFEeuHE/Pz5ev8ecY8gsKtX63HVVWLYtDxKnYuXk 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /browserup-proxy-mitm/src/test/resources/log4j2-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "configuration" : { 3 | "name": "test", 4 | "appenders": { 5 | "Console": { 6 | "name": "console", 7 | "target": "SYSTEM_OUT", 8 | "PatternLayout": { 9 | "pattern": "%-7r %date %level [%thread] %logger - %msg%n" 10 | } 11 | } 12 | }, 13 | 14 | "loggers": { 15 | "root": { 16 | "level": "info", 17 | "appender-ref": { 18 | "ref": "console" 19 | } 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /browserup-proxy-rest-clients/src/main/resources/openapi-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourcePackages" : [ 3 | "com.browserup.bup.rest.resource" 4 | ], 5 | "openapi" : "3.0.1", 6 | "info": { 7 | "version": "1.0", 8 | "title": "BrowserUp Proxy API", 9 | "description": "BrowserUp Proxy API", 10 | "contact": { 11 | "email": "hello@browserup.com" 12 | }, 13 | "license": { 14 | "name": "Apache 2.0", 15 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /browserup-proxy-rest-clients/src/test/javascript/.gitignore: -------------------------------------------------------------------------------- 1 | client/ -------------------------------------------------------------------------------- /browserup-proxy-rest-clients/src/test/javascript/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8.16.0-alpine 2 | 3 | USER root 4 | 5 | WORKDIR / 6 | 7 | COPY ./client/ /client/ 8 | 9 | # Build javascript client, install locally 10 | WORKDIR /client/ 11 | RUN rm -rf node_modules/ 12 | RUN npm install 13 | RUN npm link 14 | RUN npm link /client 15 | RUN npm run build 16 | 17 | COPY . /javascript/ 18 | WORKDIR /javascript/ 19 | 20 | CMD ["node", "test/javascript_test.js"] -------------------------------------------------------------------------------- /browserup-proxy-rest-clients/src/test/javascript/test/javascript_test.js: -------------------------------------------------------------------------------- 1 | var BrowserUpProxyApi = require('/client/node_modules/browser_up_proxy_api'); 2 | 3 | 4 | var api = new BrowserUpProxyApi.DefaultApi() 5 | api.apiClient.basePath = 'http://' + process.env.PROXY_REST_HOST + ':' + process.env.PROXY_REST_PORT 6 | var port = process.env.PROXY_PORT; 7 | var urlPattern = ".*"; 8 | var callback = function(error, data, response) { 9 | if (error) { 10 | console.error('Error while calling API: ' + JSON.stringify(error)); 11 | throw new Error(error); 12 | } else { 13 | console.log('API called successfully. Returned data: ' + JSON.stringify(data)); 14 | } 15 | }; 16 | api.entries(port, urlPattern, callback); 17 | -------------------------------------------------------------------------------- /browserup-proxy-rest-clients/src/test/python/.gitignore: -------------------------------------------------------------------------------- 1 | client/ -------------------------------------------------------------------------------- /browserup-proxy-rest-clients/src/test/python/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:2.7-alpine 2 | 3 | WORKDIR / 4 | 5 | COPY client/ /python-client/ 6 | 7 | # Build python client, install locally 8 | WORKDIR /python-client/ 9 | RUN python setup.py install --user 10 | RUN pip install -r requirements.txt 11 | RUN pip install -r test-requirements.txt 12 | 13 | COPY . /python/ 14 | WORKDIR /python/ 15 | 16 | CMD ["python", "test/python_test.py"] -------------------------------------------------------------------------------- /browserup-proxy-rest-clients/src/test/python/test/python_test.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import time 3 | import openapi_client 4 | import os 5 | 6 | from openapi_client.rest import ApiException 7 | from pprint import pprint 8 | 9 | # Create an instance of the API class 10 | api_client = openapi_client.ApiClient() 11 | api_client.configuration.host = 'http://' + os.environ['PROXY_REST_HOST'] + ':' + os.environ['PROXY_REST_PORT'] 12 | 13 | api_instance = openapi_client.DefaultApi(api_client) 14 | port = os.environ['PROXY_PORT'] 15 | url_pattern = '.*' 16 | 17 | try: 18 | api_response = api_instance.entries(port, url_pattern) 19 | pprint(api_response) 20 | except ApiException as e: 21 | print("Exception when calling DefaultApi->entries: %s\n" % e) 22 | raise -------------------------------------------------------------------------------- /browserup-proxy-rest-clients/src/test/resources/log4j2-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "configuration" : { 3 | "name": "test", 4 | "appenders": { 5 | "Console": { 6 | "name": "console", 7 | "target": "SYSTEM_OUT", 8 | "PatternLayout": { 9 | "pattern": "%-7r %date %level [%thread] %logger - %msg%n" 10 | } 11 | } 12 | }, 13 | 14 | "loggers": { 15 | "logger": [ 16 | { 17 | "name": "org.testcontainers", 18 | "level": "warn", 19 | "additivity": false, 20 | "AppenderRef": { 21 | "ref": "console" 22 | } 23 | }, 24 | { 25 | "name": "com.github.dockerjava", 26 | "level": "warn", 27 | "additivity": false, 28 | "AppenderRef": { 29 | "ref": "console" 30 | } 31 | } 32 | ], 33 | "root": { 34 | "level": "info", 35 | "appender-ref": { 36 | "ref": "console" 37 | } 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /browserup-proxy-rest-clients/src/test/ruby/.gitignore: -------------------------------------------------------------------------------- 1 | client/ -------------------------------------------------------------------------------- /browserup-proxy-rest-clients/src/test/ruby/.rspec: -------------------------------------------------------------------------------- 1 | --color -------------------------------------------------------------------------------- /browserup-proxy-rest-clients/src/test/ruby/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.5 2 | 3 | RUN bundle config --global frozen 1 4 | 5 | WORKDIR / 6 | 7 | COPY ./client/ /ruby-client/ 8 | 9 | # Build ruby client gem, install locally 10 | WORKDIR /ruby-client/ 11 | RUN gem build openapi_client.gemspec 12 | RUN gem install ./openapi_client-1.0.0.gem 13 | 14 | COPY . /ruby/ 15 | WORKDIR /ruby/ 16 | 17 | RUN gem install bundler 18 | RUN bundle update --bundler 19 | RUN bundle config --delete frozen 20 | RUN bundle install 21 | 22 | CMD ["bundle", "exec", "rspec", "--backtrace"] 23 | -------------------------------------------------------------------------------- /browserup-proxy-rest-clients/src/test/ruby/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | group :development, :test do 4 | gem 'rake', '~> 13.0.1' 5 | gem 'pry-byebug' 6 | gem 'json', '~> 2.4.0' 7 | gem 'rspec', '~> 3.10' 8 | gem 'rubocop', '~> 1.6.1' 9 | gem 'openapi_client', '~> 1.0.0' 10 | end 11 | -------------------------------------------------------------------------------- /browserup-proxy-rest-clients/src/test/ruby/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | ast (2.4.1) 5 | byebug (11.1.3) 6 | coderay (1.1.3) 7 | diff-lcs (1.4.4) 8 | ethon (0.12.0) 9 | ffi (>= 1.3.0) 10 | ffi (1.13.1) 11 | json (2.4.0) 12 | method_source (1.0.0) 13 | openapi_client (1.0.0) 14 | json (~> 2.1, >= 2.1.0) 15 | typhoeus (~> 1.0, >= 1.0.1) 16 | parallel (1.20.1) 17 | parser (2.7.2.0) 18 | ast (~> 2.4.1) 19 | pry (0.13.1) 20 | coderay (~> 1.1) 21 | method_source (~> 1.0) 22 | pry-byebug (3.9.0) 23 | byebug (~> 11.0) 24 | pry (~> 0.13.0) 25 | rainbow (3.0.0) 26 | rake (13.0.1) 27 | regexp_parser (2.0.0) 28 | rexml (3.2.4) 29 | rspec (3.10.0) 30 | rspec-core (~> 3.10.0) 31 | rspec-expectations (~> 3.10.0) 32 | rspec-mocks (~> 3.10.0) 33 | rspec-core (3.10.0) 34 | rspec-support (~> 3.10.0) 35 | rspec-expectations (3.10.0) 36 | diff-lcs (>= 1.2.0, < 2.0) 37 | rspec-support (~> 3.10.0) 38 | rspec-mocks (3.10.0) 39 | diff-lcs (>= 1.2.0, < 2.0) 40 | rspec-support (~> 3.10.0) 41 | rspec-support (3.10.0) 42 | rubocop (1.6.1) 43 | parallel (~> 1.10) 44 | parser (>= 2.7.1.5) 45 | rainbow (>= 2.2.2, < 4.0) 46 | regexp_parser (>= 1.8, < 3.0) 47 | rexml 48 | rubocop-ast (>= 1.2.0, < 2.0) 49 | ruby-progressbar (~> 1.7) 50 | unicode-display_width (>= 1.4.0, < 2.0) 51 | rubocop-ast (1.3.0) 52 | parser (>= 2.7.1.5) 53 | ruby-progressbar (1.10.1) 54 | typhoeus (1.4.0) 55 | ethon (>= 0.9.0) 56 | unicode-display_width (1.7.0) 57 | 58 | PLATFORMS 59 | ruby 60 | 61 | DEPENDENCIES 62 | json (~> 2.4.0) 63 | openapi_client (~> 1.0.0) 64 | pry-byebug 65 | rake (~> 13.0.1) 66 | rspec (~> 3.10) 67 | rubocop (~> 1.6.1) 68 | 69 | BUNDLED WITH 70 | 2.1.4 71 | -------------------------------------------------------------------------------- /browserup-proxy-rest-clients/src/test/ruby/spec/test_client_spec.rb: -------------------------------------------------------------------------------- 1 | require 'openapi_client' 2 | 3 | describe OpenapiClient do 4 | it 'connects to api' do 5 | proxy_rest_host = ENV["PROXY_REST_HOST"] 6 | proxy_rest_port = ENV["PROXY_REST_PORT"] 7 | proxy_port = ENV["PROXY_PORT"] 8 | 9 | p "Using the following env variables:" 10 | p "PROXY_REST_HOST = #{proxy_rest_host}" 11 | p "PROXY_REST_PORT = #{proxy_rest_port}" 12 | p "PROXY_PORT = #{proxy_port}" 13 | 14 | api_instance = OpenapiClient::DefaultApi.new 15 | api_instance.api_client.config.host = "#{proxy_rest_host}:#{proxy_rest_port}" 16 | port = proxy_port 17 | url_pattern = '^.*$' 18 | 19 | begin 20 | entries_response = api_instance.entries(port, url_pattern).to_json 21 | p "Got the following entries in the response: #{entries_response}" 22 | rescue OpenapiClient::ApiError => e 23 | puts "Exception when calling DefaultApi->entries: #{e}" 24 | raise 25 | end 26 | end 27 | end -------------------------------------------------------------------------------- /browserup-proxy-rest/.classpath: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /browserup-proxy-rest/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | browserup-proxy-rest 4 | Project browserup-proxy-rest created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.buildship.core.gradleprojectnature 22 | 23 | 24 | -------------------------------------------------------------------------------- /browserup-proxy-rest/src/main/java/com/browserup/bup/exception/JavascriptCompilationException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.exception; 6 | 7 | import com.google.sitebricks.headless.Request; 8 | import com.browserup.bup.filters.JavascriptRequestResponseFilter; 9 | 10 | /** 11 | * Indicates that an error occurred when compiling javascript in {@link JavascriptRequestResponseFilter}, 12 | * for use by {@link com.browserup.bup.proxy.bricks.ProxyResource#addRequestFilter(int, Request)} 13 | * or {@link com.browserup.bup.proxy.bricks.ProxyResource#addResponseFilter(int, Request)}. 14 | */ 15 | public class JavascriptCompilationException extends RuntimeException { 16 | public JavascriptCompilationException() { 17 | super(); 18 | } 19 | 20 | public JavascriptCompilationException(String message) { 21 | super(message); 22 | } 23 | 24 | public JavascriptCompilationException(String message, Throwable cause) { 25 | super(message, cause); 26 | } 27 | 28 | public JavascriptCompilationException(Throwable cause) { 29 | super(cause); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /browserup-proxy-rest/src/main/java/com/browserup/bup/exception/ProxyExistsException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.exception; 6 | 7 | public class ProxyExistsException extends RuntimeException { 8 | private static final long serialVersionUID = -5515796684778166504L; 9 | 10 | private final int port; 11 | 12 | public ProxyExistsException(int port) { 13 | this.port = port; 14 | } 15 | 16 | public ProxyExistsException(String message, int port) { 17 | super(message); 18 | this.port = port; 19 | } 20 | 21 | public ProxyExistsException(String message, Throwable cause, int port) { 22 | super(message, cause); 23 | this.port = port; 24 | } 25 | 26 | public ProxyExistsException(Throwable cause, int port) { 27 | super(cause); 28 | this.port = port; 29 | } 30 | 31 | public int getPort() { 32 | return port; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /browserup-proxy-rest/src/main/java/com/browserup/bup/exception/ProxyPortsExhaustedException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.exception; 6 | 7 | public class ProxyPortsExhaustedException extends RuntimeException { 8 | private static final long serialVersionUID = -6801448612785792233L; 9 | 10 | public ProxyPortsExhaustedException() { 11 | super(); 12 | } 13 | 14 | public ProxyPortsExhaustedException(String message, Throwable cause) { 15 | super(message, cause); 16 | } 17 | 18 | public ProxyPortsExhaustedException(String message) { 19 | super(message); 20 | } 21 | 22 | public ProxyPortsExhaustedException(Throwable cause) { 23 | super(cause); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /browserup-proxy-rest/src/main/java/com/browserup/bup/proxy/guice/JettyModule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.proxy.guice; 6 | 7 | import com.google.inject.Binder; 8 | import com.google.inject.Module; 9 | import org.eclipse.jetty.server.Server; 10 | 11 | public class JettyModule implements Module { 12 | @Override 13 | public void configure(Binder binder) { 14 | binder.bind(Server.class).toProvider(JettyServerProvider.class); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /browserup-proxy-rest/src/main/java/com/browserup/bup/proxy/guice/NamedImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.proxy.guice; 6 | 7 | import com.google.inject.name.Named; 8 | 9 | import java.lang.annotation.Annotation; 10 | 11 | public class NamedImpl implements Named { 12 | final String value; 13 | 14 | public NamedImpl(String value) { 15 | this.value = value == null ? "name" : value; 16 | } 17 | 18 | public String value() { 19 | return this.value; 20 | } 21 | 22 | public int hashCode() { 23 | // This is specified in java.lang.Annotation. 24 | return 127 * "value".hashCode() ^ value.hashCode(); 25 | } 26 | 27 | public boolean equals(Object o) { 28 | if (!(o instanceof Named)) { 29 | return false; 30 | } 31 | 32 | Named other = (Named) o; 33 | return value.equals(other.value()); 34 | } 35 | 36 | public String toString() { 37 | return "@" + Named.class.getName() + "(value=" + value + ")"; 38 | } 39 | 40 | public Class annotationType() { 41 | return Named.class; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /browserup-proxy-rest/src/main/java/com/browserup/bup/rest/filter/LoggingFilter.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.rest.filter; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import javax.ws.rs.container.ContainerRequestContext; 7 | import javax.ws.rs.container.ContainerRequestFilter; 8 | 9 | public class LoggingFilter implements ContainerRequestFilter { 10 | private static final Logger LOG = LoggerFactory.getLogger(LoggingFilter.class); 11 | 12 | @Override 13 | public void filter(ContainerRequestContext requestContext) { 14 | LOG.info(String.format("%s /%s", requestContext.getMethod().toUpperCase(), requestContext.getUriInfo().getPath())); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /browserup-proxy-rest/src/main/java/com/browserup/bup/rest/validation/NotBlankConstraint.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.rest.validation; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.validation.Constraint; 7 | import javax.validation.ConstraintValidator; 8 | import javax.validation.ConstraintValidatorContext; 9 | import javax.validation.Payload; 10 | 11 | import com.browserup.bup.rest.validation.util.MessageSanitizer; 12 | import org.apache.commons.lang3.StringUtils; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Constraint(validatedBy = { NotBlankConstraint.NotBlankValidator.class }) 18 | public @interface NotBlankConstraint { 19 | 20 | String message() default ""; 21 | 22 | String paramName() default ""; 23 | 24 | Class[] groups() default {}; 25 | 26 | Class[] payload() default {}; 27 | 28 | class NotBlankValidator implements ConstraintValidator { 29 | private static final Logger LOG = LoggerFactory.getLogger(NotBlankValidator.class); 30 | 31 | @Override 32 | public void initialize(NotBlankConstraint constraintAnnotation) { 33 | } 34 | 35 | @Override 36 | public boolean isValid(Object value, ConstraintValidatorContext context) { 37 | if (value != null && StringUtils.isNotEmpty(String.valueOf(value))) { 38 | return true; 39 | } 40 | 41 | String escapedValue = MessageSanitizer.escape(value == null ? null : value.toString()); 42 | String errorMessage = String.format("Expected not empty value, got '%s'", escapedValue); 43 | LOG.warn(errorMessage); 44 | 45 | context.buildConstraintViolationWithTemplate(errorMessage).addConstraintViolation(); 46 | return false; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /browserup-proxy-rest/src/main/java/com/browserup/bup/rest/validation/NotNullConstraint.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.rest.validation; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.validation.Constraint; 7 | import javax.validation.ConstraintValidator; 8 | import javax.validation.ConstraintValidatorContext; 9 | import javax.validation.Payload; 10 | 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Constraint(validatedBy = { NotNullConstraint.NotNullValidator.class }) 16 | public @interface NotNullConstraint { 17 | 18 | String message() default ""; 19 | 20 | String paramName() default ""; 21 | 22 | Class[] groups() default {}; 23 | 24 | Class[] payload() default {}; 25 | 26 | class NotNullValidator implements ConstraintValidator { 27 | private static final Logger LOG = LoggerFactory.getLogger(NotNullValidator.class); 28 | 29 | @Override 30 | public void initialize(NotNullConstraint constraintAnnotation) { 31 | } 32 | 33 | @Override 34 | public boolean isValid(Object value, ConstraintValidatorContext context) { 35 | if (value != null) { 36 | return true; 37 | } 38 | String errorMessage = "Expected not null value"; 39 | LOG.warn(errorMessage); 40 | 41 | context.buildConstraintViolationWithTemplate(errorMessage).addConstraintViolation(); 42 | return false; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /browserup-proxy-rest/src/main/java/com/browserup/bup/rest/validation/PatternConstraint.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.rest.validation; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | import java.util.regex.Pattern; 6 | 7 | import javax.validation.Constraint; 8 | import javax.validation.ConstraintValidator; 9 | import javax.validation.ConstraintValidatorContext; 10 | import javax.validation.Payload; 11 | 12 | import com.browserup.bup.rest.validation.util.MessageSanitizer; 13 | import org.apache.commons.lang3.StringUtils; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | @Retention(RetentionPolicy.RUNTIME) 18 | @Constraint(validatedBy = { PatternConstraint.PatternValidator.class }) 19 | public @interface PatternConstraint { 20 | 21 | String message() default ""; 22 | 23 | String paramName() default ""; 24 | 25 | Class[] groups() default {}; 26 | 27 | Class[] payload() default {}; 28 | 29 | class PatternValidator implements ConstraintValidator { 30 | private static final Logger LOG = LoggerFactory.getLogger(PatternValidator.class); 31 | 32 | @Override 33 | public void initialize(PatternConstraint constraintAnnotation) { 34 | } 35 | 36 | @Override 37 | public boolean isValid(String value, ConstraintValidatorContext context) { 38 | if (StringUtils.isEmpty(value)) { 39 | return true; 40 | } 41 | 42 | try { 43 | Pattern.compile(value); 44 | return true; 45 | } catch (Exception ex) { 46 | String escapedValue = MessageSanitizer.escape(value); 47 | String errorMessage = String.format("URL parameter '%s' is not a valid regexp", escapedValue); 48 | LOG.warn(errorMessage); 49 | 50 | context.buildConstraintViolationWithTemplate(errorMessage).addConstraintViolation(); 51 | } 52 | return false; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /browserup-proxy-rest/src/main/java/com/browserup/bup/rest/validation/mapper/ConstraintViolationExceptionMapper.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.rest.validation.mapper; 2 | 3 | import com.browserup.bup.rest.validation.mapper.model.ConstraintsErrors; 4 | import org.apache.commons.lang3.StringUtils; 5 | 6 | import javax.validation.ConstraintViolation; 7 | import javax.validation.ConstraintViolationException; 8 | import javax.ws.rs.core.MediaType; 9 | import javax.ws.rs.core.Response; 10 | import javax.ws.rs.ext.ExceptionMapper; 11 | import java.util.ArrayList; 12 | 13 | public class ConstraintViolationExceptionMapper implements ExceptionMapper { 14 | private static final String PARAMETER_NAME_ATTRIBUTE = "paramName"; 15 | 16 | @Override 17 | public Response toResponse(ConstraintViolationException exception) { 18 | return Response.status(Response.Status.BAD_REQUEST) 19 | .entity(createConstraintErrors(exception)) 20 | .type(MediaType.APPLICATION_JSON_TYPE) 21 | .build(); 22 | } 23 | 24 | private ConstraintsErrors createConstraintErrors(ConstraintViolationException exception) { 25 | ConstraintsErrors errors = new ConstraintsErrors(); 26 | exception.getConstraintViolations().stream() 27 | .filter(v -> StringUtils.isNotEmpty(v.getMessage())) 28 | .forEach(violation -> errors.addError(getArgumentName(violation), violation.getMessage())); 29 | 30 | return errors; 31 | } 32 | 33 | private String getArgumentName(ConstraintViolation violation) { 34 | String argumentIdentifier; 35 | Object paramName = violation.getConstraintDescriptor().getAttributes().get(PARAMETER_NAME_ATTRIBUTE); 36 | if (paramName instanceof String && paramName.toString().length() > 0) { 37 | argumentIdentifier = (String) paramName; 38 | } else { 39 | argumentIdentifier = StringUtils.substringAfterLast(violation.getPropertyPath().toString(), "."); 40 | } 41 | return argumentIdentifier; 42 | } 43 | } -------------------------------------------------------------------------------- /browserup-proxy-rest/src/main/java/com/browserup/bup/rest/validation/mapper/model/ArgumentConstraintsErrors.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.rest.validation.mapper.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | public class ArgumentConstraintsErrors { 9 | private String name = ""; 10 | private List errors = new ArrayList<>(); 11 | 12 | public String getName() { 13 | return name; 14 | } 15 | 16 | public void setName(String name) { 17 | this.name = name; 18 | } 19 | 20 | public List getErrors() { 21 | return errors; 22 | } 23 | 24 | public void setErrors(List errors) { 25 | this.errors = errors; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /browserup-proxy-rest/src/main/java/com/browserup/bup/rest/validation/mapper/model/ConstraintsErrors.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.rest.validation.mapper.model; 2 | 3 | import java.util.*; 4 | 5 | public class ConstraintsErrors { 6 | private List errors = new ArrayList<>(); 7 | 8 | public List getErrors() { 9 | return errors; 10 | } 11 | 12 | public void setErrors(List errors) { 13 | this.errors = errors; 14 | } 15 | 16 | private ArgumentConstraintsErrors getErrorsByArgumentName(String argumentName) { 17 | Optional argErrors = this.errors.stream() 18 | .filter(e -> e.getName().equals(argumentName)) 19 | .findFirst(); 20 | if (!argErrors.isPresent()) { 21 | ArgumentConstraintsErrors newArgErrors = new ArgumentConstraintsErrors(); 22 | newArgErrors.setName(argumentName); 23 | errors.add(newArgErrors); 24 | return newArgErrors; 25 | } else { 26 | return argErrors.get(); 27 | } 28 | } 29 | 30 | public void addError(String argumentName, String error) { 31 | getErrorsByArgumentName(argumentName).getErrors().add(error); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /browserup-proxy-rest/src/main/java/com/browserup/bup/rest/validation/util/MessageSanitizer.java: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.rest.validation.util; 2 | /* 3 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 4 | * Original from: 5 | * https://github.com/hibernate/hibernate-validator/blob/master/engine/src/main/java/org/hibernate/validator/internal/engine/messageinterpolation/util/InterpolationHelper.java 6 | */ 7 | /* 8 | * License: Apache License, Version 2.0 9 | * See the license file in the root directory or . 10 | */ 11 | 12 | import java.util.regex.Matcher; 13 | import java.util.regex.Pattern; 14 | 15 | public class MessageSanitizer { 16 | 17 | public static final char BEGIN_CHAR = '{'; 18 | public static final char END_CHAR = '}'; 19 | public static final char EL_DESIGNATOR = '$'; 20 | public static final char ESCAPE_CHARACTER = '\\'; 21 | 22 | private static final Pattern ESCAPE_PATTERN = Pattern.compile( "([\\" + ESCAPE_CHARACTER + BEGIN_CHAR + END_CHAR + EL_DESIGNATOR + "])" ); 23 | 24 | private MessageSanitizer() { 25 | } 26 | 27 | public static String escape(String message) { 28 | if ( message == null ) { 29 | return null; 30 | } 31 | return ESCAPE_PATTERN.matcher( message ).replaceAll( Matcher.quoteReplacement( String.valueOf( ESCAPE_CHARACTER ) ) + "$1" ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /browserup-proxy-rest/src/main/resources/swagger-config.yaml: -------------------------------------------------------------------------------- 1 | resourcePackages: 2 | - com.browserup.bup.rest.resource 3 | prettyPrint: true 4 | readerClass: com.browserup.bup.rest.openapi.CustomOpenApiReader 5 | cacheTTL: 0 6 | openAPI: 7 | info: 8 | version: '1.0' 9 | title: BrowserUp Proxy API 10 | contact: 11 | email: hello@browserup.com 12 | license: 13 | name: Apache 2.0 14 | url: http://www.apache.org/licenses/LICENSE-2.0.html -------------------------------------------------------------------------------- /browserup-proxy-rest/src/test/groovy/com/browserup/bup/proxy/mitmproxy/BaseRestTest.groovy: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.proxy.mitmproxy 2 | 3 | import com.browserup.bup.assertion.model.AssertionResult 4 | 5 | import static org.junit.Assert.* 6 | 7 | abstract class BaseRestTest extends WithRunningProxyRestTest { 8 | protected static final int TARGET_SERVER_RESPONSE_DELAY = 500 9 | protected static final int TARGET_SERVER_SLOW_RESPONSE_DELAY = 1000 10 | protected static final int SUCCESSFUL_ASSERTION_TIME_WITHIN = TARGET_SERVER_RESPONSE_DELAY + 100 11 | protected static final int FAILED_ASSERTION_TIME_WITHIN = TARGET_SERVER_RESPONSE_DELAY - 100 12 | protected static final int MILLISECONDS_BETWEEN_REQUESTS = 50 13 | 14 | abstract String getUrlPath(); 15 | 16 | String getFullUrlPath() { 17 | return "/proxy/${proxy.port}/${urlPath}" 18 | } 19 | 20 | static def assertAssertionNotNull(AssertionResult assertion) { 21 | assertNotNull('Expected to get non null assertion result', assertion) 22 | } 23 | 24 | static def assertAssertionPassed(AssertionResult assertion) { 25 | assertTrue("Expected assertion to pass", assertion.passed) 26 | assertFalse("Expected assertion to pass", assertion.failed) 27 | } 28 | 29 | static def assertAssertionFailed(AssertionResult assertion) { 30 | assertFalse("Expected assertion to fail", assertion.passed) 31 | assertTrue("Expected assertion to fail", assertion.failed) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /browserup-proxy-rest/src/test/groovy/com/browserup/bup/proxy/mitmproxy/ProxyManagerTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.proxy.mitmproxy; 6 | 7 | import com.browserup.bup.BrowserUpProxyServer; 8 | import com.browserup.bup.MitmProxyServer; 9 | import com.browserup.bup.proxy.MitmProxyManager; 10 | import com.browserup.bup.proxy.ProxyManager; 11 | import com.browserup.bup.proxy.guice.ConfigModule; 12 | import com.google.inject.Guice; 13 | import com.google.inject.Injector; 14 | import org.junit.After; 15 | import org.junit.Before; 16 | 17 | public abstract class ProxyManagerTest { 18 | protected MitmProxyManager proxyManager; 19 | 20 | public String[] getArgs() { 21 | return [] as String[] 22 | } 23 | 24 | @Before 25 | public void setUp() throws Exception { 26 | Injector injector = Guice.createInjector(new ConfigModule(getArgs())); 27 | proxyManager = injector.getInstance(MitmProxyManager.class); 28 | } 29 | 30 | @After 31 | public void tearDown() throws Exception { 32 | for (MitmProxyServer p : proxyManager.get()) { 33 | try { 34 | proxyManager.delete(p.getPort()); 35 | } catch (Exception e) { 36 | } 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /browserup-proxy-rest/src/test/groovy/com/browserup/bup/proxy/mitmproxy/ProxyPortAssignmentTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.proxy.mitmproxy; 6 | 7 | import com.browserup.bup.MitmProxyServer; 8 | import com.browserup.bup.exception.ProxyExistsException; 9 | import com.browserup.bup.exception.ProxyPortsExhaustedException; 10 | import org.junit.Test; 11 | 12 | import static org.junit.Assert.assertEquals; 13 | import static org.junit.Assert.fail; 14 | 15 | public class ProxyPortAssignmentTest extends ProxyManagerTest { 16 | @Override 17 | public String[] getArgs() { 18 | return new String[]{"--proxyPortRange", "9091-9093"}; 19 | } 20 | 21 | @Test 22 | public void testAutoAssignment() { 23 | int[] ports = {9091, 9092, 9093}; 24 | MitmProxyServer p; 25 | for(int port : ports){ 26 | p = proxyManager.create(); 27 | assertEquals(port, p.getPort()); 28 | } 29 | try{ 30 | proxyManager.create(); 31 | fail(); 32 | }catch(ProxyPortsExhaustedException e){ 33 | proxyManager.delete(9093); 34 | p = proxyManager.create(); 35 | assertEquals(9093, p.getPort()); 36 | 37 | proxyManager.delete(9091); 38 | p = proxyManager.create(); 39 | assertEquals(9091, p.getPort()); 40 | 41 | for(int port : ports){ 42 | proxyManager.delete(port); 43 | } 44 | } 45 | } 46 | 47 | @Test 48 | public void testManualAssignment() { 49 | MitmProxyServer p = proxyManager.create(9094); 50 | assertEquals(9094, p.getPort()); 51 | try{ 52 | proxyManager.create(9094); 53 | fail(); 54 | }catch(ProxyExistsException e){ 55 | assertEquals(9094, e.getPort()); 56 | proxyManager.delete(9094); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /browserup-proxy-rest/src/test/groovy/com/browserup/bup/proxy/mitmproxy/validation/PatternConstraintTest.groovy: -------------------------------------------------------------------------------- 1 | package com.browserup.bup.proxy.mitmproxy.validation 2 | 3 | import com.browserup.bup.rest.validation.PatternConstraint 4 | import org.junit.Assert 5 | import org.junit.Test 6 | import org.mockito.Mockito 7 | 8 | import javax.validation.ConstraintValidatorContext 9 | 10 | import static org.mockito.ArgumentMatchers.any 11 | import static org.mockito.Mockito.mock 12 | 13 | class PatternConstraintTest { 14 | 15 | @Test 16 | void validPattern() { 17 | def validator = new PatternConstraint.PatternValidator() 18 | def pattern = ".*" 19 | def mockedContext = mock(ConstraintValidatorContext) 20 | def mockedBuilder = mock(ConstraintValidatorContext.ConstraintViolationBuilder) 21 | 22 | Mockito.when(mockedContext.buildConstraintViolationWithTemplate(any(String))).thenReturn(mockedBuilder) 23 | 24 | def result = validator.isValid(pattern, mockedContext) 25 | 26 | Assert.assertTrue("Expected pattern validation to pass", result) 27 | } 28 | 29 | @Test 30 | void invalidPattern() { 31 | def validator = new PatternConstraint.PatternValidator() 32 | def pattern = "[" 33 | def mockedContext = mock(ConstraintValidatorContext) 34 | def mockedBuilder = mock(ConstraintValidatorContext.ConstraintViolationBuilder) 35 | 36 | Mockito.when(mockedContext.buildConstraintViolationWithTemplate(any(String))).thenReturn(mockedBuilder) 37 | 38 | def result = validator.isValid(pattern, mockedContext) 39 | 40 | Assert.assertFalse("Expected pattern validation to fail", result) 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /browserup-proxy-rest/src/test/java/com/browserup/bup/proxy/ProxyPortAssignmentTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.proxy; 6 | 7 | import static org.junit.Assert.assertEquals; 8 | import static org.junit.Assert.fail; 9 | 10 | import com.browserup.bup.MitmProxyServer; 11 | import com.browserup.bup.exception.ProxyExistsException; 12 | import com.browserup.bup.exception.ProxyPortsExhaustedException; 13 | import com.browserup.bup.proxy.test.util.ProxyManagerTest; 14 | import org.junit.Test; 15 | 16 | public class ProxyPortAssignmentTest extends ProxyManagerTest { 17 | @Override 18 | public String[] getArgs() { 19 | return new String[]{"--proxyPortRange", "9091-9093"}; 20 | } 21 | 22 | @Test 23 | public void testAutoAssignment() { 24 | int[] ports = {9091, 9092, 9093}; 25 | MitmProxyServer p; 26 | for(int port : ports){ 27 | p = proxyManager.create(); 28 | assertEquals(port, p.getPort()); 29 | } 30 | try{ 31 | proxyManager.create(); 32 | fail(); 33 | }catch(ProxyPortsExhaustedException e){ 34 | proxyManager.delete(9093); 35 | p = proxyManager.create(); 36 | assertEquals(9093, p.getPort()); 37 | 38 | proxyManager.delete(9091); 39 | p = proxyManager.create(); 40 | assertEquals(9091, p.getPort()); 41 | 42 | for(int port : ports){ 43 | proxyManager.delete(port); 44 | } 45 | } 46 | } 47 | 48 | @Test 49 | public void testManualAssignment() { 50 | MitmProxyServer p = proxyManager.create(9094); 51 | assertEquals(9094, p.getPort()); 52 | try{ 53 | proxyManager.create(9094); 54 | fail(); 55 | }catch(ProxyExistsException e){ 56 | assertEquals(9094, e.getPort()); 57 | proxyManager.delete(9094); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /browserup-proxy-rest/src/test/java/com/browserup/bup/proxy/test/util/ProxyManagerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | package com.browserup.bup.proxy.test.util; 6 | 7 | import com.browserup.bup.MitmProxyServer; 8 | import com.browserup.bup.proxy.MitmProxyManager; 9 | import com.google.inject.Guice; 10 | import com.google.inject.Injector; 11 | import com.browserup.bup.BrowserUpProxyServer; 12 | import com.browserup.bup.proxy.ProxyManager; 13 | import com.browserup.bup.proxy.guice.ConfigModule; 14 | import org.junit.After; 15 | import org.junit.Before; 16 | 17 | public abstract class ProxyManagerTest { 18 | protected MitmProxyManager proxyManager; 19 | 20 | public String[] getArgs() { 21 | return new String[] {}; 22 | } 23 | 24 | @Before 25 | public void setUp() throws Exception { 26 | Injector injector = Guice.createInjector(new ConfigModule(getArgs())); 27 | proxyManager = injector.getInstance(MitmProxyManager.class); 28 | } 29 | 30 | @After 31 | public void tearDown() throws Exception { 32 | for(MitmProxyServer p : proxyManager.get()){ 33 | try{ 34 | proxyManager.delete(p.getPort()); 35 | }catch(Exception e){ } 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /browserup-proxy-rest/src/test/resources/log4j2-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "configuration" : { 3 | "name": "test", 4 | "appenders": { 5 | "Console": { 6 | "name": "console", 7 | "target": "SYSTEM_OUT", 8 | "PatternLayout": { 9 | "pattern": "%-7r %date %level [%thread] %logger - %msg%n" 10 | } 11 | } 12 | }, 13 | 14 | "loggers": { 15 | "root": { 16 | "level": "info", 17 | "appender-ref": { 18 | "ref": "console" 19 | } 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /browserup-proxy.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserup/browserup-proxy/0f28ec6e8f1423e1fdb014fded72e9278bdd083f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /nohup.out: -------------------------------------------------------------------------------- 1 | Error starting proxy server: OSError(48, 'Address already in use') 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Modifications Copyright (c) 2019 BrowserUp, Inc. 3 | */ 4 | 5 | include 'browserup-proxy-core' 6 | include 'browserup-proxy-dist' 7 | include 'browserup-proxy-rest' 8 | include 'browserup-proxy-mitm' 9 | include 'browserup-proxy-rest-clients' 10 | --------------------------------------------------------------------------------